Skip to content

Commit 091f57d

Browse files
committed
fix: XML body truncation in scan requests
- Remove Content-Length header to let sqlmap auto-calculate - Use binary mode ('wb') for writing request files on Windows - Normalize body line endings to avoid \\r\\n duplication - Replace manual JSON string building with Gson/PayloadBuilder in Burp plugins
1 parent fedf9ba commit 091f57d

4 files changed

Lines changed: 75 additions & 104 deletions

File tree

src/backEnd/model/Task.py

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -277,13 +277,17 @@ def _generate_request_file_name(self):
277277
def _build_raw_http_request(self):
278278
"""
279279
根据headers和body构建HTTP原始报文
280-
格式:
281-
POST /path HTTP/1.1
282-
Host: example.com
283-
Header1: Value1
284-
...
285-
280+
格式(使用标准HTTP换行符 \\r\\n):
281+
POST /path HTTP/1.1\\r\\n
282+
Host: example.com\\r\\n
283+
Header1: Value1\\r\\n
284+
\\r\\n
286285
body_content
286+
287+
注意:
288+
- 移除 Content-Length 头,让 sqlmap 根据实际 body 长度自动计算
289+
- body 中的换行符统一规范化为 \\n(避免 \\r\\n 被重复转换)
290+
- 使用 \\r\\n 作为 HTTP 标准行分隔符
287291
"""
288292
# 解析URL获取请求路径
289293
parsed_url = urlparse(self.scanUrl)
@@ -297,11 +301,15 @@ def _build_raw_http_request(self):
297301
# 构建请求行
298302
request_line = f"{method} {path} HTTP/1.1"
299303

300-
# 构建headers部分
304+
# 构建headers部分(移除 Content-Length,让 sqlmap 自动计算)
301305
headers_list = []
302306
if self.headers:
303307
for header in self.headers:
304308
if header and ":" in header:
309+
# 跳过 Content-Length,由 sqlmap 根据实际 body 重新计算
310+
if header.lower().startswith("content-length:"):
311+
logger.debug(f"[{self.taskid}] Removing Content-Length header, sqlmap will recalculate")
312+
continue
305313
# 如果启用了randomAgent,跳过原始User-Agent头
306314
# 让SQLMap的_setHTTPUserAgent()添加随机UA
307315
if self.options.randomAgent and header.lower().startswith("user-agent:"):
@@ -319,15 +327,21 @@ def _build_raw_http_request(self):
319327
if host:
320328
headers_list.insert(0, f"Host: {host}")
321329

322-
# 组装完整报文
323-
raw_request = request_line + "\n"
324-
raw_request += "\n".join(headers_list)
330+
# 规范化 body 中的换行符:统一为 \n,避免混合换行符导致字节数不一致
331+
body = self.body
332+
if body:
333+
body = body.replace("\r\n", "\n").replace("\r", "\n")
334+
335+
# 使用 \r\n 作为 HTTP 标准行分隔符组装报文
336+
CRLF = "\r\n"
337+
raw_request = request_line + CRLF
338+
raw_request += CRLF.join(headers_list)
325339

326340
# 如果有body,添加空行和body
327-
if self.body:
328-
raw_request += "\n\n" + self.body
341+
if body:
342+
raw_request += CRLF + CRLF + body
329343
else:
330-
raw_request += "\n\n"
344+
raw_request += CRLF + CRLF
331345

332346
return raw_request
333347

@@ -351,9 +365,10 @@ def _create_request_file(self):
351365
# 构建原始HTTP报文
352366
raw_request = self._build_raw_http_request()
353367

354-
# 写入文件
355-
with open(file_path, 'w', encoding='utf-8') as f:
356-
f.write(raw_request)
368+
# 使用二进制模式写入,避免 Windows 文本模式自动将 \n 转换为 \r\n
369+
# 导致 body 字节数与 Content-Length 不匹配(XML 等多行 body 的截断根因)
370+
with open(file_path, 'wb') as f:
371+
f.write(raw_request.encode('utf-8'))
357372

358373
logger.info(f"[{self.taskid}] Created HTTP request file: {file_path}")
359374
logger.debug(f"[{self.taskid}] HTTP request content:\n{raw_request}")

src/burpEx/legacy-api/src/main/java/com/sqlmapwebui/burp/BurpExtender.java

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import com.sqlmapwebui.burp.util.TitleRule;
99
import com.sqlmapwebui.burp.util.TitleExtractor;
1010

