点击上方[蓝字],关注我们
**建议大家把公众号“Z2O安全攻防”设为星标,否则可能就看不到啦!**因为公众号现在只对常读和星标的公众号才能展示大图推送。操作方法:点击右上角的【...】,然后点击【设为星标】即可。
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者及本公众号团队不为此承担任何责任。
最新的测试中,经常遇到HTTP报文加密/加签传输的情况,这导致想要查看和修改明文报文很不方便。
之前应对这种情况我们有几种常见的办法解决,比如使用burpy插件、在Burp上下游使用mitmproxy进行代理等,但这些使用起来不太方便,并且偶尔遇到python和java间加密算法上的一些小差异,需要调试很久。
因此在想是否可以做一款Burp插件来解决这些问题,而且使用上要简单高效。在看了Burp新版接口 Montoya API
的介绍后,发现可以满足我们的需求,并且还有一些意想不到的发现,比如它可以满足 动态密钥
的场景。
详细介绍前,先放一张实现后的gif:
正常情况下,我们通过Burp代理进行渗透测试时,流量的流转情况如图所示。
而通过Burp的 Montoya API
我们可以分别做到 Request/Response 在 Client/Burp/Server 流转时实现自己对其的处理逻辑,像图中这样。
Burp Proxy
:Burp中的Proxy模块,即UI中看到的Proxy那样,负责代理以及流量记录等。
Burp Http
:Burp中的Http模块,负责请求最终从Burp出去、响应刚到达Burp时的处理等。
而要实现我们的需求,很显然只需要在图中①,②,③,④进行不同的处理逻辑即可:
①:HTTP请求从客户端到达Burp时被触发。在此处完成请求解密的代码就可以在Burp中看到明文的请求报文。
②:HTTP请求从Burp将要发送到Server时被触发。在此处完成请求加密的代码就可以将加密后的请求报文发送到Server。
③:HTTP请求从Server到达Burp时被触发。在此处完成响应解密的代码就可以在Burp中看到明文的响应报文。
④:HTTP请求从Burp将要发送到Client时被触发。在此处完成响应加密的代码就可以将加密后的响应报文返回给Client。
逻辑成立,实现开始。
据过去遇到的情况,同一个系统可能出现部分流量加密的情况、甚至还有根据某个请求头的不同,不加密或者使用不同的加密方式。
想要支持这些情况,采用host白名单,url白名单的方案有其弊端,因此我引入了表达式的方式,你可以通过实现一个表达式,来过滤哪些请求需要/不需要处理,比如我们默认的表达式为:
!request.isStaticExtension() && request.host=='192.168.1.4'
表达式采用JS的语法,执行完成会返回一个 boolean
类型,用于判断是否过滤。
这条的意思是指请求不能是静态后缀,且host必须是192.168.1.4,在使用过程中用户可以根据自己需求修改,以应对各种特殊的场景。
还是根据经验来,我想要的实现需要满足以下条件:
1. 可以根据某个请求头的不同,使用不同的加密方式。
2. 可以满足在加密加签同时存在、加密时算法组合的情况。
3. 最好可以使用java中的加解密库,避免不同语言算法实现上的小差异。
调研了现在一些常见的实现思路,有的通过配置,但配置总会有不足;有的可以让用户自定义代码后在Repeater模块中右键使用,很灵活但无法做到对代理请求/响应自动解密。
综合考虑了上述方式的缺点,借鉴了其优点,提炼出自己的方案。
我将该功能命名为 HttpHook
,四个阶段分别为 hookRequestToBurp
,hookRequestToServer
, hookResponseToBurp
, hookResponseToClient
。他们分别接收请求或响应对象,由用户对其进行自定义处理。四个阶段分别有多种实现方式:
Python
:通过python代码实现四个函数,原理为将python编译并在jvm中运行。
JS
:通过js代码实现四个函数,原理为将js编译并在jvm中运行。
Java
:通过java代码实现四个函数,原理为动态加载。
Grpc
:通过实现Grpc服务实现四个rpc接口,算是兜底方案。
这四种实现方式,可以分为两类:
Grpc
:你需要用其他语言实现Grpc Server,并自行通过三方库实现对应 Hook 接口
应有的功能。
Code
:你需要用支持的方式编写对应语言的脚本,在脚本中组合、调用项目中的DataObjects和Util,实现对应 Hook 函数
应有的功能。
它们各有优缺点:
Grpc
:优点是跨语言能力强,运行兼容性强;缺点是学习成本稍高、依赖IO -> 可能存在性能问题、不同语言算法间可能存在兼容性问题,在动态密钥的情况下很难实现需求。
Code
:优点是可以与JVM交互调用Java中的加解密库 -> 对Java来说没有算法兼容性的问题;缺点是需要熟悉项目自带的DataObjects和Utils,并且可能存在运行兼容性的问题。
看了上边的实现,你发现居然还要我写代码,好麻烦。确实,不可否认,我们为了覆盖复杂的场景,采用了写代码这种适应性最强的方式。
因此我们采用尽量多而全的自带示例,来减少了上手难度。项目中涵盖了大量的示例,如AES-CBC
,AES-ECB
, AES-GCM
, RSA
, SM2
等常见算法(后续也会持续扩充),对于这些算法,可以做到开箱即用,至于更复杂的场景,你需要熟悉这些示例中的代码,灵活运用,从而解决问题。
github地址:https://github.com/outlaws-bai/Galaxy
这块看文章顶部的 gif
即可,更加直观,也比较简单,我们详细演示动态密钥情况下的使用。
首先,我们写一个简单用于测试的客户端(index.html)和服务端(server.py),加密算法使用AES-CBC
。
index.html
:在用户打开页面时,请求 /api/getSecret
,获取加密使用的 key
和 iv
,之后用户登录(/api/login
)的请求和响应都会加密。
<!DOCTYPE html>
<htmllang="en">
<head>
<metacharset="UTF-8">
<metaname="viewport"content="width=device-width, initial-scale=1.0">
<title>Login</title>
<scriptsrc="https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/crypto-js.js"></script>
</head>
<body>
<h2>Login</h2>
<formid="loginForm">
<labelfor="username">Username:</label>
<inputtype="text"id="username"name="username"><br><br>
<labelfor="password">Password:</label>
<inputtype="password"id="password"name="password"><br><br>
<buttontype="button"onclick="login()">Login</button>
</form>
<divid="userInfo"style="margin-top:20px;">
</div>
<script>
let secretKey ='';
let iv ='';
async function getSecret(){
try{
const response = await fetch('/api/getSecret');
const data = await response.json();
secretKey = data.key;
iv = data.iv;
}catch(error){
console.error('Error fetching the secret:', error);
}
}
function encrypt(data, key, iv){
const keyUtf8 =CryptoJS.enc.Utf8.parse(key);
const ivUtf8 =CryptoJS.enc.Utf8.parse(iv);
const encrypted =CryptoJS.AES.encrypt(JSON.stringify(data), keyUtf8,{ iv: ivUtf8, mode:CryptoJS.mode.CBC });
return encrypted.toString();
}
function decrypt(data, key, iv){
const keyUtf8 =CryptoJS.enc.Utf8.parse(key);
const ivUtf8 =CryptoJS.enc.Utf8.parse(iv);
const decrypted =CryptoJS.AES.decrypt(data, keyUtf8,{ iv: ivUtf8, mode:CryptoJS.mode.CBC });
return JSON.parse(CryptoJS.enc.Utf8.stringify(decrypted));
}
async function login(){
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const data ={
username: username,
password: password
};
const encryptedData = encrypt(data, secretKey, iv);
try{
const response = await fetch('/api/login',{
method:'POST',
headers:{
'Content-Type':'application/json'
},
body: JSON.stringify({ data: encryptedData })
});
const result = await response.json();
const decryptedResult = decrypt(result.data, secretKey, iv);
if(decryptedResult.success){
displayUserInfo(decryptedResult.user);
}else{
alert('Login failed: '+ decryptedResult.message);
}
}catch(error){
console.error('Error during login:', error);
}
}
function displayUserInfo(user){
const userInfoDiv = document.getElementById('userInfo');
userInfoDiv.innerHTML =`
<h3>UserInformation</h3>
<p><strong>Username:</strong> ${user.username}</p>
<p><strong>Email:</strong> ${user.email}</p>
<p><strong>FullName:</strong> ${user.fullName}</p>
`;
}
window.onload = getSecret;
</script>
</body>
</html>
server.py
:共有三个接口,/
返回index.html
给到游览器渲染页面;/api/getSecret
获取AES- CBC
加解密过程中需要的key
和iv
;/api/login
获取客户端输入数据后加密、发送请求、最后将结果解密展示到游览器。
# pip install fastapi pycryptodome
import json
import base64
importstring
import random
import uvicorn
fromCrypto.Cipherimport AES
fromCrypto.Util.Paddingimport pad, unpad
from fastapi importFastAPI
from pydantic importBaseModel
from fastapi.responses importHTMLResponse,JSONResponse
app =FastAPI()
users_db ={
"testuser":{
"username":"testuser",
"password":"testpassword",
"email":"testuser@example.com",
"fullName":"Test User",
}
}
classLoginRequest(BaseModel):
data: str
def encrypt(data: dict, key: bytes, iv: bytes)-> str:
cipher = AES.new(key, AES.MODE_CBC, iv)
padded_data = pad(json.dumps(data).encode("utf-8"), AES.block_size)
encrypted = cipher.encrypt(padded_data)
return base64.b64encode(encrypted).decode("utf-8")
def decrypt(data: str, key: bytes, iv: bytes)-> dict:
encrypted_data = base64.b64decode(data)
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)
return json.loads(decrypted_data.decode("utf-8"))
def generate_string(length:int)-> str:
return"".join(
random.choice(string.digits +string.ascii_letters)for _ in range(length)
)
@app.get("/api/getSecret")
async def get_secret():
key = generate_string(32)
iv = generate_string(16)
app.state.secret_key = key
app.state.secret_iv = iv
return{"key": app.state.secret_key,"iv": app.state.secret_iv}
@app.post("/api/login")
async def login(request:LoginRequest):
key = app.state.secret_key.encode()
iv = app.state.secret_iv.encode()
login_data = decrypt(request.data, key, iv)
username = login_data.get("username")
password = login_data.get("password")
if username in users_db and users_db[username]["password"]== password:# type: ignore
user_info = users_db[username]# type: ignore
encrypted_response = encrypt({"success":True,"user": user_info}, key, iv)
returnJSONResponse(content={"data": encrypted_response})
else:
encrypted_response = encrypt(
{"success":False,"message":"Invalid username or password"}, key, iv
)
returnJSONResponse(content={"data": encrypted_response})
@app.get("/", response_class=HTMLResponse)
async def index():
with open("index.html","r", encoding="utf-8")as file:
html_content = file.read()
returnHTMLResponse(content=html_content)
if __name__ =="__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
然后我们启动服务:python server.py
正常情况下,客户端首先从服务端加载key
和iv
在页面上输入数据登录时,请求和响应被加密了。
Burp安装Galaxy后,配置下方的java脚本并启动服务,来完成需求
import org.m2sec.core.utils.*;
import org.m2sec.core.models.*;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
publicclassTest{
privateLogger log;
privatestaticfinalString ALGORITHM ="AES/CBC/PKCS5Padding";
privatebyte[] secret;
privatebyte[] iv;
privatestaticfinalMap<String,Object> paramMap =newHashMap<>();
privatestaticfinalString jsonKey ="data";
privatefinalThreadLocal<Boolean> flag =ThreadLocal.withInitial(()->false);
publicTest(Logger log){
this.log = log;
}
publicRequest hookRequestToBurp(Request request){
flag.set(false);
if(request.getPath().endsWith("/api/getSecret")){
log.info("match update key & iv api.");
flag.set(true);
}elseif(request.getMethod().equalsIgnoreCase("POST")){
byte[] encryptedData = getData(request.getContent());
byte[] data = decrypt(encryptedData);
request.setContent(data);
}else{
log.info("request666: {}", request);
}
return request;
}
publicRequest hookRequestToServer(Request request){
if(!flag.get()){
byte[] data = request.getContent();
byte[] encryptedData = encrypt(data);
byte[] body = toData(encryptedData);
request.setContent(body);
}
return request;
}
publicResponse hookResponseToBurp(Response response){
if(!flag.get()){
byte[] encryptedData = getData(response.getContent());
byte[] data = decrypt(encryptedData);
response.setContent(data);
}
return response;
}
publicResponse hookResponseToClient(Response response){
if(flag.get()){
Map<?,?> bodyMap =JsonUtil.jsonStrToMap(newString(response.getContent()));
String secret1 =((String) bodyMap.get("key"));
String iv1 =((String) bodyMap.get("iv"));
secret = secret1.getBytes();
iv = iv1.getBytes();
paramMap.put("iv", iv);
log.info("update key & iv: {}, {}", secret1, iv1);
}else{
byte[] data = response.getContent();
byte[] encryptedData = encrypt(data);
byte[] body = toData(encryptedData);
response.setContent(body);
}
return response;
}
publicbyte[] decrypt(byte[] content){
returnCryptoUtil.aesDecrypt(ALGORITHM, content, secret, paramMap);
}
publicbyte[] encrypt(byte[] content){
returnCryptoUtil.aesEncrypt(ALGORITHM, content, secret, paramMap);
}
publicbyte[] getData(byte[] content){
returnCodeUtil.b64decode((String)JsonUtil.jsonStrToMap(newString(content)).get(jsonKey));
}
publicbyte[] toData(byte[] content){
HashMap<String,Object> jsonBody =newHashMap<>();
jsonBody.put(jsonKey,CodeUtil.b64encodeToString(content));
returnJsonUtil.toJsonStr(jsonBody).getBytes();
}
}
再次刷新页面,触发动态密钥变换,然后输入账号和密码点击登录,就可以看到请求和响应都被自动解密了,之后对明文进行测试即可。
总结下插件的特点吧。Galaxy
1. 简单高效:用户不需要启动多余的本地服务,配置成功后可以自动对报文进行加解密。
2. 上手容易:通用算法已有示例,能做到开箱即用。
3. 灵活:可以使用Python、JS、Java、Grpc多种方式实现。
4. 支持面广:如加密算法组合、自定义算法、动态密钥等均可以支持。
欢迎加入知识星球**,星球**致力于红蓝对抗,实战攻防,星球不定时更新内外网攻防渗透技巧,以及最新学习研究成果等。常态化更新最新安全动态。针对网络安全成员的普遍水平,为星友提供了教程、工具、POC&EXP以及各种学习笔记等等。****
关注公众号回复“加群”,添加Z2OBot好友,自动拉你加入**Z2O安全攻防交流群(微信群)**分享更多好东西。
关注福利:
回复“app" 获取 app渗透和app抓包教程
回复“渗透字典" 获取 针对一些字典重新划分处理,收集了几个密码管理字典生成器用来扩展更多字典的仓库。
回复“漏洞库" 获取 最新漏洞POC库(1.2W+)****
回复“资料" 获取 网络安全、渗透测试相关资料文档****
点个【 在看 】,你最好看