October 5, 2025
82 浏览量
Welcome

软件安全JSON解析器模糊测试案例

本文将会构建一个故意设置漏洞的 JSON 解析器,并使用 AFL++(American Fuzzy Lop)自动发现五种不同类型的内存损坏漏洞。

介绍

目标程序: json_parser.c

c
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <ctype.h>
5
6#define MAX_KEY_LEN 64
7#define MAX_VALUE_LEN 256
8#define MAX_ARRAY_SIZE 1024
9
10// JSON键值对结构
11typedef struct {
12    char *key;
13    char *value;
14    int type;  // 0=string, 1=number, 2=array
15} json_pair_t;
16
17// 全局数组缓冲区
18int *global_array = NULL;
19size_t global_array_size = 0;
20
21// 漏洞1: 栈缓冲区溢出
22void process_key(char *key) {
23    char local_key[MAX_KEY_LEN];
24    strcpy(local_key, key);  // 危险!无边界检查
25    printf("Processing key: %s\n", local_key);
26}
27
28// 漏洞2: 格式字符串漏洞
29void log_value(char *value) {
30    fprintf(stderr, "Value: ");
31    fprintf(stderr, value);  // 危险!直接使用用户输入作为格式字符串
32    fprintf(stderr, "\n");
33}
34
35// 漏洞3: 整数溢出
36size_t calculate_array_size(unsigned int count, unsigned int item_size) {
37    // 危险!可能整数溢出
38    size_t total = count * item_size;
39    return total;
40}
41
42// 漏洞4: 堆缓冲区溢出
43void process_array(int *data, size_t data_count) {
44    if (!global_array) {
45        fprintf(stderr, "Error: Array buffer not initialized\n");
46        return;
47    }
48
49    // 危险!没有检查data_count是否超过global_array_size
50    memcpy(global_array, data, data_count * sizeof(int));
51
52    printf("Processed %zu array elements\n", data_count);
53}
54
55// 清理函数
56void cleanup_array() {
57    if (global_array) {
58        free(global_array);
59        global_array = NULL;
60    }
61}
62
63// 漏洞5: Use-After-Free
64void print_array_info() {
65    // 危险!可能访问已释放的内存
66    if (global_array_size > 0) {
67        printf("Array size: %zu\n", global_array_size);
68        if (global_array) {
69            printf("First element: %d\n", global_array[0]);  // UAF!
70        }
71    }
72}
73
74// 简单的JSON解析器
75char* skip_whitespace(char *json) {
76    while (*json && isspace(*json)) json++;
77    return json;
78}
79
80char* parse_string(char *json, char **result) {
81    json = skip_whitespace(json);
82
83    if (*json != '"') {
84        return NULL;
85    }
86
87    json++; // skip opening "
88    char *start = json;
89
90    while (*json && *json != '"') json++;
91
92    if (*json != '"') {
93        return NULL;
94    }
95
96    size_t len = json - start;
97    *result = malloc(len + 1);
98    strncpy(*result, start, len);
99    (*result)[len] = '\0';
100
101    json++; // skip closing "
102    return json;
103}
104
105char* parse_number(char *json, int *result) {
106    json = skip_whitespace(json);
107
108    *result = atoi(json);
109
110    while (*json && (isdigit(*json) || *json == '-')) json++;
111
112    return json;
113}
114
115char* parse_array(char *json, int **array, size_t *count) {
116    json = skip_whitespace(json);
117
118    if (*json != '[') {
119        return NULL;
120    }
121
122    json++; // skip [
123
124    // 解析数组长度
125    unsigned int capacity = 10;
126    *array = malloc(capacity * sizeof(int));
127    *count = 0;
128
129    json = skip_whitespace(json);
130
131    while (*json && *json != ']') {
132        int num;
133        json = parse_number(json, &num);
134
135        if (*count >= capacity) {
136            capacity *= 2;
137            *array = realloc(*array, capacity * sizeof(int));
138        }
139
140        (*array)[(*count)++] = num;
141
142        json = skip_whitespace(json);
143        if (*json == ',') {
144            json++;
145            json = skip_whitespace(json);
146        }
147    }
148
149    if (*json != ']') {
150        free(*array);
151        return NULL;
152    }
153
154    json++; // skip ]
155    return json;
156}
157
158int parse_json_pair(char *json) {
159    char *key = NULL;
160    char *value = NULL;
161    int num_value;
162    int *array_data = NULL;
163    size_t array_count = 0;
164
165    json = skip_whitespace(json);
166
167    if (*json != '{') {
168        fprintf(stderr, "Error: Expected '{'\n");
169        return -1;
170    }
171
172    json++; // skip {
173
174    while (1) {
175        json = skip_whitespace(json);
176
177        if (*json == '}') {
178            break;
179        }
180
181        // 解析key
182        json = parse_string(json, &key);
183        if (!json) {
184            fprintf(stderr, "Error: Failed to parse key\n");
185            return -1;
186        }
187
188        // 触发栈溢出
189        process_key(key);
190
191        json = skip_whitespace(json);
192
193        if (*json != ':') {
194            fprintf(stderr, "Error: Expected ':'\n");
195            free(key);
196            return -1;
197        }
198
199        json++; // skip :
200        json = skip_whitespace(json);
201
202        // 根据类型解析value
203        if (*json == '"') {
204            // 字符串值
205            json = parse_string(json, &value);
206            if (!json) {
207                free(key);
208                return -1;
209            }
210
211            // 触发格式字符串漏洞
212            log_value(value);
213
214            free(value);
215        }
216        else if (*json == '[') {
217            // 数组值
218            json = parse_array(json, &array_data, &array_count);
219            if (!json) {
220                free(key);
221                return -1;
222            }
223
224            // 触发整数溢出和堆溢出
225            global_array_size = calculate_array_size(array_count, sizeof(int));
226
227            if (global_array_size > 0) {
228                global_array = malloc(global_array_size);
229                if (global_array) {
230                    process_array(array_data, array_count);
231                }
232            }
233
234            free(array_data);
235        }
236        else if (isdigit(*json) || *json == '-') {
237            // 数字值
238            json = parse_number(json, &num_value);
239            printf("Number: %d\n", num_value);
240        }
241
242        free(key);
243
244        json = skip_whitespace(json);
245        if (*json == ',') {
246            json++;
247        }
248    }
249
250    return 0;
251}
252
253int main(int argc, char *argv[]) {
254    if (argc != 2) {
255        printf("Usage: %s <json_file>\n", argv[0]);
256        return 1;
257    }
258
259    FILE *fp = fopen(argv[1], "r");
260    if (!fp) {
261        fprintf(stderr, "Cannot open file: %s\n", argv[1]);
262        return 1;
263    }
264
265    // 读取整个文件
266    fseek(fp, 0, SEEK_END);
267    long file_size = ftell(fp);
268    fseek(fp, 0, SEEK_SET);
269
270    char *json_content = malloc(file_size + 1);
271    fread(json_content, 1, file_size, fp);
272    json_content[file_size] = '\0';
273    fclose(fp);
274
275    printf("Parsing JSON...\n");
276
277    // 解析JSON
278    int result = parse_json_pair(json_content);
279
280    free(json_content);
281
282    if (result == 0) {
283        printf("JSON parsed successfully\n");
284
285        // 清理资源
286        cleanup_array();
287
288        // 触发UAF
289        print_array_info();
290    }
291
292    return result;
293}
294

