Skip to content

Commit ee29a44

Browse files
committed
feat(ui): 新增复制SQLMap命令与执行扫描功能
- 在右键菜单中新增“复制SQLMap命令”和“执行SQLMap扫描”菜单项 - 支持检测二进制报文,自动禁用相关菜单并显示提示 - 复制功能支持自动复制到剪贴板或显示预览窗口供用户编辑 - 执行功能启动系统终端执行SQLMap命令,并弹窗提示执行状态 - 新增命令预览对话框支持命令编辑、复制及执行操作 - 配置管理器添加剪贴板相关配置项及直接执行配置项 - 在UI主面板新增“剪贴板配置”和“直接执行配置”两个选项卡 - 优化ScanConfig生成参数格式,统一去除等号保持兼容性 - 优化右键菜单显示逻辑,实现依赖后端连接菜单自动禁用 - 新增配置面板支持设置自动复制开关与HTTP请求临时文件目录 - 提供缺失配置提示,引导用户前往直接执行配置界面完善信息
1 parent fe2dd6b commit ee29a44

18 files changed

Lines changed: 4655 additions & 68 deletions

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

Lines changed: 244 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import burp.*;
44

55
import com.sqlmapwebui.burp.dialogs.*;
6+
import com.sqlmapwebui.burp.util.CommandExecutor;
7+
import com.sqlmapwebui.burp.util.SqlCommandBuilder;
68

