^

流量加密编程指南

前言

现在国网、移动的小程序、app基本都对流量请求进行了加密。这很大程序上让 渗透测试和漏洞挖掘的工作变得异常困难,所以我玩不过他,我就成为他,也当自己实习工作时期的学习活动好了。

根据我的一些挖洞经历,基本上都是流量请求body做rsa加密,通过前端的公钥进行加密,然后后端通过私钥进行解密。与aes加密相比,rsa加密是一种非对称的加密算法,aes是对称的加密算法。aes的key会在前端获取,从而虽然流量进行了加密,但是攻击者大可以根据正则匹配到key,然后做内容解密,或者是自己做一个新的请求。

折腾了一下午,成功让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,测试一下,这些都没问题。直到我部署到了生产的接口时,出现了问题。

6128e491db396.png

参考了一下,问了下别的师傅。才知道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中就保存了我们的解密内容,但是测试发现结果是空的,又懵逼了。

6128e6b15c4b8.png

发了朋友圈之后,还是没有人回答。还是得靠自己。看了点资料,发现rsa对解密也有长度限制。

6128e7533b5ad.png

那我就知道怎么写了,对chunks变量做128切割。然后分段解密。

foreach ($chunks as $chunk) {
    openssl_private_decrypt($chunk, $decrypt_data, $private_key);   
    // echo "$decrypt_data";
    $data .= $decrypt_data;
}


echo $data;

重新测试成功

效果

6128e7935d0fe.png