0x00 前言
reGeorg 这款工具大家在内网渗透过程中是非常常用的,但是网上关于这块工具的介绍更多是 《如何配合Proxifier代理工具来进行使用》 但是并没有很多文章去分析 reGeorg 的实现原理和源码,所以今天这篇文章就来介绍一下这款工具是如何来实现的流量代理的
0x01 利用
在介绍原理之前先简单介绍一下 reGeorg 的利用
reGeorg 提供了多款不同语言的 tunnel 脚本,使得我们能在不同环境下进行选择
当拿到一台主机的 Shell 的时候,可将文件传上去,我这边举个例子,这里我们将 tunnel.jsp 传递到 tomcat 的 examples/ 目录下
访问 jsp 之后,如果显示 Georg says, 'All seems fine
就说明 jsp 脚本正常运行了
python 运行 reGeorgSocksProxy 启动本地 socks 服务器,可以看到开在了 127.0.0.1:8888
在 Proxifier 中修改设置代理即可
0x02 原理分析
这里借用 pivotnacci 工具的流程图,这张图描述的很清楚
reGeorg 工具实际就是本地通过 reGeorgSocksProxy 脚本启动一个 socks 服务器,然后 socks 客户端 (例如:Proxifier) 连接 socks 服务器后发送 socks 请求,同时 socks 服务器与远端代理服务器相连接,于是 socks 服务器将我们的请求数据(通过 HTTP 隧道)发送到远端代理服务器。远端服务器(Web Server)与内网主机建立连接后将请求转发至内网主机。该流程为发送数据的过程,获取内网的数据也是相同
挂 Socks 代理发包过程
本地 Socks Client ----> reGeorgSocksProxy 启动的Socks Server ----> 远端 Web Server ----> 内网站点
挂 Socks 代理获取数据
内网站点 ----> 远端 Web Server ----> reGeorgSocksProxy 启动的Socks Server ----> 本地 Socks Client
ps: 远端 Web Server 即指上传了 tunnel.jsp 的 Web Server
具体请看源码分析部分~
0x03 源码分析
主要文件就俩:reGeorgSocksProxy.py 和 tunnel.jsp
直接从 reGeorgSocksProxy.py 的 main 函数开始看起来
首先在 if 判断语句中调用了 askGeorg 函数,该函数主要判断远端服务器中我们传上去的脚本是否正常运行
函数代码很简单就是判断 html 页面中是否存在 Georg says, 'All seems fine'
这里本地开启了 socksServer ,绑定了本地地址和端口,默认为 127.0.0.1 和 8888,然后接受本地转发的流量同时将远程 Webserver 地址作为参数传入 session 对象的构造函数
先来看到构造函数可以发现这里是对传入 url 的解析,并根据 http/https 来选择对应的 url 连接池
由于调用的是 session().start() 我们来看到 run 方法,可以看到会先调用 handleSocks 来处理 socks 客户端 传过来的的数据
首先会根据第一位来判断是 socks5 还是 socks4
这里需要结合 socks5 的对应 RFC 文档
文章链接:https://www.ietf.org/rfc/rfc1928.txt
在 socks 建立连接之前会先进行一次协商
socks client 会发送 VER、NMETHODS、METHODS 这三个值来告诉 socks 服务端一些基本信息例如这里的 VER 就是 socks 版本号
于是在函数中检测到了 "\x05" 就调用 parseSocks5 来解析,检测到了 "\x04" 就调用 parseSocks4 来进行解析,由于现在都是用 socks5 ,因为 socks4 不支持身份认证等原因现在已经不怎么使用了,所以我们来看到 parseSocks5 函数
前面提到 socks5 在建立连接之后会进行一次协商,之前只是 socks 客户端发来的请求,我们的 socks 服务端并没有进行回应,所以在该函数中首先会对请求进行回应
可在 RFC 中看到详细的规则
在文件最上方的全局定义中可找到 VER 和 METHOD 的变量,结合 RFC 的信息,返回的 METHOD 为 "\x00" 说明没有身份认证的需求
VER = "\x05"
METHOD = "\x00"
sock.sendall(VER + METHOD)
继续往下看 parseSocks5 函数,上面 socks 服务器已进行回应,于是 socks 就建立连接了,接下来就是 socks client 发送请求
同样的还是需要参考 RFC
上面的代码就是对应处理每个变量的值,这里针对 proxychains 进行了特殊处理,个人猜测 proxychains 发的包会在前面加一个 "\x02" (如有错误欢迎师傅指出)
if ver == "\x02": # this is a hack for proxychains
ver, cmd, rsv, atyp = (sock.recv(1), sock.recv(1), sock.recv(1), sock.recv(1))
else:
cmd, rsv, atyp = (sock.recv(1), sock.recv(1), sock.recv(1))
然后获取到了 cmd, rsv, atyp 三个变量
首先对 atype 返回的三种可能(IPv4、Hostname、IPv6)进行不同的处理
接下来就是针对包中的 CMD 变量进行处理,根据 RFC 中介绍 CMD 主要有三种分别对应连接建立、UDP、BIND 可以从代码中针对 CONNECT 状态进行处理,另外两种则是抛出了报错
对另外两种感兴趣的可以查看:https://www.jianshu.com/p/55c0259d1a36
在 cmd == "\x01"
的判断中,将 IP 和 PORT 传入 setupRemoteSession 函数,注意这里的 IP 和 PORT 是我们挂上 socks5 代理之后要访问站点和端口(例如我们要通过 socks5 代理访问 https://www.baidu.com 那么此时 IP 和 PORT 就对应百度的 ip 和 对应端口 443)
通过 setupRemoteSession 函数获取返回 cookie 然后来进行发送数据
sock.sendall(VER + SUCCESS + "\x00" + "\x01" + serverIp + chr(targetPort / 256) + chr(targetPort % 256))
跟进 setupRemoteSession 函数
该函数中将 ip 和 port 放到了 header 中利用 ?cmd=connect&target=%s&port=%d
的形式来进行请求,如果返回是 200 就说明连接成功建立,同时获取 cookie
这里需要结合 tunnel.jsp 一起分析
在 tunnel.jsp 中会获取 Header 中 X-TARGET 和 X-PORT 的参数来与目标进行 socket 的连接建立,并且将结果存到 session 的 socket 属性中
结合 wireshark 抓的流量就很清晰
进行连接的建立 ?cmd=connect&target=192.168.1.3&port=443
于是远端的 Web Server 与内网主机就建立了连接,接下来就可以开始数据的交互了
再次回到 run 函数,前面都是对连接建立的操作,从这里开始就进行数据的交互了
在 run 函数中分别调用了 reader 和 writer 函数来进行数据的读写
reader 部分
先看 reader 函数,reader 函数首先就是去读取数据,比如我们要获取内网的一个 web页面的 html 信息,那么就是通过 reader 来进行的 ,我们远端 WebServer 读取内网站点页面,然后我们本地 socks Server 获取远端 Web Server 中的信息
具体的从代码中来看吧~
先看 tunnel.jsp
首先从 session 中的 socket 属性里取出之前已建立连接的 socketChannel 对象,然后从 socketChannel 中进行内容的读取,写到 response 中
然后 reGeorgSocksProxy 的 reader 函数会通过 ?cmd=read
对 response 进行读取,然后通过 self.pSocket.send(data)
读取到的数据转发到我们的 socks client
Wireshark 抓取流量如下,很清晰明了了
writer 部分
Writer 部分就是将我们的数据转发到目标站点,数据流向大致如下:
本地 Socks Client ----> reGeorgSocksProxy 启动到 Socks Server ----> 远端 Web Server ----> 内网站点
reGeorgSocksProxy#writer 会将收到的数据放到 http 的 POST 包中来进行发送
通过 ?cmd=forward
来作为标识,该方法会将数据通过 http 包的方式发送到远端 Web Server 的 tunnel.jsp
来看到 tunnel.jsp 的 FORWARD 判断处
同样的也是首先从 socket 中取出之前初始化好的 socketChannel ,然后会调用 request.getInputStream().read()
来读取我们发过来的 http 包中的 body 的内容,然后通过 socketChannel.write 将数据发送到目标主机
Wireshark 大致的流量包就是以下
0x04 思考
- 通常代理是打上去多人一起使用的,那么 reGeorg 是如何解决多人的问题的呢?
在 reGeorg 中通过 Cookie 来进行区分,因为 tunnel.jsp 会将创建好的 Channel 对象存在 Session 中,并将 Cookie 通过 Set-Cookie 来进行返回,然后每次获取数据或者发送都会带上 Cookie 从而达到区分的作用
- reGeorg 是一个很好的工具但是感觉也缺少了一些东西
首先 reGeorg 的特征特别明显,各种 get 和 header 在日志中看的一清二楚
第二 没有设置 password 等功能导致上传上去的脚本非常容易被另外攻击队所利用
- 过度利用 Header 导致在一些 Nginx 反代会不好用
最好的解决办法是放在 POST Body 来进行传输