reGeorgSocksProxy修改版-可指定Cookie
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
16def 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 - 服务端作相应修改,具体见代码
好了,大概就是如上,懒得写了,详细改动见代码。
下载地址