绝杀 GET/POST 嵌套的 JSON 参数

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,常用于Web应用程序中的数据传输。在HTTP数据包信息传递时,JSON扮演着非常正常的角色,因为它是一种通用的数据格式,可以被多种编程语言和应用程序所支持。

当客户端向服务器发送HTTP请求时,请求头中可以指定请求体的数据格式为JSON。服务器在接收到请求后,可以解析JSON数据并进行相应的处理。同样地,当服务器向客户端返回HTTP响应时,响应头中可以指定响应体的数据格式为JSON。客户端可以解析JSON数据并进行相应的处理。

背景

我们平时遇到的 JSON 接口非常多,但是 JSON 中的各种字段测试以及不同深度的 JSON 测试其实非常难自动化测试。在安全服务测试和漏洞挖掘中,遇到这种情况,大多数可能只能手动测试,或者只测试少数几层。当然这个问题其实由来已久,在一些场景下这种情况大多数通过堆人力来解决,我们如何解决这个问题?或者我们有没有一些更优秀的解决方案?

我们以一个很经典的实际案例来说明这种情况,如果一个数据包长这个样子:

GET / HTTP/1.1
Host: www.example.com
Content-Type: application/json; charset=UTF-8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

{
    "abc": 123,
    "foo": "bar",
    "obj": {
        "deep1": 1,
        "deep2": "deepStr",
        "depth3": {
            "ccc": 1,
            "ddd": 1
        }
    }
}

那么我们如何对这个数据包进行测试呢?比如说我们的 payload 是 {{payload}} 那么,我们希望获得如下结果

{
    "abc": {{payload}},
    "foo": "bar",
    "obj": {
        "deep1": 1,
        "deep2": "deepStr",
        "depth3": {
            "ccc": 1,
            "ddd": 1
        }
    }
}

甚至

{
    "abc": 123,
    "foo": "bar",
    "obj": {
        "deep1": {{payload}},
        "deep2": "deepStr",
        "depth3": {
            "ccc": 1,
            "ddd": 1
        }
    }
}

基础方法

先说结论吧,我们在 Yaklang 的 fuzz 模块中实现了这样的变换,可以递归深度遍历 JSON 的 Key/Value,并且同时实现替换的功能。很简单地,我们可以编写一段 Yaklang 代码很简单地实现这个功能:

熟悉 Yaklang Fuzz 模块的同学对上述的结果其实并不陌生,实际上这个问题已经得到了很好的解决。但是往往渗透测试中遇到的 JSON 可并不是这么简单。

难度升级:如果 JSON 内容是在 GET/POST 参数中呢?

如果本身参数是 a=123&&b=123&&key=value1&&obj={"abc": 123, "keyInQuery": "ccc"} 这种情况呢?我们再来看另一个数据包

GET /file.php?a=123&&b=123&&key=value1&&obj=%7B%22abc%22%3A+123%2C+%22keyInQuery%22%3A+%22ccc%22%7D HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/83.0.4103.116

那么这种情况,其实就非常令人恐惧了。我们这个数据包中,包含 4 个 GET 参数,虽然表面上包含四个参数,但是参数中有一个 obj 其实非常复杂,他是被编码的,并且还包含了 abckeyInQuery 这两个隐藏参数。

也就是说,上面这个数据包,实际包含了 6 个参数:

  1. GET 参数:a

  2. GET 参数:b

  3. GET 参数:key

  4. GET 参数:obj

  5. GET 参数嵌套 JSON 参数:obj.abc

  6. GET 参数嵌套 JSON 参数:obj.keyInQuery

一般来说,我们可能只能测试到 a, b, key, obj 这四个参数,并不能测试到 obj.abcobj.keyInQuery 参数。如果遇到这种情况,原有的方法可能就无法生效了,那么我们如何解决呢?

Show Me the CODE!

freq = fuzz.HTTPRequest(`GET /file.php?a=123&&b=123&&key=value1&&obj=%7B%22abc%22%3A+123%2C+%22keyInQuery%22%3A+%22ccc%22%7D HTTP/1.1
Host: www.example.com
Content-Type: application/json; charset=UTF-8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
`)~

for param in freq.GetGetQueryParams() {
    req = param.Fuzz("___________").GetFirstFuzzHTTPRequest()~
    println(codec.DecodeUrl(req.GetRequestURI())~)
}

