论报文加密加签场景下如何高效的进行渗透测试

admin 2024年7月19日23:10:34评论16 views字数 11483阅读38分16秒阅读模式
01
前言

最新的测试中,经常遇到HTTP报文加密/加签传输的情况,这导致想要查看和修改明文报文很不方便。

之前应对这种情况我们有几种常见的办法解决,比如使用burpy插件、在Burp上下游使用mitmproxy进行代理等,但这些使用起来不太方便,并且偶尔遇到python和java间加密算法上的一些小差异,需要调试很久。

因此在想是否可以做一款Burp插件来解决这些问题,而且使用上要简单高效。在看了Burp新版接口 Montoya API 的介绍后,发现可以满足我们的需求,并且还有一些意想不到的发现,比如它可以满足 动态密钥 的场景。

详细介绍前,先放一张实现后的gif:

论报文加密加签场景下如何高效的进行渗透测试

02
原理

正常情况下,我们通过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。

03
设计思路

逻辑成立,实现开始。

请求过滤

据过去遇到的情况,同一个系统可能出现部分流量加密的情况、甚至还有根据某个请求头的不同,不加密或者使用不同的加密方式。

想要支持这些情况,采用host白名单,url白名单的方案有其弊端,因此我引入了表达式的方式,你可以通过实现一个表达式,来过滤哪些请求需要/不需要处理,比如我们默认的表达式为:

1!request.isStaticExtension() && request.host=='192.168.1.4'

表达式采用JS的语法,执行完成会返回一个 boolean 类型,用于判断是否过滤。

这条的意思是指请求不能是静态后缀,且host必须是192.168.1.4,在使用过程中用户可以根据自己需求修改,以应对各种特殊的场景。

核心实现

还是根据经验来,我想要的实现需要满足以下条件:

可以根据某个请求头的不同,使用不同的加密方式。

可以满足在加密加签同时存在、加密时算法组合的情况。

最好可以使用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)的请求和响应都会加密。

 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6    <title>Login</title>
 7    <script src="https://cdn.jsdelivr.net/npm/[email protected]/crypto-js.js"></script>
 8</head>
 9<body>
