写在前面
安全测试需要, 为防止后台响应数据返给前台过程中被篡改前台再拿被篡改后的数据进行接下来的操作影响正常业务, 决定采用RSA对响应数据进行签名和验签, 于是有了这篇.
我这里所谓的返给前台的数据只是想加密用户验证通过与否的字段success是true还是false, 前台拿这个success作为判断依据进行下一步的操作, 是进一步向后台发起请求还是直接弹出错误消息.照测试结果看这是个逻辑漏洞, 即使后台返回的是false, 在返回前台的过程中响应包被劫获, 将false改为true, 这样的操作也是能做到的(BurpSuit). 所以后台响应数据尽量不要再二次使用. 那既然能篡改, 如何防止流氓篡改呢?
说一下整体思路: 首先生成密钥对, 私钥存放在后台用于签名, 公钥存放在前台用于验签. 所谓签名就是指拿明文+私钥生成的签名结果, 返回数据给前台时将明文+签名结果一并返给前台, 前台用公钥+接收到的明文+签名结果进行验签, 这样即使响应包被劫获, 篡改明文后, 验证签名时也不会验证通过, 从而达到响应数据防篡改的目的.
接下来说一下具体步骤.
正文(具体步骤)
1.在线生成密钥对
采用在线工具生成密钥对, 私钥密码可填可不填, 网址:http://web.chacuo.net/netrsakeypair 截图中的密钥对是写博客时重新生成的, 和代码中的不一样不要见怪~
生成密钥对备用.
2.后台签名
Controller层java代码
privateAjaxJson getAjaxJson(HttpServletRequest req, HttpServletResponse res) {
AjaxJson j= newAjaxJson();
String passresStr= GetRSAStr.getResStr(true);//pass明文
j.setRsaStr(passresStr);//明文
try{
j.setSign(RSAEnDeUtils.sign(passresStr, RSAEnDeUtils.getPrivateKey(RSAEnDeUtils.getPrivateKey())));//pass签名
} catch(Exception e) {
e.printStackTrace();
}//============================client判断开始============================
String sessionCounterStr =sysConfigService.queryConfValueByConfId(SysParamConfig.forSecurityTest.SESSION_COUNTER.getParam());
BigInteger counterParam= newBigInteger(sessionCounterStr);int clientCount =clientManager.getAllClient().size();
BigInteger clients= newBigInteger(String.valueOf(clientCount));if (clients.compareTo(counterParam) >= 0) {
j.setSuccess(false);
j.setRsaStr(GetRSAStr.getResStr(false));//notpass明文
j.setMsg("系统已达最大会话数!");returnj;
}
......
简单说一下这段代码逻辑, 这是校验登录用户用户名密码的其中一段代码.(关键代码已加粗)
思路就是进入该方法时, 把将要返回给前台的结果ajaxJson中首先设置两个参数, 一个属性名为rsaStr, 另一个属性名为sign.
其中rsaStr用于存放随机生成的uuid, sign用于存放由该uuid和第1步的私钥生成的签名结果.
当程序中遇到用户校验不通过时生成另一个uuid替换上面的rsaStr的值(sign并不替换), 由ajaxJson一并返回给前台.
后台关键代码(两个工具类)
签名用到的工具类RSAEnDeUtils.java和生成uuid的工具类GetRSAStr.java如下:
RSAEnDeUtils.java
packageorg.jeecgframework.web.system.util;importorg.apache.commons.codec.binary.Base64;importjavax.crypto.Cipher;importjava.io.ByteArrayOutputStream;import java.security.*;importjava.security.spec.PKCS8EncodedKeySpec;importjava.security.spec.X509EncodedKeySpec;importjava.util.UUID;public classRSAEnDeUtils {//私钥
private static final String PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMlEZXt7J32l4s84ioWDeiKidaqmauNWKTbDInNaq/yK3fIC+j+jg5HjTJutk8ernbqTqeC+oc4I0m+Gs3vBc1QQhP49fIu7B9Y/TgjgQMFLcGfctxMwCcZWgiRrR/k7qWjcjRi09bCfKFxCGsda5OJ60YLQI3