介绍
目标程序: 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.jsonAFL 模糊测试
bash
1# 启动AFL++
2afl-fuzz -i seeds -o output \
3 -M fuzzer01 \
4 -- ./json_parser_fuzz @@参数说明:
-i seeds: 输入种子目录-o output: 输出目录-M fuzzer01: 主fuzzer@@: AFL++插入测试文件路径的位置
种子设计的好的话,分分钟出结果

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 - 写入内存的格式字符串
- 触发位置: log_value() 函数 (json_parser.c:31)
- 漏洞类型: 格式字符串漏洞 (%n 试图写入内存,但地址无效导致崩溃)
导致的问题
- 信息泄露 - %p/%x 泄露内存地址 → 绕过ASLR
- 任意地址写入 - %n 可以写入内存 → 控制程序流
- 组合利用 - 泄露地址 + 精心构造的%n → RCE(远程代码执行)
短短几分钟 就已经出来8个crash了

12分钟后

20分钟后

参考文献
- AFLplusplus. (n.d.). AFL++ (American Fuzzy Lop plus plus). GitHub. https://github.com/AFLplusplus/AFLplusplus
- OWASP Foundation. (n.d.-a). Format string attack. https://owasp.org/www-community/attacks/Format_string_attack
- OWASP Foundation. (n.d.-b). Secure coding practices quick reference guide. https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/
- 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