10    <h2>Login</h2>
11    <form id="loginForm">
12        <label for="username">Username:</label>
13        <input type="text" id="username" name="username"><br><br>
14        <label for="password">Password:</label>
15        <input type="password" id="password" name="password"><br><br>
16        <button type="button" onclick="login()">Login</button>
17    </form>
18    <div id="userInfo" style="margin-top: 20px;">
19    </div>
20    <script>
21        let secretKey = '';
22        let iv = '';
23        async function getSecret() {
24            try {
25            const response = await fetch('/api/getSecret');
26            const data = await response.json();
27            secretKey = data.key;
28            iv = data.iv;
29        } catch (error) {
30            console.error('Error fetching the secret:', error);
31        }
32    }
33    function encrypt(data, key, iv{
34        const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
35        const ivUtf8 = CryptoJS.enc.Utf8.parse(iv);
36        const encrypted = CryptoJS.AES.encrypt(JSON.stringify(data), keyUtf8, { iv: ivUtf8, mode: CryptoJS.mode.CBC });
37        return encrypted.toString();
38    }
39    function decrypt(data, key, iv{
40        const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
41        const ivUtf8 = CryptoJS.enc.Utf8.parse(iv);
42        const decrypted = CryptoJS.AES.decrypt(data, keyUtf8, { iv: ivUtf8, mode: CryptoJS.mode.CBC });
43        return JSON.parse(CryptoJS.enc.Utf8.stringify(decrypted));
44    }
45    async function login() {
46        const username = document.getElementById('username').value;
47        const password = document.getElementById('password').value;
48        const data = {
49            username: username,
50            password: password
51        };
52        const encryptedData = encrypt(data, secretKey, iv);
53        try {
54            const response = await fetch('/api/login', {
55                method'POST',
56                headers: {
57                    'Content-Type''application/json'
58                },
59                bodyJSON.stringify({ data: encryptedData })
60            });
61            const result = await response.json();
62
63            const decryptedResult = decrypt(result.data, secretKey, iv);
64
65            if (decryptedResult.success) {
66                displayUserInfo(decryptedResult.user);
67            } else {
68                alert('Login failed: ' + decryptedResult.message);
69            }
70        } catch (error) {
71            console.error('Error during login:', error);
72        }
73    }
74    function displayUserInfo(user{
75        const userInfoDiv = document.getElementById('userInfo');
76        userInfoDiv.innerHTML = `
77            <h3>User Information</h3>
78            <p><strong>Username:</strong> ${user.username}</p>
79            <p><strong>Email:</strong> ${user.email}</p>
80            <p><strong>Full Name:</strong> ${user.fullName}</p>
81        `;
82    }
83    window.onload = getSecret;
84</script>
85</body>
86</html>

server.py:共有三个接口,/ 返回index.html 给到游览器渲染页面;/api/getSecret 获取AES-CBC加解密过程中需要的key和iv;/api/login 获取客户端输入数据后加密、发送请求、最后将结果解密展示到游览器。

 1# pip install fastapi pycryptodome
 2import json
 3import base64
 4import string
 5import random
 6import uvicorn
 7from Crypto.Cipher import AES
 8from Crypto.Util.Padding import pad, unpad
 9from fastapi import FastAPI
10from pydantic import BaseModel
11from fastapi.responses import HTMLResponse, JSONResponse
12
13app = FastAPI()
14
15users_db = {
16"testuser": {
17    "username""testuser",
18    "password""testpassword",
19    "email""[email protected]",
20    "fullName""Test User",
21    }
22}
23
24
25class LoginRequest(BaseModel):
26    data: str
27
28
29def encrypt(data: dict, key: bytes, iv: bytes) -> str:
30cipher = AES.new(key, AES.MODE_CBC, iv)
31padded_data = pad(json.dumps(data).encode("utf-8"), AES.block_size)
32encrypted = cipher.encrypt(padded_data)
33return base64.b64encode(encrypted).decode("utf-8")
34
35
36def decrypt(data: str, key: bytes, iv: bytes) -> dict:
37encrypted_data = base64.b64decode(data)
38cipher = AES.new(key, AES.MODE_CBC, iv)
39decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)
40return json.loads(decrypted_data.decode("utf-8"))
41
42
43def generate_string(length: int) -> str:
44return "".join(
45    random.choice(string.digits + string.ascii_letters) for _ in range(length)
46)
47
48
49@app.get("/api/getSecret")
50async def get_secret():
51key = generate_string(32)
52iv = generate_string(16)
53app.state.secret_key = key
54app.state.secret_iv = iv
55return {"key": app.state.secret_key, "iv": app.state.secret_iv}
56
57
58@app.post("/api/login")
59async def login(request: LoginRequest):
60key = app.state.secret_key.encode()
61iv = app.state.secret_iv.encode()
62
63login_data = decrypt(request.data, key, iv)
64
65username = login_data.get("username")
66password = login_data.get("password")
67
68if username in users_db and users_db[username]["password"] == password:  # type: ignore
69    user_info = users_db[username]  # type: ignore
70    encrypted_response = encrypt({"success"True"user": user_info}, key, iv)
71    return JSONResponse(content={"data": encrypted_response})
72else:
73    encrypted_response = encrypt(
74        {"success"False"message""Invalid username or password"}, key, iv
75    )
76    return JSONResponse(content={"data": encrypted_response})
77
78
79@app.get("/", response_class=HTMLResponse)
80async def index():
81with open("index.html""r", encoding="utf-8"as file:
82    html_content = file.read()
83return HTMLResponse(content=html_content)
84
85
86if __name__ == "__main__":
87uvicorn.run(app, host="0.0.0.0", port=8000)

然后我们启动服务:python server.py

正常情况下,客户端首先从服务端加载key和iv

论报文加密加签场景下如何高效的进行渗透测试

在页面上输入数据登录时,请求和响应被加密了。

论报文加密加签场景下如何高效的进行渗透测试

尝试解密

Burp安装Galaxy后,配置下方的java脚本并启动服务,来完成需求

 1import org.m2sec.core.utils.*;
 2import org.m2sec.core.models.*;
 3
 4import java.util.HashMap;
 5import java.util.Map;
 6
 7import org.slf4j.Logger;
 8
 9public class Test {
10
11private Logger log;
12private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
13private byte[] secret;
14private byte[] iv;
15private static final Map<String, Object> paramMap = new HashMap<>();
16private static final String jsonKey = "data";
17
18private final ThreadLocal<Boolean> flag = ThreadLocal.withInitial(() -> false);
19
20public Test(Logger log) {
21    this.log = log;
22}
23
24public Request hookRequestToBurp(Request request) {
25    flag.set(false);
26    if (request.getPath().endsWith("/api/getSecret")) {
27        log.info("match update key & iv api.");
28        flag.set(true);
29    } else if (request.getMethod().equalsIgnoreCase("POST")) {
30        byte[] encryptedData = getData(request.getContent());
31        byte[] data = decrypt(encryptedData);
32        request.setContent(data);
33    } else {
34        log.info("request666: {}"request);
35    }
36    return request;
37}
38
39public Request hookRequestToServer(Request request) {
40    if (!flag.get()) {
41        byte[] data = request.getContent();
42        byte[] encryptedData = encrypt(data);
43        byte[] body = toData(encryptedData);
44        request.setContent(body);
45    }
46    return request;
47}
48
49public Response hookResponseToBurp(Response response) {
50    if (!flag.get()) {
51        byte[] encryptedData = getData(response.getContent());
52        byte[] data = decrypt(encryptedData);
53        response.setContent(data);
54    }
55    return response;
56}
57
58public Response hookResponseToClient(Response response) {
59    if (flag.get()) {
60        Map<?, ?> bodyMap = JsonUtil.jsonStrToMap(new String(response.getContent()));
61        String secret1 = ((String) bodyMap.get("key"));
62        String iv1 = ((String) bodyMap.get("iv"));
63        secret = secret1.getBytes();
64        iv = iv1.getBytes();
65        paramMap.put("iv", iv);
66        log.info("update key & iv: {}, {}", secret1, iv1);
67    } else {
68        byte[] data = response.getContent();
69        byte[] encryptedData = encrypt(data);
70        byte[] body = toData(encryptedData);
71        response.setContent(body);
72    }
73    return response;
74}
75
76public byte[] decrypt(byte[] content) {
77    return CryptoUtil.aesDecrypt(ALGORITHM, content, secret, paramMap);
78}
79
80public byte[] encrypt(byte[] content) {
81    return CryptoUtil.aesEncrypt(ALGORITHM, content, secret, paramMap);
82}
83
84public byte[] getData(byte[] content) {
85    return CodeUtil.b64decode((String) JsonUtil.jsonStrToMap(new String(content)).get(jsonKey));
86}
87
88public byte[] toData(byte[] content) {
89    HashMap<String, Object> jsonBody = new HashMap<>();
90    jsonBody.put(jsonKey, CodeUtil.b64encodeToString(content));
91    return JsonUtil.toJsonStr(jsonBody).getBytes();
92}
93}

再次刷新页面,触发动态密钥变换,然后输入账号和密码点击登录,就可以看到请求和响应都被自动解密了,之后对明文进行测试即可。

论报文加密加签场景下如何高效的进行渗透测试

总结

总结下插件的特点吧。Galaxy

简单高效:用户不需要启动多余的本地服务,配置成功后可以自动对报文进行加解密。

上手容易:通用算法已有示例,能做到开箱即用。

灵活:可以使用Python、JS、Java、Grpc多种方式实现。

支持面广:如加密算法组合、自定义算法、动态密钥等均可以支持。

END
论报文加密加签场景下如何高效的进行渗透测试

文:越关山丶

原文链接:https://xz.aliyun.com/t/15051?time__1311=GqjxuiqCwxgDlxGgx%2Brxmh8DcCXr3%2B%3Dw3x

原文始发于微信公众号(开源聚合网络空间安全研究院):【重点剖析】论报文加密加签场景下如何高效的进行渗透测试

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月19日23:10:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   论报文加密加签场景下如何高效的进行渗透测试https://cn-sec.com/archives/2975296.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息