79
import javax.swing.*;
810
import java.awt.*;
@@ -145,11 +147,6 @@ public Component getUiComponent() {
145147
public List<JMenuItem> createMenuItems(IContextMenuInvocation invocation) {
146148
List<JMenuItem> menuItems = new ArrayList<>();
147149

148-
// 只有已连接状态才显示菜单
149-
if (!configManager.isConnected()) {
150-
return menuItems;
151-
}
152-
153150
byte invocationContext = invocation.getInvocationContext();
154151
if (invocationContext == IContextMenuInvocation.CONTEXT_MESSAGE_EDITOR_REQUEST ||
155152
invocationContext == IContextMenuInvocation.CONTEXT_MESSAGE_VIEWER_REQUEST ||
@@ -166,9 +163,15 @@ public List<JMenuItem> createMenuItems(IContextMenuInvocation invocation) {
166163
FilterResult filterResult = filterBinaryRequests(selectedMessages);
167164
String menuSuffix = filterResult.getMenuSuffix();
168165

166+
// ==================== 需要后端连接的菜单项(未连接时置灰) ====================
167+
boolean connected = configManager.isConnected();
168+
169169
// 使用用户选择的配置发送
170170
JMenuItem sendWithDefault = new JMenuItem("Send to SQLMap WebUI" + menuSuffix);
171-
if (filterResult.allBinary()) {
171+
if (!connected) {
172+
sendWithDefault.setEnabled(false);
173+
sendWithDefault.setToolTipText("未连接到SQLMap WebUI后端,请检查连接配置");
174+
} else if (filterResult.allBinary()) {
172175
sendWithDefault.setEnabled(false);
173176
sendWithDefault.setToolTipText("所有选中的报文都是二进制格式,无法发起扫描任务");
174177
} else {
@@ -182,7 +185,10 @@ public List<JMenuItem> createMenuItems(IContextMenuInvocation invocation) {
182185
// 标记注入点并扫描 - 支持多选报文
183186
int maxMarkCount = configManager.getMaxInjectionMarkCount();
184187
JMenuItem markInjectionPoints = new JMenuItem("标记注入点并扫描 (*)" + menuSuffix);
185-
if (filterResult.allBinary()) {
188+
if (!connected) {
189+
markInjectionPoints.setEnabled(false);
190+
markInjectionPoints.setToolTipText("未连接到SQLMap WebUI后端,请检查连接配置");
191+
} else if (filterResult.allBinary()) {
186192
markInjectionPoints.setEnabled(false);
187193
markInjectionPoints.setToolTipText("所有选中的报文都是二进制格式,无法发起扫描任务");
188194
} else {
@@ -204,7 +210,10 @@ public List<JMenuItem> createMenuItems(IContextMenuInvocation invocation) {
204210

205211
// 配置扫描发送(高级配置对话框)
206212
JMenuItem sendWithOptions = new JMenuItem("Send to SQLMap WebUI (配置扫描)..." + menuSuffix);
207-
if (filterResult.allBinary()) {
213+
if (!connected) {
214+
sendWithOptions.setEnabled(false);
215+
sendWithOptions.setToolTipText("未连接到SQLMap WebUI后端,请检查连接配置");
216+
} else if (filterResult.allBinary()) {
208217
sendWithOptions.setEnabled(false);
209218
sendWithOptions.setToolTipText("所有选中的报文都是二进制格式,无法发起扫描任务");
210219
} else {
@@ -219,22 +228,62 @@ public List<JMenuItem> createMenuItems(IContextMenuInvocation invocation) {
219228
}
220229
menuItems.add(sendWithOptions);
221230

222-
// 提交会话Header 和 Header规则 - 仅在选中单条请求时显示
231+
// 提交会话Header 和 Header规则 - 仅在选中单条请求时显示,且需要后端连接
223232
if (selectedMessages.length == 1 && filterResult.hasTextMessages()) {
224233
JMenuItem submitSessionHeaders = new JMenuItem("提交会话Header");
225-
submitSessionHeaders.addActionListener(e -> {
226-
SessionHeaderDialog dialog = new SessionHeaderDialog(callbacks, apiClient, uiTab);
227-
dialog.show(filterResult.textMessages.get(0));
228-
});
234+
if (!connected) {
235+
submitSessionHeaders.setEnabled(false);
236+
submitSessionHeaders.setToolTipText("未连接到SQLMap WebUI后端,请检查连接配置");
237+
} else {
238+
submitSessionHeaders.addActionListener(e -> {
239+
SessionHeaderDialog dialog = new SessionHeaderDialog(callbacks, apiClient, uiTab);
240+
dialog.show(filterResult.textMessages.get(0));
241+
});
242+
}
229243
menuItems.add(submitSessionHeaders);
230244

231245
JMenuItem submitHeaderRule = new JMenuItem("提交Header规则");
232-
submitHeaderRule.addActionListener(e -> {
233-
HeaderRuleDialog dialog = new HeaderRuleDialog(callbacks, apiClient, uiTab);
234-
dialog.show(filterResult.textMessages.get(0));
235-
});
246+
if (!connected) {
247+
submitHeaderRule.setEnabled(false);
248+
submitHeaderRule.setToolTipText("未连接到SQLMap WebUI后端,请检查连接配置");
249+
} else {
250+
submitHeaderRule.addActionListener(e -> {
251+
HeaderRuleDialog dialog = new HeaderRuleDialog(callbacks, apiClient, uiTab);
252+
dialog.show(filterResult.textMessages.get(0));
253+
});
254+
}
236255
menuItems.add(submitHeaderRule);
237256
}
257+
258+
// ==================== 不依赖后端的菜单项(始终可用) ====================
259+
260+
// 复制SQLMap命令
261+
JMenuItem copySqlCommand = new JMenuItem("复制SQLMap命令" + menuSuffix);
262+
if (filterResult.allBinary()) {
263+
copySqlCommand.setEnabled(false);
264+
copySqlCommand.setToolTipText("所有选中的报文都是二进制格式,无法生成SQLMap命令");
265+
} else {
266+
copySqlCommand.addActionListener(e -> {
267+
if (filterResult.hasTextMessages()) {
268+
handleCopySqlCommand(filterResult.textMessages.get(0));
269+
}
270+
});
271+
}
272+
menuItems.add(copySqlCommand);
273+
274+
// 执行SQLMap扫描
275+
JMenuItem executeSqlMap = new JMenuItem("执行SQLMap扫描" + menuSuffix);
276+
if (filterResult.allBinary()) {
277+
executeSqlMap.setEnabled(false);
278+
executeSqlMap.setToolTipText("所有选中的报文都是二进制格式,无法执行SQLMap扫描");
279+
} else {
280+
executeSqlMap.addActionListener(e -> {
281+
if (filterResult.hasTextMessages()) {
282+
handleExecuteSqlMap(filterResult.textMessages.get(0));
283+
}
284+
});
285+
}
286+
menuItems.add(executeSqlMap);
238287
}
239288

240289
return menuItems;
@@ -398,4 +447,182 @@ private void sendRequestToBackend(IHttpRequestResponse requestResponse, ScanConf
398447
stderr.println("[-] Error: " + e.getMessage());
399448
}
400449
}
450+
451+
// ==================== 新增:复制SQLMap命令和执行SQLMap扫描 ====================
452+
453+
/**
454+
* 处理"复制SQLMap命令"菜单点击
455+
*/
456+
private void handleCopySqlCommand(IHttpRequestResponse message) {
457+
try {
458+
// 检查SQLMap路径是否配置
459+
String sqlmapPath = configManager.getDirectSqlmapPath();
460+
if (sqlmapPath == null || sqlmapPath.trim().isEmpty()) {
461+
if (CommandPreviewDialog.showConfigWarning(uiTab, "SQLMap路径(请在\"直接执行配置\"选项卡中配置)")) {
462+
uiTab.switchToDirectExecuteTab();
463+
}
464+
return;
465+
}
466+
467+
// 生成HTTP请求字符串
468+
String httpRequest = buildHttpRequest(message);
469+
470+
// 生成临时文件
471+
String requestFilePath = SqlCommandBuilder.generateRequestFile(
472+
httpRequest,
473+
configManager.getClipboardTempDir()
474+
);
475+
476+
// 构建SQLMap命令
477+
String command = SqlCommandBuilder.buildCopyableCommand(
478+
configManager.getDirectPythonPath(),
479+
sqlmapPath,
480+
requestFilePath,
481+
buildAdditionalParams(configManager.getSelectedScanConfig())
482+
);
483+
484+
// 根据配置决定是直接复制还是显示预览
485+
if (configManager.isClipboardAutoCopy()) {
486+
// 直接复制到剪贴板
487+
CommandExecutor.copyToClipboard(command);
488+
uiTab.appendLog("[+] SQLMap命令已复制到剪贴板");
489+
uiTab.appendLog(" 请求文件: " + requestFilePath);
490+
JOptionPane.showMessageDialog(uiTab,
491+
"SQLMap命令已复制到剪贴板!\n\n" +
492+
"请求文件: " + requestFilePath,
493+
"复制成功", JOptionPane.INFORMATION_MESSAGE);
494+
} else {
495+
// 显示预览对话框
496+
CommandPreviewDialog dialog = new CommandPreviewDialog(configManager);
497+
dialog.showCopyDialog(uiTab, command, requestFilePath);
498+
}
499+
500+
} catch (Exception e) {
501+
uiTab.appendLog("[-] 生成SQLMap命令失败: " + e.getMessage());
502+
JOptionPane.showMessageDialog(uiTab,
503+
"生成SQLMap命令失败:\n" + e.getMessage(),
504+
"错误", JOptionPane.ERROR_MESSAGE);
505+
}
506+
}
507+
508+
/**
509+
* 处理"执行SQLMap扫描"菜单点击
510+
*/
511+
private void handleExecuteSqlMap(IHttpRequestResponse message) {
512+
try {
513+
// 检查SQLMap路径是否配置
514+
String sqlmapPath = configManager.getDirectSqlmapPath();
515+
if (sqlmapPath == null || sqlmapPath.isEmpty()) {
516+
if (CommandPreviewDialog.showConfigWarning(uiTab, "SQLMap路径")) {
517+
uiTab.switchToDirectExecuteTab();
518+
}
519+
return;
520+
}
521+
522+
// 生成HTTP请求字符串
523+
String httpRequest = buildHttpRequest(message);
524+
525+
// 生成临时文件
526+
String requestFilePath = SqlCommandBuilder.generateRequestFile(
527+
httpRequest,
528+
configManager.getClipboardTempDir()
529+
);
530+
531+
// 构建SQLMap命令
532+
String sqlmapCommand = SqlCommandBuilder.buildSqlMapCommand(
533+
configManager.getDirectPythonPath(),
534+
configManager.getDirectSqlmapPath(),
535+
requestFilePath,
536+
buildAdditionalParams(configManager.getSelectedScanConfig())
537+
);
538+
539+
// 构建终端命令
540+
String terminalCommand = SqlCommandBuilder.buildTerminalCommand(
541+
sqlmapCommand,
542+
configManager.getDirectTerminalType(),
543+
configManager.isDirectKeepTerminal()
544+
);
545+
546+
uiTab.appendLog("[+] 正在启动SQLMap扫描...");
547+
uiTab.appendLog(" 请求文件: " + requestFilePath);
548+
549+
// 执行命令
550+
CommandExecutor.ExecutionResult result = CommandExecutor.executeInTerminal(
551+
sqlmapCommand,
552+
configManager.getDirectTerminalType(),
553+
configManager.isDirectKeepTerminal()
554+
);
555+
556+
if (result.isSuccess()) {
557+
uiTab.appendLog("[+] SQLMap扫描已在终端中启动");
558+
JOptionPane.showMessageDialog(uiTab,
559+
"SQLMap扫描已在终端中启动!\n\n" +
560+
"终端窗口会独立运行,您可以继续使用Burp。",
561+
"执行成功", JOptionPane.INFORMATION_MESSAGE);
562+
} else {
563+
uiTab.appendLog("[-] 启动终端失败: " + result.getMessage());
564+
JOptionPane.showMessageDialog(uiTab,
565+
"启动终端失败:\n" + result.getMessage(),
566+
"执行失败", JOptionPane.ERROR_MESSAGE);
567+
}
568+
569+
} catch (Exception e) {
570+
uiTab.appendLog("[-] 执行SQLMap扫描失败: " + e.getMessage());
571+
JOptionPane.showMessageDialog(uiTab,
572+
"执行SQLMap扫描失败:\n" + e.getMessage(),
573+
"错误", JOptionPane.ERROR_MESSAGE);
574+
}
575+
}
576+
577+
/**
578+
* 从IHttpRequestResponse构建HTTP请求字符串
579+
*/
580+
private String buildHttpRequest(IHttpRequestResponse message) {
581+
StringBuilder request = new StringBuilder();
582+
583+
// 获取请求信息
584+
IRequestInfo requestInfo = helpers.analyzeRequest(message);
585+
586+
// 请求行
587+
String method = requestInfo.getMethod();
588+
String path = requestInfo.getUrl().getPath();
589+
String query = requestInfo.getUrl().getQuery();
590+
591+
request.append(method).append(" ").append(path);
592+
if (query != null && !query.isEmpty()) {
593+
request.append("?").append(query);
594+
}
595+
request.append(" HTTP/1.1\r\n");
596+
597+
// 请求头
598+
List<String> headers = requestInfo.getHeaders();
599+
for (String header : headers) {
600+
request.append(header).append("\r\n");
601+
}
602+
603+
// 空行
604+
request.append("\r\n");
605+
606+
// 请求体
607+
byte[] body = message.getRequest();
608+
if (body != null && body.length > 0) {
609+
int bodyOffset = requestInfo.getBodyOffset();
610+
String bodyStr = new String(body, bodyOffset, body.length - bodyOffset, StandardCharsets.UTF_8);
611+
request.append(bodyStr);
612+
}
613+
614+
return request.toString();
615+
}
616+
617+
/**
618+
* 构建额外的SQLMap参数(CLI格式)
619+
*/
620+
private String buildAdditionalParams(ScanConfig config) {
621+
if (config == null) {
622+
return "";
623+
}
624+
// 使用 toCommandLineString() 生成正确的 CLI 参数(如 --random-agent, --batch 等)
625+
return config.toCommandLineString().trim();
626+
}
401627
}
628+

0 commit comments

Comments
 (0)