文件格式: JSON (JavaScript Object Notation)
测试工具: AFL++ (American Fuzzy Lop)
基础种子: seed.json

json
1{
2  "name": "test",
3  "port": 8080,
4  "array": [1, 2, 3]
5}

漏洞点详解

漏洞1: 栈缓冲区溢出 (Critical)

c
1// json_parser.c:20
2void process_key(char *key) {
3    char local_key[MAX_KEY_LEN];  // 64字节缓冲区
4    strcpy(local_key, key);       // 无边界检查!
5    printf("Processing key: %s\n", local_key);
6}

触发条件: JSON中key超过64字节
测试用例:

json
1{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": "value"}

漏洞2: 格式字符串漏洞 (High)

c
1// json_parser.c:27
2void log_value(char *value) {
3    fprintf(stderr, "Value: ");
4    fprintf(stderr, value);  // 危险!
5    fprintf(stderr, "\n");
6}

触发条件: JSON中value包含格式字符串
测试用例:

json
1{"name": "%s%s%s"}
2{"name": "%p%p%p"}
3{"name": "%n"}

漏洞3: 整数溢出 (Medium)

c
1// json_parser.c:34
2size_t calculate_array_size(unsigned int count, unsigned int item_size) {
3    size_t total = count * item_size;  // 可能溢出!
4    return total;
5}

触发条件: 极大的数组元素数量
测试用例:

json
1{"array": [1,2,3,...]} // 包含0xFFFFFFFF个元素

漏洞4: 堆缓冲区溢出 (Critical)

c
1// json_parser.c:40
2void process_array(int *data, size_t data_count) {
3    if (!global_array) {
4        return;
5    }
6
7    // 没有检查data_count vs global_array_size!
8    memcpy(global_array, data, data_count * sizeof(int));
9}

触发条件: 数组数据超过分配的缓冲区
利用链: 整数溢出 → 小缓冲区 → 大数据 → 堆溢出

漏洞5: Use-After-Free (Medium)

c
1// json_parser.c:279-280
2cleanup_array();        // 释放global_array
3print_array_info();     // 访问已释放内存!
4
5void print_array_info() {
6    if (global_array_size > 0) {
7        if (global_array) {
8            printf("First element: %d\n", global_array[0]);  // UAF!
9        }
10    }
11}

触发条件: 正常解析包含数组的JSON

编译目标程序

bash
1
2# 1. AFL++插桩版本 (用于fuzzing)
3afl-clang-fast -o json_parser_fuzz json_parser.c
4
5# 2. AddressSanitizer版本 (用于检测)
6afl-clang-fast -fsanitize=address -fsanitize=undefined -g -O1 \
7    -o json_parser_asan json_parser.c
8
9# 3. 调试版本
10gcc -g -fno-stack-protector -z execstack -no-pie \
11    -o json_parser_debug json_parser.c
12
13# 4. 标准版本
14gcc -o json_parser_normal json_parser.c

测试基础种子

bash
1root@softsec2:/opt/json_fuzzing_exercise# ./json_parser_normal seed.json
2Parsing JSON...
3Processing key: name
4Value: test
5Processing key: port
6Number: 8080
7Processing key: array
8Processed 3 array elements
9JSON parsed successfully
10Array size: 12

种子设计

JSON格式简单,种子设计关键:

  • 覆盖3种数据类型 (string/number/array)
  • 边界值测试 (超长key/value,巨大数组)
  • 格式错误 (缺失引号,括号不匹配)
  • 攻击性payload (格式字符串,溢出数据)

接下来将会基于基础种子(种子1)的基础上生成一共10个变种种子

种子2: 超长key (栈溢出)

bash
1cat > seeds/02_long_key.json << 'EOF'
2{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": "value"}
3EOF

目的: 触发process_key中的strcpy溢出

种子3: 格式字符串

bash
1cat > seeds/03_format_string.json << 'EOF'
2{"name": "%s%s%s%n"}
3EOF

目的: 触发log_value格式字符串漏洞

种子4: 超长数组

bash
1# 生成包含1000个元素的数组
2echo -n '{"array": [' > seeds/04_large_array.json
3for i in {1..1000}; do
4    echo -n "$i"
5    [ $i -lt 1000 ] && echo -n ","
6done >> seeds/04_large_array.json
7echo ']}' >> seeds/04_large_array.json

目的: 触发整数溢出和堆溢出

种子5: 缺失引号

bash
1cat > seeds/05_invalid_quote.json << 'EOF'
2{"name: "test"}
3EOF

目的: 测试错误处理

种子6: 括号不匹配

bash
1cat > seeds/06_unmatched_bracket.json << 'EOF'
2{"name": "test"
3EOF

目的: 测试解析器鲁棒性

种子7: 嵌套结构

bash
1cat > seeds/07_nested.json << 'EOF'
2{"obj": {"key": "value"}}
3EOF

注意: 当前解析器不支持嵌套,会触发错误处理

种子8: 空JSON

bash
1echo '{}' > seeds/08_empty.json

目的: 边界条件测试

种子9: 负数

bash
1cat > seeds/09_negative.json << 'EOF'
2{"port": -1}
3EOF

种子10: 超长value

bash
1printf '{"msg": "%0500d"}' 1 > seeds/10_long_value.json

AFL 模糊测试

bash
1# 启动AFL++
2afl-fuzz -i seeds -o output \
3    -M fuzzer01 \
4    -- ./json_parser_fuzz @@

参数说明:

  • -i seeds: 输入种子目录
  • -o output: 输出目录
  • -M fuzzer01: 主fuzzer
  • @@: AFL++插入测试文件路径的位置

种子设计的好的话,分分钟出结果

afl-1

bash
1root@softsec2:/opt/json_fuzzing_exercise/output/fuzzer01/crashes# ls
2id:000000,sig:11,src:000002,time:3113,execs:9537,op:havoc,rep:8           id:000003,sig:11,src:000006,time:52490,execs:37465,op:havoc,rep:8
3id:000001,sig:11,src:000002,time:3815,execs:15601,op:havoc,rep:16         id:000004,sig:11,src:000006,time:103749,execs:40472,op:havoc,rep:16
4id:000002,sig:11,src:000002+000008,time:6913,execs:33715,op:splice,rep:4  README.txt

先用正常编译版本,拿一个crash试一下

bash
1root@softsec2:/opt/json_fuzzing_exercise/output/fuzzer01/crashes# ../../../json_parser_normal "id:000000,sig:11,src:000002,time:3113,execs:9537,op:havoc,rep:8" 
2Parsing JSON...
3Processing key: pa
4Value: 0x561fbb39a8b0
5Processing key: b
6Value: bb39a8b0
7Processing key: c
8Value: 
9JSON parsed successfully

可以用ASAN版本分析

bash
1root@softsec2:/opt/json_fuzzing_exercise/output/fuzzer01/crashes# ../../../json_parser_asan "id:000000,sig:11,src:000002,time:3113,execs:9537,op:havoc,rep:8"
2Parsing JSON...
3Processing key: pa
4Value: 0xffffffff
5Processing key: b
6Value: ffffffff
7Processing key: c
8Value: AddressSanitizer:DEADLYSIGNAL
9=================================================================
10==1749957==ERROR: AddressSanitizer: SEGV on unknown address 0x00009fff7fff (pc 0x0000004344a2 bp 0x7ffe31b6b460 sp 0x7ffe31b6abd8 T0)
11==1749957==The signal is caused by a READ memory access.
12    #0 0x4344a2 in __asan::QuickCheckForUnpoisonedRegion(unsigned long, unsigned long) asan_interceptors.cpp.o
13    #1 0x43fc2e in printf_common(void*, char const*, __va_list_tag*) asan_interceptors.cpp.o
14    #2 0x4414b9 in fprintf (/opt/json_fuzzing_exercise/json_parser_asan+0x4414b9)
15    #3 0x4cf1bf in log_value /opt/json_fuzzing_exercise/json_parser.c:31:5
16    #4 0x4d0f38 in parse_json_pair /opt/json_fuzzing_exercise/json_parser.c:212:13
17    #5 0x4d1ab4 in main /opt/json_fuzzing_exercise/json_parser.c:278:18
18    #6 0x7fcec770bd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
19    #7 0x7fcec770be3f in __libc_start_main csu/../csu/libc-start.c:392:3
20    #8 0x420394 in _start (/opt/json_fuzzing_exercise/json_parser_asan+0x420394)
21
22AddressSanitizer can not provide additional info.
23SUMMARY: AddressSanitizer: SEGV asan_interceptors.cpp.o in __asan::QuickCheckForUnpoisonedRegion(unsigned long, unsigned long)
24==1749957==ABORTING

可以用hexdump查看一下crash的实际内容

bash
1root@softsec2:/opt/json_fuzzing_exercise/output/fuzzer01/crashes# hexdump -C "id:000000,sig:11,src:000002,time:3113,execs:9537,op:havoc,rep:8"
200000000  7b 22 70 61 22 3a 20 22  25 70 22 2c 20 22 62 22  |{"pa": "%p", "b"|
300000010  3a 20 22 25 78 22 2c 20  22 63 22 3a 20 22 25 6e  |: "%x", "c": "%n|
400000020  22 7d 6f 3e 74 22 35 20  73 08                    |"}o>t"5 s.|
50000002a
  • %p - 指针泄露
  • %x - 十六进制值泄露
  • %n - 写入内存的格式字符串
  1. 触发位置: log_value() 函数 (json_parser.c:31)
  2. 漏洞类型: 格式字符串漏洞 (%n 试图写入内存,但地址无效导致崩溃)

导致的问题

  1. 信息泄露 - %p/%x 泄露内存地址 → 绕过ASLR
  2. 任意地址写入 - %n 可以写入内存 → 控制程序流
  3. 组合利用 - 泄露地址 + 精心构造的%n → RCE(远程代码执行)

短短几分钟 就已经出来8个crash了

afl2


12分钟后

afl3


20分钟后
afl4

参考文献

  1. AFLplusplus. (n.d.). AFL++ (American Fuzzy Lop plus plus). GitHub. https://github.com/AFLplusplus/AFLplusplus
  2. OWASP Foundation. (n.d.-a). Format string attack. https://owasp.org/www-community/attacks/Format_string_attack
  3. OWASP Foundation. (n.d.-b). Secure coding practices quick reference guide. https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/
  4. Software Engineering Institute. (n.d.). SEI CERT C coding standard. Carnegie Mellon University. https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard

喜欢这篇文章吗?

分享给你的朋友和同事吧!

Welcome
Last updated: October 5, 2025
相关文章
正在检查服务状态...
软件安全JSON解析器模糊测试案例 - ICTRUN