From 95e1cb4dc159fa672ba84dfbc1dbe4252a29ceed Mon Sep 17 00:00:00 2001 From: gh0stkey <24655118+gh0stkey@users.noreply.github.com> Date: Tue, 6 May 2025 11:24:17 +0800 Subject: [PATCH] Version: 4.2 Update Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com> --- src/main/java/hae/Config.java | 2 + src/main/java/hae/HaE.java | 11 +- .../{MessageCache.java => DataCache.java} | 2 +- src/main/java/hae/component/Config.java | 12 +- .../java/hae/component/board/Databoard.java | 8 +- .../board/message/MessageTableModel.java | 4 +- .../hae/instances/editor/RequestEditor.java | 2 +- .../hae/instances/editor/ResponseEditor.java | 2 +- .../hae/instances/editor/WebSocketEditor.java | 2 +- .../http/HttpMessageActiveHandler.java | 2 +- .../http/HttpMessagePassiveHandler.java | 2 +- .../http/utils/MessageProcessor.java | 72 +++-- .../instances/http/utils/RegularMatcher.java | 249 ++++++++++-------- .../websocket/WebSocketMessageHandler.java | 5 +- src/main/java/hae/utils/ConfigLoader.java | 41 ++- src/main/java/hae/utils/DataManager.java | 4 +- src/main/java/hae/utils/http/HttpUtils.java | 25 +- .../java/hae/utils/rule/RuleProcessor.java | 3 + src/main/resources/rules/Rules.yml | 31 ++- 19 files changed, 300 insertions(+), 179 deletions(-) rename src/main/java/hae/cache/{MessageCache.java => DataCache.java} (96%) diff --git a/src/main/java/hae/Config.java b/src/main/java/hae/Config.java index ec636be..f91bf08 100644 --- a/src/main/java/hae/Config.java +++ b/src/main/java/hae/Config.java @@ -12,6 +12,8 @@ public class Config { public static String status = "404"; + public static String header = "Last-Modified|Date|Connection|ETag"; + public static String size = "0"; public static String boundary = "\n\t\n"; diff --git a/src/main/java/hae/HaE.java b/src/main/java/hae/HaE.java index fd23c4b..36b0204 100644 --- a/src/main/java/hae/HaE.java +++ b/src/main/java/hae/HaE.java @@ -3,7 +3,7 @@ package hae; import burp.api.montoya.BurpExtension; import burp.api.montoya.MontoyaApi; import burp.api.montoya.logging.Logging; -import hae.cache.MessageCache; +import hae.cache.DataCache; import hae.component.Main; import hae.component.board.message.MessageTableModel; import hae.instances.editor.RequestEditor; @@ -19,7 +19,7 @@ public class HaE implements BurpExtension { public void initialize(MontoyaApi api) { // 设置扩展名称 api.extension().setName("HaE - Highlighter and Extractor"); - String version = "4.1.2"; + String version = "4.2"; // 加载扩展后输出的项目信息 Logging logging = api.logging(); @@ -40,7 +40,7 @@ public class HaE implements BurpExtension { api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel)); // 注册WebSocket处理器 - api.proxy().registerWebSocketCreationHandler(proxyWebSocketCreation -> proxyWebSocketCreation.proxyWebSocket().registerProxyMessageHandler(new WebSocketMessageHandler(api))); + api.proxy().registerWebSocketCreationHandler(proxyWebSocketCreation -> proxyWebSocketCreation.proxyWebSocket().registerProxyMessageHandler(new WebSocketMessageHandler(api, configLoader))); // 注册消息编辑框(用于展示数据) api.userInterface().registerHttpRequestEditorProvider(new RequestEditor(api, configLoader)); @@ -51,18 +51,17 @@ public class HaE implements BurpExtension { DataManager dataManager = new DataManager(api); dataManager.loadData(messageTableModel); - api.extension().registerUnloadingHandler(() -> { // 卸载清空数据 Config.globalDataMap.clear(); - MessageCache.clear(); + DataCache.clear(); }); } private Boolean getBurpSuiteProStatus(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) { boolean burpSuiteProStatus = false; try { - burpSuiteProStatus = api.burpSuite().version().name().contains("Professional"); + burpSuiteProStatus = api.burpSuite().version().edition().displayName().equals("Professional"); } catch (Exception e) { try { api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel)).deregister(); diff --git a/src/main/java/hae/cache/MessageCache.java b/src/main/java/hae/cache/DataCache.java similarity index 96% rename from src/main/java/hae/cache/MessageCache.java rename to src/main/java/hae/cache/DataCache.java index 1fe27c5..3873ffc 100644 --- a/src/main/java/hae/cache/MessageCache.java +++ b/src/main/java/hae/cache/DataCache.java @@ -6,7 +6,7 @@ import com.github.benmanes.caffeine.cache.Caffeine; import java.util.Map; import java.util.concurrent.TimeUnit; -public class MessageCache { +public class DataCache { private static final int MAX_SIZE = 100000; private static final int EXPIRE_DURATION = 4; diff --git a/src/main/java/hae/component/Config.java b/src/main/java/hae/component/Config.java index af8fd21..67ca768 100644 --- a/src/main/java/hae/component/Config.java +++ b/src/main/java/hae/component/Config.java @@ -73,7 +73,7 @@ public class Config extends JPanel { constraints.gridx = 1; JTabbedPane configTabbedPanel = new JTabbedPane(); - String[] settingMode = new String[]{"Exclude suffix", "Block host", "Exclude status"}; + String[] settingMode = new String[]{"Exclude suffix", "Block host", "Exclude status", "Dynamic Header"}; JPanel settingPanel = createConfigTablePanel(settingMode); JPanel northPanel = new JPanel(new BorderLayout()); @@ -194,6 +194,12 @@ public class Config extends JPanel { configLoader.setExcludeStatus(values); } } + + if (selected.equals("Dynamic Header")) { + if (!values.equals(configLoader.getExcludeStatus()) && !values.isEmpty()) { + configLoader.setDynamicHeader(values); + } + } } }; } @@ -214,6 +220,10 @@ public class Config extends JPanel { if (selected.equals("Exclude status")) { addDataToTable(configLoader.getExcludeStatus().replaceAll("\\|", "\r\n"), model); } + + if (selected.equals("Dynamic Header")) { + addDataToTable(configLoader.getDynamicHeader().replaceAll("\\|", "\r\n"), model); + } } }; } diff --git a/src/main/java/hae/component/board/Databoard.java b/src/main/java/hae/component/board/Databoard.java index f384dce..b5ae191 100644 --- a/src/main/java/hae/component/board/Databoard.java +++ b/src/main/java/hae/component/board/Databoard.java @@ -2,7 +2,7 @@ package hae.component.board; import burp.api.montoya.MontoyaApi; import hae.Config; -import hae.cache.MessageCache; +import hae.cache.DataCache; import hae.component.board.message.MessageTableModel; import hae.component.board.message.MessageTableModel.MessageTable; import hae.component.board.table.Datatable; @@ -345,7 +345,11 @@ public class Databoard extends JPanel { } private void clearCacheActionPerformed(ActionEvent e) { - MessageCache.clear(); + int retCode = JOptionPane.showConfirmDialog(this, "Do you want to clear cache?", "Info", + JOptionPane.YES_NO_OPTION); + if (retCode == JOptionPane.YES_OPTION) { + DataCache.clear(); + } } private void clearDataActionPerformed(ActionEvent e) { diff --git a/src/main/java/hae/component/board/message/MessageTableModel.java b/src/main/java/hae/component/board/message/MessageTableModel.java index e169ddf..ae01c61 100644 --- a/src/main/java/hae/component/board/message/MessageTableModel.java +++ b/src/main/java/hae/component/board/message/MessageTableModel.java @@ -292,13 +292,13 @@ public class MessageTableModel extends AbstractTableModel { String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8); String requestHeaders = httpRequest.headers().stream() .map(HttpHeader::toString) - .collect(Collectors.joining("\n")); + .collect(Collectors.joining("\r\n")); String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8); String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8); String responseHeaders = httpResponse.headers().stream() .map(HttpHeader::toString) - .collect(Collectors.joining("\n")); + .collect(Collectors.joining("\r\n")); Config.globalRules.keySet().forEach(i -> { for (Object[] objects : Config.globalRules.get(i)) { diff --git a/src/main/java/hae/instances/editor/RequestEditor.java b/src/main/java/hae/instances/editor/RequestEditor.java index e025ca9..9eba676 100644 --- a/src/main/java/hae/instances/editor/RequestEditor.java +++ b/src/main/java/hae/instances/editor/RequestEditor.java @@ -73,7 +73,7 @@ public class RequestEditor implements HttpRequestEditorProvider { this.configLoader = configLoader; this.httpUtils = new HttpUtils(api, configLoader); this.creationContext = creationContext; - this.messageProcessor = new MessageProcessor(api); + this.messageProcessor = new MessageProcessor(api, configLoader); } @Override diff --git a/src/main/java/hae/instances/editor/ResponseEditor.java b/src/main/java/hae/instances/editor/ResponseEditor.java index 5558c7a..6e9facc 100644 --- a/src/main/java/hae/instances/editor/ResponseEditor.java +++ b/src/main/java/hae/instances/editor/ResponseEditor.java @@ -50,7 +50,7 @@ public class ResponseEditor implements HttpResponseEditorProvider { this.configLoader = configLoader; this.httpUtils = new HttpUtils(api, configLoader); this.creationContext = creationContext; - this.messageProcessor = new MessageProcessor(api); + this.messageProcessor = new MessageProcessor(api, configLoader); } @Override diff --git a/src/main/java/hae/instances/editor/WebSocketEditor.java b/src/main/java/hae/instances/editor/WebSocketEditor.java index 13e93db..288ed0d 100644 --- a/src/main/java/hae/instances/editor/WebSocketEditor.java +++ b/src/main/java/hae/instances/editor/WebSocketEditor.java @@ -44,7 +44,7 @@ public class WebSocketEditor implements WebSocketMessageEditorProvider { this.api = api; this.configLoader = configLoader; this.creationContext = creationContext; - this.messageProcessor = new MessageProcessor(api); + this.messageProcessor = new MessageProcessor(api, configLoader); } @Override diff --git a/src/main/java/hae/instances/http/HttpMessageActiveHandler.java b/src/main/java/hae/instances/http/HttpMessageActiveHandler.java index ab15c52..f8e50fb 100644 --- a/src/main/java/hae/instances/http/HttpMessageActiveHandler.java +++ b/src/main/java/hae/instances/http/HttpMessageActiveHandler.java @@ -35,7 +35,7 @@ public class HttpMessageActiveHandler implements HttpHandler { this.configLoader = configLoader; this.httpUtils = new HttpUtils(api, configLoader); this.messageTableModel = messageTableModel; - this.messageProcessor = new MessageProcessor(api); + this.messageProcessor = new MessageProcessor(api, configLoader); } @Override diff --git a/src/main/java/hae/instances/http/HttpMessagePassiveHandler.java b/src/main/java/hae/instances/http/HttpMessagePassiveHandler.java index 81cbc69..3b88368 100644 --- a/src/main/java/hae/instances/http/HttpMessagePassiveHandler.java +++ b/src/main/java/hae/instances/http/HttpMessagePassiveHandler.java @@ -37,7 +37,7 @@ public class HttpMessagePassiveHandler implements ScanCheck { this.configLoader = configLoader; this.httpUtils = new HttpUtils(api, configLoader); this.messageTableModel = messageTableModel; - this.messageProcessor = new MessageProcessor(api); + this.messageProcessor = new MessageProcessor(api, configLoader); } @Override diff --git a/src/main/java/hae/instances/http/utils/MessageProcessor.java b/src/main/java/hae/instances/http/utils/MessageProcessor.java index d4c4cb6..aff3c65 100644 --- a/src/main/java/hae/instances/http/utils/MessageProcessor.java +++ b/src/main/java/hae/instances/http/utils/MessageProcessor.java @@ -5,6 +5,7 @@ import burp.api.montoya.http.message.HttpHeader; import burp.api.montoya.http.message.requests.HttpRequest; import burp.api.montoya.http.message.responses.HttpResponse; import hae.Config; +import hae.utils.ConfigLoader; import java.nio.charset.StandardCharsets; import java.util.*; @@ -14,18 +15,16 @@ public class MessageProcessor { private final MontoyaApi api; private final RegularMatcher regularMatcher; - private String finalColor = ""; - - public MessageProcessor(MontoyaApi api) { + public MessageProcessor(MontoyaApi api, ConfigLoader configLoader) { this.api = api; - this.regularMatcher = new RegularMatcher(api); + this.regularMatcher = new RegularMatcher(api, configLoader); } public List> processMessage(String host, String message, boolean flag) { Map> obj = null; try { - obj = regularMatcher.match(host, "any", message, message, message); + obj = regularMatcher.performRegexMatching(host, "any", message, message, message); } catch (Exception ignored) { } @@ -40,9 +39,9 @@ public class MessageProcessor { String body = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8); String header = httpResponse.headers().stream() .map(HttpHeader::toString) - .collect(Collectors.joining("\n")); + .collect(Collectors.joining("\r\n")); - obj = regularMatcher.match(host, "response", response, header, body); + obj = regularMatcher.performRegexMatching(host, "response", response, header, body); } catch (Exception ignored) { } @@ -57,9 +56,9 @@ public class MessageProcessor { String body = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8); String header = httpRequest.headers().stream() .map(HttpHeader::toString) - .collect(Collectors.joining("\n")); + .collect(Collectors.joining("\r\n")); - obj = regularMatcher.match(host, "request", request, header, body); + obj = regularMatcher.performRegexMatching(host, "request", request, header, body); } catch (Exception ignored) { } @@ -137,41 +136,38 @@ public class MessageProcessor { return indices; } - private void upgradeColors(List colorList) { - int colorSize = colorList.size(); - String[] colorArray = Config.color; - colorList.sort(Comparator.comparingInt(Integer::intValue)); - int i = 0; - List stack = new ArrayList<>(); - while (i < colorSize) { - if (stack.isEmpty()) { - stack.add(colorList.get(i)); - } else { - if (!Objects.equals(colorList.get(i), stack.stream().reduce((first, second) -> second).orElse(99999999))) { - stack.add(colorList.get(i)); - } else { - stack.set(stack.size() - 1, stack.get(stack.size() - 1) - 1); - } - } - i++; + private String upgradeColors(List colorList) { + if (colorList == null || colorList.isEmpty()) { + return Config.color[0]; } - // 利用HashSet删除重复元素 - HashSet tmpList = new HashSet(stack); - if (stack.size() == tmpList.size()) { - stack.sort(Comparator.comparingInt(Integer::intValue)); - if (stack.get(0) < 0) { - finalColor = colorArray[0]; - } else { - finalColor = colorArray[stack.get(0)]; + + // 创建副本避免修改原始数据 + List indices = new ArrayList<>(colorList); + indices.sort(Comparator.comparingInt(Integer::intValue)); + + // 处理颜色升级 + for (int i = 1; i < indices.size(); i++) { + if (indices.get(i).equals(indices.get(i - 1))) { + // 如果发现重复的颜色索引,将当前索引降级 + indices.set(i - 1, indices.get(i - 1) - 1); } - } else { - upgradeColors(stack); } + + // 获取最终的颜色索引 + int finalIndex = indices.stream() + .min(Integer::compareTo) + .orElse(0); + + // 处理负数索引情况 + if (finalIndex < 0) { + return Config.color[0]; + } + + return Config.color[finalIndex]; } public String retrieveFinalColor(List colorList) { - upgradeColors(colorList); - return finalColor; + return upgradeColors(colorList); } } diff --git a/src/main/java/hae/instances/http/utils/RegularMatcher.java b/src/main/java/hae/instances/http/utils/RegularMatcher.java index 953313e..a0f0300 100644 --- a/src/main/java/hae/instances/http/utils/RegularMatcher.java +++ b/src/main/java/hae/instances/http/utils/RegularMatcher.java @@ -8,7 +8,8 @@ import dk.brics.automaton.AutomatonMatcher; import dk.brics.automaton.RegExp; import dk.brics.automaton.RunAutomaton; import hae.Config; -import hae.cache.MessageCache; +import hae.cache.DataCache; +import hae.utils.ConfigLoader; import hae.utils.DataManager; import hae.utils.string.HashCalculator; import hae.utils.string.StringProcessor; @@ -20,14 +21,17 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; public class RegularMatcher { + private static final Map nfaPatternCache = new ConcurrentHashMap<>(); + private static final Map dfaAutomatonCache = new ConcurrentHashMap<>(); private final MontoyaApi api; + private final ConfigLoader configLoader; - public RegularMatcher(MontoyaApi api) { + public RegularMatcher(MontoyaApi api, ConfigLoader configLoader) { this.api = api; - + this.configLoader = configLoader; } - public synchronized static void putDataToGlobalMap(MontoyaApi api, String host, String name, List dataList, boolean flag) { + public synchronized static void updateGlobalMatchCache(MontoyaApi api, String host, String name, List dataList, boolean flag) { // 添加到全局变量中,便于Databoard检索 if (!Objects.equals(host, "") && host != null) { Config.globalDataMap.compute(host, (existingHost, existingMap) -> { @@ -74,95 +78,123 @@ public class RegularMatcher { } } - public Map> match(String host, String type, String message, String header, String body) { - // 先从缓存池里判断是否有已经匹配好的结果 - String messageIndex = HashCalculator.calculateHash(message.getBytes()); - Map> map = MessageCache.get(messageIndex); - if (map != null) { - return map; - } else { - // 最终返回的结果 - Map> finalMap = new HashMap<>(); - Config.globalRules.keySet().parallelStream().forEach(i -> { - for (Object[] objects : Config.globalRules.get(i)) { - // 多线程执行,一定程度上减少阻塞现象 - String matchContent = ""; - // 遍历获取规则 - List result; - Map tmpMap = new HashMap<>(); + public Map> performRegexMatching(String host, String type, String message, String header, String body) { + // 删除动态响应头再进行存储 + String originalMessage = message; + String dynamicHeader = configLoader.getDynamicHeader(); - boolean loaded = (Boolean) objects[0]; - String name = objects[1].toString(); - String f_regex = objects[2].toString(); - String s_regex = objects[3].toString(); - String format = objects[4].toString(); - String color = objects[5].toString(); - String scope = objects[6].toString(); - String engine = objects[7].toString(); - boolean sensitive = (Boolean) objects[8]; - - // 判断规则是否开启与作用域 - if (loaded && (scope.contains(type) || scope.contains("any") || type.equals("any"))) { - switch (scope) { - case "any": - case "request": - case "response": - matchContent = message; - break; - case "any header": - case "request header": - case "response header": - matchContent = header; - break; - case "any body": - case "request body": - case "response body": - matchContent = body; - break; - case "request line": - case "response line": - matchContent = message.split("\\r?\\n", 2)[0]; - break; - default: - break; - } - - try { - result = new ArrayList<>(matchByRegex(f_regex, s_regex, matchContent, format, engine, sensitive)); - } catch (Exception e) { - api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex)); - api.logging().logToError(e.getMessage()); - continue; - } - - // 去除重复内容 - HashSet tmpList = new HashSet(result); - result.clear(); - result.addAll(tmpList); - - if (!result.isEmpty()) { - tmpMap.put("color", color); - String dataStr = String.join(Config.boundary, result); - tmpMap.put("data", dataStr); - - String nameAndSize = String.format("%s (%s)", name, result.size()); - finalMap.put(nameAndSize, tmpMap); - - putDataToGlobalMap(api, host, name, result, true); - } - } - } - }); - MessageCache.put(messageIndex, finalMap); - return finalMap; + if (!dynamicHeader.isBlank()) { + String modifiedHeader = header.replaceAll(String.format("(%s):.*?\r\n", configLoader.getDynamicHeader()), ""); + message = message.replace(header, modifiedHeader); } + + String messageIndex = HashCalculator.calculateHash(message.getBytes()); + + // 从数据缓存中读取 + Map> dataCacheMap = DataCache.get(messageIndex); + + // 存在则返回 + if (dataCacheMap != null) { + return dataCacheMap; + } + + // 最终返回的结果 + String firstLine = originalMessage.split("\\r?\\n", 2)[0]; + Map> finalMap = applyMatchingRules(host, type, originalMessage, firstLine, header, body); + + // 数据缓存写入,有可能是空值,当作匹配过的索引不再匹配 + DataCache.put(messageIndex, finalMap); + + return finalMap; } - private List matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) { + private Map> applyMatchingRules(String host, String type, String message, String firstLine, String header, String body) { + Map> finalMap = new HashMap<>(); + + Config.globalRules.keySet().parallelStream().forEach(i -> { + for (Object[] objects : Config.globalRules.get(i)) { + String matchContent = ""; + // 遍历获取规则 + List result; + Map tmpMap = new HashMap<>(); + + boolean loaded = (Boolean) objects[0]; + String name = objects[1].toString(); + String f_regex = objects[2].toString(); + String s_regex = objects[3].toString(); + String format = objects[4].toString(); + String color = objects[5].toString(); + String scope = objects[6].toString(); + String engine = objects[7].toString(); + boolean sensitive = (Boolean) objects[8]; + + // 判断规则是否开启与作用域 + if (loaded && (scope.contains(type) || scope.contains("any") || type.equals("any"))) { + // 在此处检查内容是否缓存,缓存则返回为空 + switch (scope) { + case "any": + case "request": + case "response": + matchContent = message; + break; + case "any header": + case "request header": + case "response header": + matchContent = header; + break; + case "any body": + case "request body": + case "response body": + matchContent = body; + break; + case "request line": + case "response line": + matchContent = firstLine; + break; + default: + break; + } + + // 匹配内容为空则跳出 + if (matchContent.isBlank()) { + break; + } + + try { + result = new ArrayList<>(executeRegexEngine(f_regex, s_regex, matchContent, format, engine, sensitive)); + } catch (Exception e) { + api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex)); + api.logging().logToError(e.getMessage()); + continue; + } + + // 去除重复内容 + HashSet tmpList = new HashSet(result); + result.clear(); + result.addAll(tmpList); + + if (!result.isEmpty()) { + tmpMap.put("color", color); + String dataStr = String.join(Config.boundary, result); + tmpMap.put("data", dataStr); + + String nameAndSize = String.format("%s (%s)", name, result.size()); + finalMap.put(nameAndSize, tmpMap); + + updateGlobalMatchCache(api, host, name, result, true); + } + } + } + }); + + return finalMap; + } + + private List executeRegexEngine(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) { List retList = new ArrayList<>(); if ("nfa".equals(engine)) { Matcher matcher = createPatternMatcher(f_regex, content, sensitive); - retList.addAll(extractMatches(s_regex, format, sensitive, matcher)); + retList.addAll(extractRegexMatchResults(s_regex, format, sensitive, matcher)); } else { // DFA不支持格式化输出,因此不关注format String newContent = content; @@ -172,44 +204,44 @@ public class RegularMatcher { newFirstRegex = f_regex.toLowerCase(); } AutomatonMatcher autoMatcher = createAutomatonMatcher(newFirstRegex, newContent); - retList.addAll(extractMatches(s_regex, autoMatcher, content)); + retList.addAll(extractRegexMatchResults(s_regex, autoMatcher, content)); } return retList; } - private List extractMatches(String s_regex, String format, boolean sensitive, Matcher matcher) { + private List extractRegexMatchResults(String s_regex, String format, boolean sensitive, Matcher matcher) { List matches = new ArrayList<>(); if (s_regex.isEmpty()) { - matches.addAll(getFormatString(matcher, format)); + matches.addAll(formatMatchResults(matcher, format)); } else { while (matcher.find()) { String matchContent = matcher.group(1); if (!matchContent.isEmpty()) { matcher = createPatternMatcher(s_regex, matchContent, sensitive); - matches.addAll(getFormatString(matcher, format)); + matches.addAll(formatMatchResults(matcher, format)); } } } return matches; } - private List extractMatches(String s_regex, AutomatonMatcher autoMatcher, String content) { + private List extractRegexMatchResults(String s_regex, AutomatonMatcher autoMatcher, String content) { List matches = new ArrayList<>(); if (s_regex.isEmpty()) { - matches.addAll(getFormatString(autoMatcher, content)); + matches.addAll(formatMatchResults(autoMatcher, content)); } else { while (autoMatcher.find()) { String s = autoMatcher.group(); if (!s.isEmpty()) { - autoMatcher = createAutomatonMatcher(s_regex, getSubString(content, s)); - matches.addAll(getFormatString(autoMatcher, content)); + autoMatcher = createAutomatonMatcher(s_regex, extractMatchedContent(content, s)); + matches.addAll(formatMatchResults(autoMatcher, content)); } } } return matches; } - private List getFormatString(Matcher matcher, String format) { + private List formatMatchResults(Matcher matcher, String format) { List indexList = parseIndexesFromString(format); List stringList = new ArrayList<>(); @@ -222,20 +254,20 @@ public class RegularMatcher { return ""; }).toArray(); - stringList.add(MessageFormat.format(reorderIndex(format), params)); + stringList.add(MessageFormat.format(normalizeFormatIndexes(format), params)); } } return stringList; } - private List getFormatString(AutomatonMatcher matcher, String content) { + private List formatMatchResults(AutomatonMatcher matcher, String content) { List stringList = new ArrayList<>(); while (matcher.find()) { String s = matcher.group(0); if (!s.isEmpty()) { - stringList.add(getSubString(content, s)); + stringList.add(extractMatchedContent(content, s)); } } @@ -243,14 +275,21 @@ public class RegularMatcher { } private Matcher createPatternMatcher(String regex, String content, boolean sensitive) { - Pattern pattern = sensitive ? Pattern.compile(regex) : Pattern.compile(regex, Pattern.CASE_INSENSITIVE); + Pattern pattern = nfaPatternCache.computeIfAbsent(regex, k -> { + int flags = sensitive ? 0 : Pattern.CASE_INSENSITIVE; + return Pattern.compile(regex, flags); + }); + return pattern.matcher(content); } private AutomatonMatcher createAutomatonMatcher(String regex, String content) { - RegExp regexp = new RegExp(regex); - Automaton auto = regexp.toAutomaton(); - RunAutomaton runAuto = new RunAutomaton(auto, true); + RunAutomaton runAuto = dfaAutomatonCache.computeIfAbsent(regex, k -> { + RegExp regexp = new RegExp(regex); + Automaton auto = regexp.toAutomaton(); + return new RunAutomaton(auto, true); + }); + return runAuto.newMatcher(content); } @@ -269,15 +308,16 @@ public class RegularMatcher { return indexes; } - private String getSubString(String content, String s) { + private String extractMatchedContent(String content, String s) { byte[] contentByte = api.utilities().byteUtils().convertFromString(content); byte[] sByte = api.utilities().byteUtils().convertFromString(s); int startIndex = api.utilities().byteUtils().indexOf(contentByte, sByte, false, 1, contentByte.length); int endIndex = startIndex + s.length(); + return content.substring(startIndex, endIndex); } - private String reorderIndex(String format) { + private String normalizeFormatIndexes(String format) { Pattern pattern = Pattern.compile("\\{(\\d+)}"); Matcher matcher = pattern.matcher(format); int count = 0; @@ -287,6 +327,7 @@ public class RegularMatcher { format = format.replace(matchStr, newStr); count++; } + return format; } } diff --git a/src/main/java/hae/instances/websocket/WebSocketMessageHandler.java b/src/main/java/hae/instances/websocket/WebSocketMessageHandler.java index 175666f..ba7e449 100644 --- a/src/main/java/hae/instances/websocket/WebSocketMessageHandler.java +++ b/src/main/java/hae/instances/websocket/WebSocketMessageHandler.java @@ -4,6 +4,7 @@ import burp.api.montoya.MontoyaApi; import burp.api.montoya.core.HighlightColor; import burp.api.montoya.proxy.websocket.*; import hae.instances.http.utils.MessageProcessor; +import hae.utils.ConfigLoader; import java.util.List; import java.util.Map; @@ -12,9 +13,9 @@ public class WebSocketMessageHandler implements ProxyMessageHandler { private final MontoyaApi api; private final MessageProcessor messageProcessor; - public WebSocketMessageHandler(MontoyaApi api) { + public WebSocketMessageHandler(MontoyaApi api, ConfigLoader configLoader) { this.api = api; - this.messageProcessor = new MessageProcessor(api); + this.messageProcessor = new MessageProcessor(api, configLoader); } @Override diff --git a/src/main/java/hae/utils/ConfigLoader.java b/src/main/java/hae/utils/ConfigLoader.java index 86228a1..731457c 100644 --- a/src/main/java/hae/utils/ConfigLoader.java +++ b/src/main/java/hae/utils/ConfigLoader.java @@ -3,7 +3,9 @@ package hae.utils; import burp.api.montoya.MontoyaApi; import hae.Config; import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; import org.yaml.snakeyaml.representer.Representer; import java.io.*; @@ -21,10 +23,7 @@ public class ConfigLoader { public ConfigLoader(MontoyaApi api) { this.api = api; - DumperOptions dop = new DumperOptions(); - dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - Representer representer = new Representer(dop); - this.yaml = new Yaml(representer, dop); + this.yaml = createSecureYaml(); String configPath = determineConfigPath(); this.configFilePath = String.format("%s/%s", configPath, "Config.yml"); @@ -54,6 +53,25 @@ public class ConfigLoader { return configPathFile.exists() && configPathFile.isDirectory(); } + private Yaml createSecureYaml() { + // 配置 LoaderOptions 进行安全限制 + LoaderOptions loaderOptions = new LoaderOptions(); + // 禁用注释处理 + loaderOptions.setProcessComments(false); + // 禁止递归键 + loaderOptions.setAllowRecursiveKeys(false); + + // 配置 DumperOptions 控制输出格式 + DumperOptions dop = new DumperOptions(); + dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + + // 创建 Representer + Representer representer = new Representer(dop); + + // 使用 SafeConstructor创建安全的 YAML 实例 + return new Yaml(new SafeConstructor(loaderOptions), representer, dop); + } + private String determineConfigPath() { // 优先级1:用户根目录 String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home")); @@ -79,6 +97,8 @@ public class ConfigLoader { r.put("ExcludeStatus", getExcludeStatus()); r.put("LimitSize", getLimitSize()); r.put("HaEScope", getScope()); + r.put("DynamicHeader", getDynamicHeader()); + try { Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8); yaml.dump(r, ws); @@ -97,10 +117,7 @@ public class ConfigLoader { try { InputStream inputStream = Files.newInputStream(Paths.get(getRulesFilePath())); - DumperOptions dop = new DumperOptions(); - dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - Representer representer = new Representer(dop); - Map rulesMap = new Yaml(representer, dop).load(inputStream); + Map rulesMap = yaml.load(inputStream); Object rulesObj = rulesMap.get("rules"); if (rulesObj instanceof List) { @@ -156,6 +173,14 @@ public class ConfigLoader { setValueToConfig("ExcludeStatus", status); } + public String getDynamicHeader() { + return getValueFromConfig("DynamicHeader", Config.header); + } + + public void setDynamicHeader(String header) { + setValueToConfig("DynamicHeader", header); + } + public String getLimitSize() { return getValueFromConfig("LimitSize", Config.size); } diff --git a/src/main/java/hae/utils/DataManager.java b/src/main/java/hae/utils/DataManager.java index 9b30240..661cacf 100644 --- a/src/main/java/hae/utils/DataManager.java +++ b/src/main/java/hae/utils/DataManager.java @@ -64,7 +64,7 @@ public class DataManager { dataIndex.forEach(index -> { PersistedObject dataObj = persistence.extensionData().getChildObject(index); try { - dataObj.stringListKeys().forEach(dataKey -> RegularMatcher.putDataToGlobalMap(api, index, dataKey, dataObj.getStringList(dataKey).stream().toList(), false)); + dataObj.stringListKeys().forEach(dataKey -> RegularMatcher.updateGlobalMatchCache(api, index, dataKey, dataObj.getStringList(dataKey).stream().toList(), false)); } catch (Exception ignored) { } }); @@ -114,7 +114,7 @@ public class DataManager { } } } catch (Exception e) { - api.logging().logToError("处理消息数据时出错: " + e.getMessage() + ", index: " + index); + api.logging().logToError("processBatch: " + e.getMessage()); } }); } diff --git a/src/main/java/hae/utils/http/HttpUtils.java b/src/main/java/hae/utils/http/HttpUtils.java index 38be798..f9d206d 100644 --- a/src/main/java/hae/utils/http/HttpUtils.java +++ b/src/main/java/hae/utils/http/HttpUtils.java @@ -25,16 +25,29 @@ public class HttpUtils { boolean retStatus = false; try { String host = StringProcessor.getHostByUrl(request.url()); - String[] hostList = configLoader.getBlockHost().split("\\|"); - boolean isBlockHost = isBlockHost(hostList, host); - List suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|")); - boolean isExcludeSuffix = suffixList.contains(request.fileExtension().toLowerCase()); + boolean isBlockHost = false; + String blockHost = configLoader.getBlockHost(); + if (!blockHost.isBlank()) { + String[] hostList = configLoader.getBlockHost().split("\\|"); + isBlockHost = isBlockHost(hostList, host); + } + + boolean isExcludeSuffix = false; + String suffix = configLoader.getExcludeSuffix(); + if (!suffix.isBlank()) { + List suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|")); + isExcludeSuffix = suffixList.contains(request.fileExtension().toLowerCase()); + } boolean isToolScope = !configLoader.getScope().contains(toolType); - List statusList = Arrays.asList(configLoader.getExcludeStatus().split("\\|")); - boolean isExcludeStatus = statusList.contains(String.valueOf(response.statusCode())); + boolean isExcludeStatus = false; + String status = configLoader.getExcludeStatus(); + if (!status.isBlank()) { + List statusList = Arrays.asList(configLoader.getExcludeStatus().split("\\|")); + isExcludeStatus = statusList.contains(String.valueOf(response.statusCode())); + } retStatus = isExcludeSuffix || isBlockHost || isToolScope || isExcludeStatus; } catch (Exception ignored) { diff --git a/src/main/java/hae/utils/rule/RuleProcessor.java b/src/main/java/hae/utils/rule/RuleProcessor.java index ed03608..92d5a17 100644 --- a/src/main/java/hae/utils/rule/RuleProcessor.java +++ b/src/main/java/hae/utils/rule/RuleProcessor.java @@ -2,6 +2,7 @@ package hae.utils.rule; import burp.api.montoya.MontoyaApi; import hae.Config; +import hae.cache.DataCache; import hae.utils.ConfigLoader; import hae.utils.rule.model.Group; import hae.utils.rule.model.Info; @@ -27,6 +28,8 @@ public class RuleProcessor { } public void rulesFormatAndSave() { + DataCache.clear(); + DumperOptions dop = new DumperOptions(); dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Representer representer = new Representer(dop); diff --git a/src/main/resources/rules/Rules.yml b/src/main/resources/rules/Rules.yml index 63c8165..cdf4881 100644 --- a/src/main/resources/rules/Rules.yml +++ b/src/main/resources/rules/Rules.yml @@ -55,6 +55,15 @@ rules: scope: response body engine: dfa sensitive: false + - name: Vite DevMode + loaded: true + f_regex: (/@vite/client) + s_regex: '' + format: '{0}' + color: red + scope: response body + engine: dfa + sensitive: true - group: Maybe Vulnerability rule: - name: Java Deserialization @@ -102,6 +111,24 @@ rules: scope: request engine: dfa sensitive: false + - name: Passwd File + loaded: true + f_regex: (/root:/bin/bash) + s_regex: '' + format: '{0}' + color: red + scope: response body + engine: dfa + sensitive: true + - name: Win.ini File + loaded: true + f_regex: (for 16-bit app) + s_regex: '' + format: '{0}' + color: red + scope: response body + engine: dfa + sensitive: true - group: Basic Information rule: - name: Email @@ -244,7 +271,7 @@ rules: rule: - name: Linkfinder loaded: true - f_regex: (?:"|')((?:(?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|(?:(?:(?:/|\.\./|\./)?[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,}\.[a-zA-Z]{1,4})|(?:(?:/|\.\./|\./)?[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,}/[^"'><,;|()]{1,}(?:\.[a-zA-Z]{1,4}|action)?)))(?:[\?|#][^"|']{0,})?(?:"|') + f_regex: (?:"|')(((?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,})|((?:/|\.\./|\./)[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,})|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{1,}\.(?:[a-zA-Z]{1,4}|action)(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{3,}(?:[\?|#][^"|']{0,}|))|([a-zA-Z0-9_\-]{1,}\.(?:\w)(?:[\?|#][^"|']{0,}|)))(?:"|') s_regex: '' format: '{0}' color: gray @@ -271,7 +298,7 @@ rules: sensitive: false - name: URL Schemes loaded: true - f_regex: (\b(?![\w]{0,10}?https?://)(([-A-Za-z0-9]{1,20})://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|])) + f_regex: (\b(?![\w]{0,10}?https?://)(([A-Za-z0-9-\.]{1,20})://([-\w+&@#/%?=~_|!:,.;]*[-\w+&@#/%=~_|])?)) s_regex: '' format: '{0}' color: yellow