/*
/file.php?a=123&b=123&key=value1&obj={"abc":"___________","keyInQuery":"ccc"}
/file.php?a=123&b=123&key=value1&obj={"abc":123,"keyInQuery":"___________"}
/file.php?a=123&b=123&key=value1&obj=___________
/file.php?a=___________&b=123&key=value1&obj={"abc": 123, "keyInQuery": "ccc"}
/file.php?a=123&b=___________&key=value1&obj={"abc": 123, "keyInQuery": "ccc"}
/file.php?a=123&b=123&key=___________&obj={"abc": 123, "keyInQuery": "ccc"}
*/

我们按上面描述的内容,如果可以进行 payload 替换的话,我们应该有 6 个参数需要替换,通过 fuzz 模块中 HTTPRequest 构造一个模糊测试模版,然后通过内置的获取 GetQueryParams 方法。使用获取到的参数调用 Fuzz 方法,在每次 Fuzzing 后,使用 GetFirstFuzzHTTPRequest 方法获取第一个 Fuzzing 后的 HTTP 请求,并使用 DecodeUrl 方法解码请求 URI。最终,使用 println 方法将解码后的请求 URI 打印出来。

最后我们获取到的结果非常明显:

 这两个参数已经可以成功被我们手动覆盖了,因此我们可以尝试对这类的所有数据包进行很精密的测试了。

仿真测试

 我们首先手造了一个 /expr/injection 的路由,其中有三个参数,我们把 b 参数中的内容作为 JSON 进行反序列化,并且把 JSON 后对象的 "a" 参数取出来。然后把 “a” 的内容作为一个沙箱表达式进行执行。

我们对刚刚编写的靶场进行简单的测试:

发现只要有 b-json 参数的内容中的 a 为数值表达式的时候,他的结果为运算结果。这种漏洞如何进行自动发现呢? 

全自动化测试

我们编写一个表达式注入的通用测试脚本:

freq = fuzz.HTTPRequest(`
GET /expr/injection?b={"a":1} HTTP/1.1
Host: 127.0.0.1:8787

`)~

for param in freq.GetGetQueryParams() {
    try {
        exprParams = fuzz.FuzzCalcExpr()
        result := param.Fuzz(exprParams.expr).ExecFirst()~    
        if exprParams.result in string(result.ResponseRaw) {
            println("----------------------------------")
            println("----------------------------------")
            println("--------------表达式执行------------")
            println("----------------------------------")
            println("----------------------------------")
        }
    } catch err {
        dump(err)
    }
}

我们在这个脚本中,需要测试表达式,通过 fuzz.FuzzCalcExpr 生成一个减法(加性)表达式,为了让表达式更加简单,我们认为他是一个“日期表达式”,类似 2012-12-21 这样的减法,这样他的计算结果为 1979。如果表达式执行了,页面应该会有 1979 的字样。

我们执行上述内容:

[INFO] 2023-05-15 14:25:56 [http_pool:612] start to send to http://127.0.0.1:8787/expr/injection?b=%7B%22a%22%3A%222011-03-9%22%7D(:0) (packet mode)
----------------------------------
----------------------------------
--------------表达式执行------------
----------------------------------
----------------------------------
HTTP/1.1 200 OK
Date: Mon, 15 May 2023 06:25:56 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 282

-----------------ORIGIN PACKET---------------
GET /expr/injection?b=%7B%22a%22%3A%222011-03-9%22%7D HTTP/1.1
Host: 127.0.0.1:8787

-----------------Handled---------------
a[]: last Stack Value is nil/undefined

b[{"a":"2011-03-9"}]: 1999

c[]: last Stack Value is nil/undefined


[INFO] 2023-05-15 14:25:56 [http_pool:612] start to send to http://127.0.0.1:8787/expr/injection?b=2018-05-9(:0) (packet mode)

确实,我们发送的数据包参数部分为 b=%7B%22a%22%3A%222011-03-9%22%7D解码后为 b={"a":"2011-03-9"},并且数据包中也有 “1999” 作为测试结果,这很符合我们的测试要求。

我们的代码最精彩的部分在于,没有写明测试的路径,仅仅是写明了测试的原始数据包,当然这个原始数据包的来源可以是任何地方,比如 Yakit MITM 模块中。

