流量加密编程指南
前言
现在国网、移动的小程序、app基本都对流量请求进行了加密。这很大程序上让渗透测试和漏洞挖掘的工作变得异常困难。根据一些挖洞经历,基本上都是通过前端的公钥进行数据加密,然后后端通过私钥进行解密。折腾了一下午,成功让un1kpoc平台的请求的body被rsa加密,最终也不影响业务运行。
前期准备
简单的来梳理一下我们的业务流程:
1.前端获取公钥(publickey)
2.对请求json数据用公钥加密
3.传递给后端
4.私钥解密
5.常规的后期业务逻辑
所以我们需要做的首先是准备一个配套的公私钥。
openssl genrsa -out pri.pem 1024 //生成私钥 openssl rsa -in pri.pem -pubout -out pub.pem //根据私钥生成公钥
js有个库,叫jsencrypt.min.js,大致参考了一下教程,最简单的写法如下
var rsa = new RSAKey(); //建立一个rsa对象 rsa.setPublic(modulus, exponent); var modulus = "***"; //生成私钥的模量 命令为 openssl rsa -in pri.pem -noout -modulu var exponent = "10001"; //设置e var res = rsa.encrypt("加密内容"); console.log(res)
这里是坑点之一,本来产生加密内容非常的正常顺利。加密内容为123,123456a,测试一下,这些都没问题。直到我部署到了生产的接口时,出现了问题。
参考了一下别人,才知道rsa加密不了长数据。而我的请求包的body是很长的,它会根据漏洞poc改变长度,我也无法确定。
看了很多资料,有个师傅写的方法进入了我的视线:
JSEncrypt.prototype.encryptLong2 = function (string) { var k = this.getKey(); try { var lt = ""; var ct = ""; //RSA每次加密117bytes,需要辅助方法判断字符串截取位置 //1.获取字符串截取点 var bytes = new Array(); bytes.push(0); var byteNo = 0; var len, c; len = string.length; var temp = 0; for (var i = 0; i < len; i++) { c = string.charCodeAt(i); if (c >= 0x010000 && c <= 0x10FFFF) { byteNo += 4; } else if (c >= 0x000800 && c <= 0x00FFFF) { byteNo += 3; } else if (c >= 0x000080 && c <= 0x0007FF) { byteNo += 2; } else { byteNo += 1; } if ((byteNo % 117) >= 114 || (byteNo % 117) == 0) { if (byteNo - temp >= 114) { bytes.push(i); temp = byteNo; } } } //2.截取字符串并分段加密 if (bytes.length > 1) { for (var i = 0; i < bytes.length - 1; i++) { var str; if (i == 0) { str = string.substring(0, bytes[i + 1] + 1); } else { str = string.substring(bytes[i] + 1, bytes[i + 1] + 1); } var t1 = k.encrypt(str); ct += t1; } ; if (bytes[bytes.length - 1] != string.length - 1) { var lastStr = string.substring(bytes[bytes.length - 1] + 1); ct += k.encrypt(lastStr); } return hexToBytes(ct); } var t = k.encrypt(string); var y = hexToBytes(t); return y; } catch (ex) { return false; } };
方法大致意思就是,加密的字符超过了一定长度。就以117为单位进行分段加密,至于为什么可以这样,这里就不阐述了。
既然是别人写的就拿过来用吧,但是他产生的是字节数组。
之前后端的解密代码,我用的是php,因为很简单,他解密的字段类型是十六进制字符串
$encrypt_data = pack("H*", $hex_encrypt_data);
我们可控的是hex_encrypt_data,但是我们的字节数组是无法传入的。
于是只能前端写的方法,把字节数组转换成十六进制的字符串
function hexToBytes(hex) { for (var bytes = [], c = 0; c < hex.length; c += 2) bytes.push(parseInt(hex.substr(c, 2), 16)); return bytes; } function bytesToHex(bytes) { for (var hex = [], i = 0; i < bytes.length; i++) { hex.push((bytes[i] >>> 4).toString(16)); hex.push((bytes[i] & 0xF).toString(16)); } return hex.join(""); }
我们对提交的poc进行rsa加密,把内容打印出来,通过上面两个方法,成功产生了长字符串产生的rsa加密后的字符串。于是前端的任务已经完成了,我们把注释删掉,直接进行解密。
openssl_private_decrypt($encrypt_data, $decrypt_data, $private_key);
这样$decrypt_data中就保存了我们的解密内容,但是测试发现结果是空的,又懵逼了。看了点资料,发现rsa对解密也有长度限制。
那我就知道怎么写了,对chunks变量做128切割。然后分段解密。
foreach ($chunks as $chunk) { openssl_private_decrypt($chunk, $decrypt_data, $private_key); // echo "$decrypt_data"; $data .= $decrypt_data; } echo $data;
重新测试成功
效果