11+
import com.google.gson.Gson;
12+
import com.google.gson.JsonObject;
13+
1114
import javax.swing.*;
1215
import java.awt.*;
1316
import java.nio.charset.StandardCharsets;
@@ -378,29 +381,7 @@ private void sendRequestToBackend(IHttpRequestResponse requestResponse, ScanConf
378381
body = new String(request, bodyOffset, request.length - bodyOffset, StandardCharsets.UTF_8);
379382
}
380383

381-
StringBuilder headersJson = new StringBuilder("[");
382-
for (int i = 0; i < headers.size(); i++) {
383-
headersJson.append("\"").append(JsonUtils.escapeJson(headers.get(i))).append("\"");
384-
if (i < headers.size() - 1) headersJson.append(",");
385-
}
386-
headersJson.append("]");
387-
388384
Map<String, Object> options = config.toOptionsMap();
389-
StringBuilder optionsJson = new StringBuilder("{");
390-
boolean first = true;
391-
for (Map.Entry<String, Object> entry : options.entrySet()) {
392-
if (!first) optionsJson.append(",");
393-
first = false;
394-
optionsJson.append("\"").append(entry.getKey()).append("\":");
395-
if (entry.getValue() instanceof String) {
396-
optionsJson.append("\"").append(JsonUtils.escapeJson((String)entry.getValue())).append("\"");
397-
} else if (entry.getValue() instanceof Boolean) {
398-
optionsJson.append(entry.getValue());
399-
} else {
400-
optionsJson.append(entry.getValue());
401-
}
402-
}
403-
optionsJson.append("}");
404385

405386
// 提取HTTP方法(从headers的第一行)
406387
String method = "GET";
@@ -411,15 +392,17 @@ private void sendRequestToBackend(IHttpRequestResponse requestResponse, ScanConf
411392
}
412393
}
413394

414-
String jsonPayload = String.format(
415-
"{\"scanUrl\":\"%s\",\"host\":\"%s\",\"method\":\"%s\",\"headers\":%s,\"body\":\"%s\",\"options\":%s}",
416-
JsonUtils.escapeJson(url),
417-
JsonUtils.escapeJson(requestInfo.getUrl().getHost()),
418-
JsonUtils.escapeJson(method),
419-
headersJson.toString(),
420-
JsonUtils.escapeJson(body),
421-
optionsJson.toString()
422-
);
395+
// 使用 Gson 构建 JSON,避免手动拼接导致 XML 等特殊字符转义不完备
396+
Gson gson = new Gson();
397+
JsonObject payload = new JsonObject();
398+
payload.addProperty("scanUrl", url);
399+
payload.addProperty("host", requestInfo.getUrl().getHost());
400+
payload.addProperty("method", method);
401+
payload.add("headers", gson.toJsonTree(headers));
402+
payload.addProperty("body", body);
403+
payload.add("options", gson.toJsonTree(options));
404+
405+
String jsonPayload = gson.toJson(payload);
423406

