在在测试一个目标站点的过程中发现请求包参数只要有一点点变化,就无法正常走应用流程,观察参数名,发现其中可疑参数 signature 。 于是开启了signature 生成机制的探索之路,并最终实现一个代理脚本保证参数fuzz可以继续走下去。
实际请求包参数如下
token=o5z2z6f0U9X6T1x4T3Z1O685N5K0z6A2D8B37675p2k3h5c889e9q253b42243q985f9006526q1o929k3j605q80731& time=1559801535& version=2& signature=5af79f46836aa9d935615bee565ba9ab& md5str=time1559801535tokeno5z2z6f0U9X6T1x4T3Z1O685N5K0z6A2D8B37675p2k3h5c889e9q253b42243q985f9006526q1o929k3j605q80731version2
以为直接是 md5str 做一次md5 生成 signature,然而并不是。 开始寻找
前端由 vue 制作,在app.js 中发现下面这段代码
{ token: e.token, time: t, version: 2, signature: l, md5str: f }
function(e, t, n) { "use strict"; Object.defineProperty(t, "__esModule", { value: !0 }); var r = n("mtWM"), i = n.n(r), o = n("mw3O"), s = n.n(o), a = n("vaVw"), u = n("c03J"), c = n("wtEF"), l = i.a.create({ baseURL: "/api.n/index.php", timeout: 3e4, headers: { "Content-Type": "application/x-www-form-urlencoded;charset=utf-8" } }); l.interceptors.request.use(function(e) { return Object.assign(e, { data: function(e) { var t = Date.parse(new Date) / 1e3, n = {}, r = []; for (var i in e.time = t, e.version = 2, e) if ("key" !== i) { n[i] = e[i]; var o = { key: i, value: e[i] }; r.push(o); for (var u = 0; u < r.length; u++) for (var c = u + 1; c < r.length; c++) r[u].key > r[c].key && (r[u] = [r[c], r[c] = r[u]][0]) } var l = ""; r.map(function(e) { "" !== e.value && (l += "" + e.key + e.value) }); var f = l; return l += e.key, l = Object(a.hexMd5)(l), n = Object.assign({ token: e.token, time: t, version: 2, signature: l, md5str: f }, n), s.a.stringify(n) } (e.data) }) }
最后有一句 l += e.key,
, signature 由 md5str + e.key
md5 得到
更新:不是解析 是由于目标配置错误,将xxxx.js文件对应的 xxxxx.js.map放上去了,在map文件里可以看见具体的代码
搜索关键词 signature 找到操作过程
requestStr
是遍历参数 拼接字符串形成( js 在遍历时会按key 对参数进行排序,故requestStr
是固定的)
requestStr += data.key
md5str
可以从请求包参数中生成了,现在还差找到key
继续搜索 找到 getTokenKey
函数 直接post 请求
export function getTokenKey (data) { return request({ url: '?vcode/key', method: 'post', data }) } getTokenKey({}).then(response => { if (response.data.status === 200) { const { token } = response.data const { key, siteCode } = response.data.data this.SET_ONEFRIST(true) this.SET_TOKEN(token) this.SET_KEY(key) this.getGameList({token, key}) this.SET_SITECODE(siteCode) this.getBannerData({token, key}) this.getqqService({token, key}) this.getappDownload({token, key}) this._getBullentin({token, key}) // 获取跑马灯信息 this.getRegisterShow({token, key}) // 获取初始化注册信息配置 } }) 请求包如下 POST /xx/index.php/?vcode/key HTTP/1.1 Host: xxxxxxx User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0 Accept: application/json, text/plain, */* Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded;charset=utf-8 Content-Length: 0 Connection: close HTTP/1.1 200 OK Date: Sun, 09 Jun 2019 13:11:36 GMT Content-Type: text/html; charset=utf-8 Connection: close Server: nginx Vary: Accept-Encoding Access-Control-Allow-Origin: * Set-Cookie: PHPSESSID=5dv7o0nffsks0m161i5bg24es7; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Content-Length: 233 {"status":200,"data":{"key":"08e95876b4d844789c00b350c1dc3e5d","siteCode":"hervu"},"token":"q7c2z319g3H1d8v5w8X7c2a0z7T6k9d8x2D9O2Z5d75275v100o7f2n3s2f8s2k9m1056915i618b15425g4e54370s4","errorMsg":"(token\u65e0\u6548)","cache":false}
多次请求发现 其 key并不会变化,key只是最后进行hash 的扰乱,token 会变换
key=08e95876b4d844789c00b350c1dc3e5d
signature=5af79f46836aa9d935615bee565ba9ab
md5str=time1559801535tokeno5z2z6f0U9X6T1x4T3Z1O685N5K0z6A2D8B37675p2k3h5c889e9q253b42243q985f9006526q1o929k3j605q80731version2
验证了请求包中的signature
hexmd5(time1559801535tokeno5z2z6f0U9X6T1x4T3Z1O685N5K0z6A2D8B37675p2k3h5c889e9q253b42243q985f9006526q1o929k3j605q80731version208e95876b4d844789c00b350c1dc3e5d)
每个fuzz请求都需要进行重新计算一次signature,为了适应已有的扫描工具(sqlmap 或 burp 通过使用代理,继续对应用进行fuzz)考 虑将 signature 的计算做到一个http/https 代理中。
考虑到以后遇到不同站点的 signature 可能使用不同的算法,github 上参考下面项目,用 tornado 实现了功能。
signature 生成代码如下
def upadte_post_body(body): ''' token=o5z2z6f0U9X6T1x4T3Z1O685N5K0z6A2D8B37675p2k3h5c889e9q253b42243q985f9006526q1o929k3j605q80731& time=1559801535& version=2& signature=5af79f46836aa9d935615bee565ba9ab& md5str=time1559801535tokeno5z2z6f0U9X6T1x4T3Z1O685N5K0z6A2D8B37675p2k3h5c889e9q253b42243q985f9006526q1o929k3j605q80731version2 ''' key = '08e95876b4d844789c00b350c1dc3e5d' paramlist = body.split('&') paramlist.sort() # Simulate js key field sorting paramdic = {} new_md5str = '' old_md5str = '' new_sign = '' old_sign = '' for i in paramlist: _ = i.split('=') if _[0] != 'md5str' and _[0] != 'signature': new_md5str = new_md5str + _[0] + _[1] elif _[0] == 'md5str': old_md5str = _[1] elif _[0] == 'signature': old_sign = _[1] md5 = hashlib.md5() hashstring = new_md5str + key md5.update(hashstring.encode('utf-8')) new_sign = md5.hexdigest() body = body.replace(old_sign,new_sign) body = body.replace(old_md5str,new_md5str) logger.debug('new_sign %s new_md5str %s',new_sign, new_md5str) return body
完整项目地址
针对不同站点前端signature 的生成机制,修改 upadte_post_body 函数。
常规情况下 运行 python proxy_add_sign.py -p 8080
即可在 8080 端口开启一个http代理
https 的问题
由于仅仅为了测试,sqlmap、burp 不必了解目标站点是否为https,就没有实现自签名证书的 https 代理。仅将sqlmap、burp发来的http包,以https的方式请求至源站,对工具来说目标只是一个正常的http服务。
如下命令测试目标站为https 的情况python proxy_add_sign.py -p 8080 -s True