reGeorgSocksProxy是一款渗透测试中常用到的http2socks隧道,本文简单介绍reGeorgSocksProxy的工作原理以及我在使用过程中遇到的小问题,并给出我的修改方法及最终修改后的代码。

reGeorgSocksProxy工作原理

reGeorgSocksProxy由服务端和客户端两部分组成。服务端有php、aspx、asph、jsp、node.js等多个版本,客户端则由python编写。其工作原理可简单描述为python客户端在本地监听一个端口,提供socks服务,并将数据通过http/https协议发送到服务端上,并从服务端上用socket实现转发。具体流程:

  • 客户端在本地启动,用bind()方法绑定-l和-p参数传入的本地地址
  • 客户端监听的地址有连接传入时,开启一个新线程,实例化session类,并在新线程里由handleSocks()方法对socks协议版本进行判断并调用相应的解析方法(parseSocks4()parseSocks5())
  • 客户端从数据中解析出目标地址和目标端口,由setupRemoteSession()方法与服务端进行通讯,在服务端建立新的session并创建socket对象,连接到目标主机,最后将实例化后的socket对象保存到当前session,由Set-Cookie头返回sessionid
  • 客户端在setupRemoteSession()方法返回后,保存保存sessionid到当前线程,后续由writer()reader()方法以sessionid为标识,实现每个连接的数据交互
  • 交互完成后,客户端调用closeRemoteSession()方法,通知服务端关闭远程链接并销毁session

其中reGeorgSocksProxy的交互命令通过HTTP Headers发送,socks代理数据通过HTTP Body发送。

两处小问题

使用过程中遇到的部分小问题,均在比较特殊的环境中才能触发。

多个Set-Cookie头

上文提到,每个链接之间的区分是靠sessionid,也就是Cookie。我们来看看reGeorgSocksProxy中处理Cookie的代码:
setupRemoteSession()方法中,直接通过cookie = response.getheader("set-cookie")return cookie来获取并且返回Cookie,而调用该方法的地方self.cookie = self.setupRemoteSession(target,targetPort)直接将函数返回值赋值到成员变量self.cookie中。我们接着看看后面引用self.cookie的地方。
reader()中:
headers = {"X-CMD": "READ", "Cookie": self.cookie, "Connection": "Keep-Alive"}先定义头部
response = conn.urlopen('POST', self.connectString+"?cmd=read", headers=headers, body="")接着发送http请求。
这看上去并没有什么问题,我们知道,Set-Cookie头的格式大概是这样的
Set-Cookie:PHPSESSID=638b23sd4838gvib78afgf7p36; path=/
而真正的Cookie键值对仅仅是PHPSESSID=638b23sd4838gvib78afgf7p36部分,后面path是Cookie的作用域。上述代码获取并存储下来的部分却是PHPSESSID=638b23sd4838gvib78afgf7p36; path=/,所以后续http请求发送的Cookie也是这部分。但是这也并没有什么影响,因为按照这个格式发送http请求的话,可以认为是发送了两组Cookie。
然而,在某些情况下,为了单点登录(SSO)或者其他原因,在web容器层面会发送一些的跨域Cookie,这样就导致客户端收到的HTTP Response包中Set-Cookie头不止一个,此时,上述代码就出问题了。
在存在多个Set-Cookie头的情况下,response.getheader("set-cookie")会返回一个List,这样在后续通讯将self.cookie拼接到headers中时,就会出问题,导致客户端不能正常区分会话,造成交互失败。
我的解决方法是做了一个小小的hack,实现了一个cookieFilter()方法,在setupRemoteSession()返回前以及reader()writer()里对self.cookie赋值前,均先调用cookieFilter()对Cookie进行格式化输出,保证其正确性,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def cookiesFilter(self,cookie):
newcookies = [] #临时List
if cookie:
for x in cookie.split(','):
if ';' in x:
#如果;存在,则为带属性(如作用域名,有效时间等)的cookie,用正则匹配k-v对,并忽略跨域cookie
match = re.findall('((.+?)=(.+?));(\sdomain=(.+);)?',x.strip())
if match:
if match[0][4] == self.httpHost or match[0][4] == '':
self.cookieDic[match[0][1]] = match[0][2]
else:
self.cookieDic[x.split('=')[0]] = x.split('=')[1]
#self.cookieDic为用来保存cookie的字典
for k in self.cookieDic.keys():
newcookies.append(k + '=' + self.cookieDic[k])
return '; '.join(newcookies)

这样就保证了在多个Set-Cookie头的情况下reGeorgSocksProxy能正确处理Cookie并正常工作。

Java WEB、ASP.NET等存在filter的情况下的登录问题

另一个问题就是在某些java web环境下存在filter,会导致对服务端的访问被拦截,必须带上登录后的Cookie才能正常访问。而根据上文分析,reGeorgSocksProxy客户端与服务端的通讯是基于session来区分的,并不支持在指定的session下进行。
再来重复一下reGeorgSocksProxy的流程:

  • 客户端监听
  • 传入链接
  • 服务端新建session,初始化socket,保存到session
  • 交互
  • 关闭socket,销毁session
  • 结束

于是我们要解决这个问题,本质就是修改客户端和服务端,使其支持在指定的session下工作,修改后的期望流程是:

  • 客户端监听
  • 传入连接
  • 初始化socket,保存到指定session
  • 交互
  • 从指定session中关闭socket,并释放
  • 结束

修改的地方:

  • 增加命令行参数,用于输入指定的Cookie
  • 修改程序逻辑,如果传入了自定的Cookie,则在session类的构造函数中将自定Cookie写入self.cookieDic,并生成一个随机id,用于在同一个session里却分不同的socket
  • 在调用初始化方法setupRemoteSession()时,如果传入了自定的Cookie,则要带上自定Cookie,表示复用已存在的session,并同时传入生成的随机socket id,在初始化socket时用于命名区分
  • 同理,如果处于复用session模,reader()writer()方法中也要向服务端传入socket id
  • 服务端作相应修改,具体见代码

好了,大概就是如上,懒得写了,详细改动见代码。
下载地址