当然选取了一个 JSON 中的表达式注入作为测试案例,这个案例其实是非常具有代表性的,他很难被正常的扫描器,甚至启发式扫描算法检测到,并且甚至作为手动测试的时候,如果测试者忽略了这个小点也会漏掉;甚至很多通用框架型的漏洞也具有这个特征。

核心原理

这个算法看起来非常 amazing!但是他的核心原理其实并不复杂:代码部分开源在 https://github.com/yaklang/yaklang 仓库中的如下位置:

  1. common/mutate/..

  2. common/jsonpath/..

我们先用 JSON 对象的递归方法构建出它每个字段的 JSONPATH,然后我们使用构造出的一组 JSONPATH 对数据进行 Replace。因为递归构建参数的原因,并且 JSON 本身的数据是不存在环结构的,因此我们只要递归结束,就可以构建出所有的字段的 JSONPath 标记位置。

当每一个位置都可以被精确定位,只需要每个位置依次替换,就可以对任何位置的 JSON 进行替换了。

JSONPath 是一种用于在 JSON 数据中进行数据查询的语言。它类似于 XPath,但是针对 JSON 格式的数据。使用 JSONPath 可以方便地从 JSON 数据中提取出所需要的数据,非常适合用于 API 开发和数据分析等场景。例如,可以使用 JSONPath 从 JSON 数据中提取出特定字段的值,或者根据条件过滤出符合要求的数据。JSONPath 语法简洁易懂,可以通过点号和方括号来访问 JSON 对象和数组中的元素。

fuzz.HTTPRequest 我们设计了一套链式 API 以达到模糊测试的目的,这种模糊测试可以自动提取所有的参数,我们寻找到 GET/POST 中参数的时候,可以检查它参数中的值是否是 JSON,如果是 JSON 的话,可以采用上面提到的 JSONPath 标记法生成对应的可模糊测试的模版对象。这样就接入了我们已有的基础设施中。

我们把上面提到的所有技术实现,开放在 YakVM 中,就可以在 Yaklang 中直接使用到这一套组合算法,并且它可以完美融合进 MITM 的测试过程和任何爬虫过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您可以使用以下java代码实现该post请求: ```java import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.HashMap; import java.util.Map; public class PostRequestExample { public static void main(String[] args) { String url = "your_url_here"; String method = "POST"; Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", "application/json"); String postData = "{\"applycode\":\"KC01\",\"data\":{\"kfmc\":\"泰州时泰老百姓大药房\",\"zxbz\":\"0\"},\"dtapplet\":\"1\",\"ssqh\":\"320981\",\"yybh\":\"32129166620221110036\"}"; Map<String, String> formData = new HashMap<>(); formData.put("miniprogram", postData); try { String response = sendPostRequest(url, method, headers, formData); System.out.println(response); } catch (Exception e) { e.printStackTrace(); } } public static String sendPostRequest(String url, String method, Map<String, String> headers, Map<String, String> formData) throws Exception { URL requestUrl = new URL(url); HttpURLConnection connection = (HttpURLConnection) requestUrl.openConnection(); connection.setRequestMethod(method); // 设置请求头 if (headers != null) { for (String key : headers.keySet()) { connection.setRequestProperty(key, headers.get(key)); } } // 设置请求体 if (formData != null) { String boundary = "----" + System.currentTimeMillis(); connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); StringBuilder sb = new StringBuilder(); for (String key : formData.keySet()) { sb.append("--").append(boundary).append("\r\n"); sb.append("Content-Disposition: form-data; name=\"").append(key).append("\"\r\n"); sb.append("\r\n"); sb.append(formData.get(key)).append("\r\n"); } sb.append("--").append(boundary).append("--").append("\r\n"); connection.getOutputStream().write(sb.toString().getBytes()); } // 发送请求并获取响应 BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); String line; StringBuilder response = new StringBuilder(); while ((line = in.readLine()) != null) { response.append(line); } in.close(); connection.disconnect(); return response.toString(); } } ``` 其中,您需要将 `your_url_here` 替换为实际的请求URL,然后将请求参数按照json格式存入postData变量中,最后将 `miniprogram` 和 `postData` 存入 formData 变量中即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值