424407
new Thread(() -> {
425408
try {

src/burpEx/legacy-api/src/main/java/com/sqlmapwebui/burp/dialogs/InjectionPointDialog.java

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import com.sqlmapwebui.burp.SqlmapApiClient;
77
import com.sqlmapwebui.burp.SqlmapUITab;
88

9+
import com.google.gson.Gson;
10+
import com.google.gson.JsonObject;
11+
912
import javax.swing.*;
1013
import java.awt.*;
1114
import java.io.PrintWriter;
@@ -194,38 +197,26 @@ private void sendMarkedRequestToBackend(IHttpRequestResponse requestResponse, St
194197
ScanConfig config = configManager.getDefaultConfig().copy();
195198
Map<String, Object> options = config.toOptionsMap();
196199

197-
// 构建JSON
198-
StringBuilder headersJson = new StringBuilder("[");
199-
for (int i = 0; i < headersList.size(); i++) {
200-
headersJson.append("\"").append(JsonUtils.escapeJson(headersList.get(i))).append("\"");
201-
if (i < headersList.size() - 1) headersJson.append(",");
202-
}
203-
headersJson.append("]");
204-
205-
StringBuilder optionsJson = new StringBuilder("{");
206-
boolean first = true;
207-
for (Map.Entry<String, Object> entry : options.entrySet()) {
208-
if (!first) optionsJson.append(",");
209-
first = false;
210-
optionsJson.append("\"").append(entry.getKey()).append("\":");
211-
if (entry.getValue() instanceof String) {
212-
optionsJson.append("\"").append(JsonUtils.escapeJson((String)entry.getValue())).append("\"");
213-
} else if (entry.getValue() instanceof Boolean) {
214-
optionsJson.append(entry.getValue());
215-
} else {
216-
optionsJson.append(entry.getValue());
200+
// 提取HTTP方法(从headers的第一行)
201+
String method = "GET";
202+
if (!headersList.isEmpty()) {
203+
String firstHeader = headersList.get(0);
204+
if (firstHeader != null && firstHeader.contains(" ")) {
205+
method = firstHeader.substring(0, firstHeader.indexOf(" "));
217206
}
218207
}
219-
optionsJson.append("}");
220208

221-
String jsonPayload = String.format(
222-
"{\"scanUrl\":\"%s\",\"host\":\"%s\",\"headers\":%s,\"body\":\"%s\",\"options\":%s}",
223-
JsonUtils.escapeJson(url),
224-
JsonUtils.escapeJson(host),
225-
headersJson.toString(),
226-
JsonUtils.escapeJson(body),
227-
optionsJson.toString()
228-
);
209+
// 使用 Gson 构建 JSON,避免手动拼接导致特殊字符转义不完备
210+
Gson gson = new Gson();
211+
JsonObject payload = new JsonObject();
212+
payload.addProperty("scanUrl", url);
213+
payload.addProperty("host", host);
214+
payload.addProperty("method", method);
215+
payload.add("headers", gson.toJsonTree(headersList));
216+
payload.addProperty("body", body);
217+
payload.add("options", gson.toJsonTree(options));
218+
219+
String jsonPayload = gson.toJson(payload);
229220

230221
long markCount = markedRequestText.chars().filter(ch -> ch == '*').count();
231222

src/burpEx/montoya-api/src/main/java/com/sqlmapwebui/burp/dialogs/InjectionPointDialog.java

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.sqlmapwebui.burp.ScanConfig;
99
import com.sqlmapwebui.burp.SqlmapApiClient;
1010
import com.sqlmapwebui.burp.SqlmapUITab;
11+
import com.sqlmapwebui.burp.util.PayloadBuilder;
1112

1213
import javax.swing.*;
1314
import java.awt.*;
@@ -194,37 +195,18 @@ private void sendMarkedRequestToBackend(HttpRequest originalRequest, String mark
194195
ScanConfig config = configManager.getDefaultConfig().copy();
195196
Map<String, Object> options = config.toOptionsMap();
196197

197-
// 构建JSON
198-
StringBuilder headersJson = new StringBuilder("[");
199-
for (int i = 0; i < headersList.size(); i++) {
200-
headersJson.append("\"").append(JsonUtils.escapeJson(headersList.get(i))).append("\"");
201-
if (i < headersList.size() - 1) headersJson.append(",");
202-
}
203-
headersJson.append("]");
204-
205-
StringBuilder optionsJson = new StringBuilder("{");
206-
boolean first = true;
207-
for (Map.Entry<String, Object> entry : options.entrySet()) {
208-
if (!first) optionsJson.append(",");
209-
first = false;
210-
optionsJson.append("\"").append(entry.getKey()).append("\":");
211-
if (entry.getValue() instanceof String) {
212-
optionsJson.append("\"").append(JsonUtils.escapeJson((String)entry.getValue())).append("\"");
213-
} else if (entry.getValue() instanceof Boolean) {
214-
optionsJson.append(entry.getValue());
215-
} else {
216-
optionsJson.append(entry.getValue());
198+
// 提取HTTP方法(从headers的第一行)
199+
String method = "GET";
200+
if (!headersList.isEmpty()) {
201+
String firstHeader = headersList.get(0);
202+
if (firstHeader != null && firstHeader.contains(" ")) {
203+
method = firstHeader.substring(0, firstHeader.indexOf(" "));
217204
}
218205
}
219-
optionsJson.append("}");
220206

221-
String jsonPayload = String.format(
222-
"{\"scanUrl\":\"%s\",\"host\":\"%s\",\"headers\":%s,\"body\":\"%s\",\"options\":%s}",
223-
JsonUtils.escapeJson(url),
224-
JsonUtils.escapeJson(host),
225-
headersJson.toString(),
226-
JsonUtils.escapeJson(body),
227-
optionsJson.toString()
207+
// 使用 PayloadBuilder 构建 JSON,避免手动拼接导致特殊字符转义不完备
208+
String jsonPayload = PayloadBuilder.buildTaskPayload(
209+
url, host, method, headersList, body, options
228210
);
229211

230212
long markCount = markedRequestText.chars().filter(ch -> ch == '*').count();

0 commit comments

Comments
 (0)