Version: 4.2 Update

Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
This commit is contained in:
gh0stkey
2025-05-06 11:24:17 +08:00
parent baa7270f46
commit 95e1cb4dc1
19 changed files with 300 additions and 179 deletions

View File

@@ -12,6 +12,8 @@ public class Config {
public static String status = "404"; public static String status = "404";
public static String header = "Last-Modified|Date|Connection|ETag";
public static String size = "0"; public static String size = "0";
public static String boundary = "\n\t\n"; public static String boundary = "\n\t\n";

View File

@@ -3,7 +3,7 @@ package hae;
import burp.api.montoya.BurpExtension; import burp.api.montoya.BurpExtension;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import burp.api.montoya.logging.Logging; import burp.api.montoya.logging.Logging;
import hae.cache.MessageCache; import hae.cache.DataCache;
import hae.component.Main; import hae.component.Main;
import hae.component.board.message.MessageTableModel; import hae.component.board.message.MessageTableModel;
import hae.instances.editor.RequestEditor; import hae.instances.editor.RequestEditor;
@@ -19,7 +19,7 @@ public class HaE implements BurpExtension {
public void initialize(MontoyaApi api) { public void initialize(MontoyaApi api) {
// 设置扩展名称 // 设置扩展名称
api.extension().setName("HaE - Highlighter and Extractor"); api.extension().setName("HaE - Highlighter and Extractor");
String version = "4.1.2"; String version = "4.2";
// 加载扩展后输出的项目信息 // 加载扩展后输出的项目信息
Logging logging = api.logging(); Logging logging = api.logging();
@@ -40,7 +40,7 @@ public class HaE implements BurpExtension {
api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel)); api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel));
// 注册WebSocket处理器 // 注册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)); api.userInterface().registerHttpRequestEditorProvider(new RequestEditor(api, configLoader));
@@ -51,18 +51,17 @@ public class HaE implements BurpExtension {
DataManager dataManager = new DataManager(api); DataManager dataManager = new DataManager(api);
dataManager.loadData(messageTableModel); dataManager.loadData(messageTableModel);
api.extension().registerUnloadingHandler(() -> { api.extension().registerUnloadingHandler(() -> {
// 卸载清空数据 // 卸载清空数据
Config.globalDataMap.clear(); Config.globalDataMap.clear();
MessageCache.clear(); DataCache.clear();
}); });
} }
private Boolean getBurpSuiteProStatus(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) { private Boolean getBurpSuiteProStatus(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
boolean burpSuiteProStatus = false; boolean burpSuiteProStatus = false;
try { try {
burpSuiteProStatus = api.burpSuite().version().name().contains("Professional"); burpSuiteProStatus = api.burpSuite().version().edition().displayName().equals("Professional");
} catch (Exception e) { } catch (Exception e) {
try { try {
api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel)).deregister(); api.scanner().registerScanCheck(new HttpMessagePassiveHandler(api, configLoader, messageTableModel)).deregister();

View File

@@ -6,7 +6,7 @@ import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class MessageCache { public class DataCache {
private static final int MAX_SIZE = 100000; private static final int MAX_SIZE = 100000;
private static final int EXPIRE_DURATION = 4; private static final int EXPIRE_DURATION = 4;

View File

@@ -73,7 +73,7 @@ public class Config extends JPanel {
constraints.gridx = 1; constraints.gridx = 1;
JTabbedPane configTabbedPanel = new JTabbedPane(); 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 settingPanel = createConfigTablePanel(settingMode);
JPanel northPanel = new JPanel(new BorderLayout()); JPanel northPanel = new JPanel(new BorderLayout());
@@ -194,6 +194,12 @@ public class Config extends JPanel {
configLoader.setExcludeStatus(values); 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")) { if (selected.equals("Exclude status")) {
addDataToTable(configLoader.getExcludeStatus().replaceAll("\\|", "\r\n"), model); addDataToTable(configLoader.getExcludeStatus().replaceAll("\\|", "\r\n"), model);
} }
if (selected.equals("Dynamic Header")) {
addDataToTable(configLoader.getDynamicHeader().replaceAll("\\|", "\r\n"), model);
}
} }
}; };
} }

View File

@@ -2,7 +2,7 @@ package hae.component.board;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import hae.Config; import hae.Config;
import hae.cache.MessageCache; import hae.cache.DataCache;
import hae.component.board.message.MessageTableModel; import hae.component.board.message.MessageTableModel;
import hae.component.board.message.MessageTableModel.MessageTable; import hae.component.board.message.MessageTableModel.MessageTable;
import hae.component.board.table.Datatable; import hae.component.board.table.Datatable;
@@ -345,7 +345,11 @@ public class Databoard extends JPanel {
} }
private void clearCacheActionPerformed(ActionEvent e) { 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) { private void clearDataActionPerformed(ActionEvent e) {

View File

@@ -292,13 +292,13 @@ public class MessageTableModel extends AbstractTableModel {
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8); String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
String requestHeaders = httpRequest.headers().stream() String requestHeaders = httpRequest.headers().stream()
.map(HttpHeader::toString) .map(HttpHeader::toString)
.collect(Collectors.joining("\n")); .collect(Collectors.joining("\r\n"));
String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8); String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8); String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
String responseHeaders = httpResponse.headers().stream() String responseHeaders = httpResponse.headers().stream()
.map(HttpHeader::toString) .map(HttpHeader::toString)
.collect(Collectors.joining("\n")); .collect(Collectors.joining("\r\n"));
Config.globalRules.keySet().forEach(i -> { Config.globalRules.keySet().forEach(i -> {
for (Object[] objects : Config.globalRules.get(i)) { for (Object[] objects : Config.globalRules.get(i)) {

View File

@@ -73,7 +73,7 @@ public class RequestEditor implements HttpRequestEditorProvider {
this.configLoader = configLoader; this.configLoader = configLoader;
this.httpUtils = new HttpUtils(api, configLoader); this.httpUtils = new HttpUtils(api, configLoader);
this.creationContext = creationContext; this.creationContext = creationContext;
this.messageProcessor = new MessageProcessor(api); this.messageProcessor = new MessageProcessor(api, configLoader);
} }
@Override @Override

View File

@@ -50,7 +50,7 @@ public class ResponseEditor implements HttpResponseEditorProvider {
this.configLoader = configLoader; this.configLoader = configLoader;
this.httpUtils = new HttpUtils(api, configLoader); this.httpUtils = new HttpUtils(api, configLoader);
this.creationContext = creationContext; this.creationContext = creationContext;
this.messageProcessor = new MessageProcessor(api); this.messageProcessor = new MessageProcessor(api, configLoader);
} }
@Override @Override

View File

@@ -44,7 +44,7 @@ public class WebSocketEditor implements WebSocketMessageEditorProvider {
this.api = api; this.api = api;
this.configLoader = configLoader; this.configLoader = configLoader;
this.creationContext = creationContext; this.creationContext = creationContext;
this.messageProcessor = new MessageProcessor(api); this.messageProcessor = new MessageProcessor(api, configLoader);
} }
@Override @Override

View File

@@ -35,7 +35,7 @@ public class HttpMessageActiveHandler implements HttpHandler {
this.configLoader = configLoader; this.configLoader = configLoader;
this.httpUtils = new HttpUtils(api, configLoader); this.httpUtils = new HttpUtils(api, configLoader);
this.messageTableModel = messageTableModel; this.messageTableModel = messageTableModel;
this.messageProcessor = new MessageProcessor(api); this.messageProcessor = new MessageProcessor(api, configLoader);
} }
@Override @Override

View File

@@ -37,7 +37,7 @@ public class HttpMessagePassiveHandler implements ScanCheck {
this.configLoader = configLoader; this.configLoader = configLoader;
this.httpUtils = new HttpUtils(api, configLoader); this.httpUtils = new HttpUtils(api, configLoader);
this.messageTableModel = messageTableModel; this.messageTableModel = messageTableModel;
this.messageProcessor = new MessageProcessor(api); this.messageProcessor = new MessageProcessor(api, configLoader);
} }
@Override @Override

View File

@@ -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.requests.HttpRequest;
import burp.api.montoya.http.message.responses.HttpResponse; import burp.api.montoya.http.message.responses.HttpResponse;
import hae.Config; import hae.Config;
import hae.utils.ConfigLoader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
@@ -14,18 +15,16 @@ public class MessageProcessor {
private final MontoyaApi api; private final MontoyaApi api;
private final RegularMatcher regularMatcher; private final RegularMatcher regularMatcher;
private String finalColor = ""; public MessageProcessor(MontoyaApi api, ConfigLoader configLoader) {
public MessageProcessor(MontoyaApi api) {
this.api = api; this.api = api;
this.regularMatcher = new RegularMatcher(api); this.regularMatcher = new RegularMatcher(api, configLoader);
} }
public List<Map<String, String>> processMessage(String host, String message, boolean flag) { public List<Map<String, String>> processMessage(String host, String message, boolean flag) {
Map<String, Map<String, Object>> obj = null; Map<String, Map<String, Object>> obj = null;
try { try {
obj = regularMatcher.match(host, "any", message, message, message); obj = regularMatcher.performRegexMatching(host, "any", message, message, message);
} catch (Exception ignored) { } catch (Exception ignored) {
} }
@@ -40,9 +39,9 @@ public class MessageProcessor {
String body = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8); String body = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
String header = httpResponse.headers().stream() String header = httpResponse.headers().stream()
.map(HttpHeader::toString) .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) { } catch (Exception ignored) {
} }
@@ -57,9 +56,9 @@ public class MessageProcessor {
String body = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8); String body = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
String header = httpRequest.headers().stream() String header = httpRequest.headers().stream()
.map(HttpHeader::toString) .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) { } catch (Exception ignored) {
} }
@@ -137,41 +136,38 @@ public class MessageProcessor {
return indices; return indices;
} }
private void upgradeColors(List<Integer> colorList) { private String upgradeColors(List<Integer> colorList) {
int colorSize = colorList.size(); if (colorList == null || colorList.isEmpty()) {
String[] colorArray = Config.color; return Config.color[0];
colorList.sort(Comparator.comparingInt(Integer::intValue)); }
int i = 0;
List<Integer> stack = new ArrayList<>(); // 创建副本避免修改原始数据
while (i < colorSize) { List<Integer> indices = new ArrayList<>(colorList);
if (stack.isEmpty()) { indices.sort(Comparator.comparingInt(Integer::intValue));
stack.add(colorList.get(i));
} else { // 处理颜色升级
if (!Objects.equals(colorList.get(i), stack.stream().reduce((first, second) -> second).orElse(99999999))) { for (int i = 1; i < indices.size(); i++) {
stack.add(colorList.get(i)); if (indices.get(i).equals(indices.get(i - 1))) {
} else { // 如果发现重复的颜色索引,将当前索引降级
stack.set(stack.size() - 1, stack.get(stack.size() - 1) - 1); indices.set(i - 1, indices.get(i - 1) - 1);
} }
} }
i++;
} // 获取最终的颜色索引
// 利用HashSet删除重复元素 int finalIndex = indices.stream()
HashSet tmpList = new HashSet(stack); .min(Integer::compareTo)
if (stack.size() == tmpList.size()) { .orElse(0);
stack.sort(Comparator.comparingInt(Integer::intValue));
if (stack.get(0) < 0) { // 处理负数索引情况
finalColor = colorArray[0]; if (finalIndex < 0) {
} else { return Config.color[0];
finalColor = colorArray[stack.get(0)];
}
} else {
upgradeColors(stack);
} }
return Config.color[finalIndex];
} }
public String retrieveFinalColor(List<Integer> colorList) { public String retrieveFinalColor(List<Integer> colorList) {
upgradeColors(colorList); return upgradeColors(colorList);
return finalColor;
} }
} }

View File

@@ -8,7 +8,8 @@ import dk.brics.automaton.AutomatonMatcher;
import dk.brics.automaton.RegExp; import dk.brics.automaton.RegExp;
import dk.brics.automaton.RunAutomaton; import dk.brics.automaton.RunAutomaton;
import hae.Config; import hae.Config;
import hae.cache.MessageCache; import hae.cache.DataCache;
import hae.utils.ConfigLoader;
import hae.utils.DataManager; import hae.utils.DataManager;
import hae.utils.string.HashCalculator; import hae.utils.string.HashCalculator;
import hae.utils.string.StringProcessor; import hae.utils.string.StringProcessor;
@@ -20,14 +21,17 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class RegularMatcher { public class RegularMatcher {
private static final Map<String, Pattern> nfaPatternCache = new ConcurrentHashMap<>();
private static final Map<String, RunAutomaton> dfaAutomatonCache = new ConcurrentHashMap<>();
private final MontoyaApi api; private final MontoyaApi api;
private final ConfigLoader configLoader;
public RegularMatcher(MontoyaApi api) { public RegularMatcher(MontoyaApi api, ConfigLoader configLoader) {
this.api = api; this.api = api;
this.configLoader = configLoader;
} }
public synchronized static void putDataToGlobalMap(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) { public synchronized static void updateGlobalMatchCache(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) {
// 添加到全局变量中便于Databoard检索 // 添加到全局变量中便于Databoard检索
if (!Objects.equals(host, "") && host != null) { if (!Objects.equals(host, "") && host != null) {
Config.globalDataMap.compute(host, (existingHost, existingMap) -> { Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
@@ -74,18 +78,41 @@ public class RegularMatcher {
} }
} }
public Map<String, Map<String, Object>> match(String host, String type, String message, String header, String body) { public Map<String, Map<String, Object>> performRegexMatching(String host, String type, String message, String header, String body) {
// 先从缓存池里判断是否有已经匹配好的结果 // 删除动态响应头再进行存储
String originalMessage = message;
String dynamicHeader = configLoader.getDynamicHeader();
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()); String messageIndex = HashCalculator.calculateHash(message.getBytes());
Map<String, Map<String, Object>> map = MessageCache.get(messageIndex);
if (map != null) { // 从数据缓存中读取
return map; Map<String, Map<String, Object>> dataCacheMap = DataCache.get(messageIndex);
} else {
// 存在则返回
if (dataCacheMap != null) {
return dataCacheMap;
}
// 最终返回的结果 // 最终返回的结果
String firstLine = originalMessage.split("\\r?\\n", 2)[0];
Map<String, Map<String, Object>> finalMap = applyMatchingRules(host, type, originalMessage, firstLine, header, body);
// 数据缓存写入,有可能是空值,当作匹配过的索引不再匹配
DataCache.put(messageIndex, finalMap);
return finalMap;
}
private Map<String, Map<String, Object>> applyMatchingRules(String host, String type, String message, String firstLine, String header, String body) {
Map<String, Map<String, Object>> finalMap = new HashMap<>(); Map<String, Map<String, Object>> finalMap = new HashMap<>();
Config.globalRules.keySet().parallelStream().forEach(i -> { Config.globalRules.keySet().parallelStream().forEach(i -> {
for (Object[] objects : Config.globalRules.get(i)) { for (Object[] objects : Config.globalRules.get(i)) {
// 多线程执行,一定程度上减少阻塞现象
String matchContent = ""; String matchContent = "";
// 遍历获取规则 // 遍历获取规则
List<String> result; List<String> result;
@@ -103,6 +130,7 @@ public class RegularMatcher {
// 判断规则是否开启与作用域 // 判断规则是否开启与作用域
if (loaded && (scope.contains(type) || scope.contains("any") || type.equals("any"))) { if (loaded && (scope.contains(type) || scope.contains("any") || type.equals("any"))) {
// 在此处检查内容是否缓存,缓存则返回为空
switch (scope) { switch (scope) {
case "any": case "any":
case "request": case "request":
@@ -121,14 +149,19 @@ public class RegularMatcher {
break; break;
case "request line": case "request line":
case "response line": case "response line":
matchContent = message.split("\\r?\\n", 2)[0]; matchContent = firstLine;
break; break;
default: default:
break; break;
} }
// 匹配内容为空则跳出
if (matchContent.isBlank()) {
break;
}
try { try {
result = new ArrayList<>(matchByRegex(f_regex, s_regex, matchContent, format, engine, sensitive)); result = new ArrayList<>(executeRegexEngine(f_regex, s_regex, matchContent, format, engine, sensitive));
} catch (Exception e) { } catch (Exception e) {
api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex)); api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex));
api.logging().logToError(e.getMessage()); api.logging().logToError(e.getMessage());
@@ -148,21 +181,20 @@ public class RegularMatcher {
String nameAndSize = String.format("%s (%s)", name, result.size()); String nameAndSize = String.format("%s (%s)", name, result.size());
finalMap.put(nameAndSize, tmpMap); finalMap.put(nameAndSize, tmpMap);
putDataToGlobalMap(api, host, name, result, true); updateGlobalMatchCache(api, host, name, result, true);
} }
} }
} }
}); });
MessageCache.put(messageIndex, finalMap);
return finalMap; return finalMap;
} }
}
private List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) { private List<String> executeRegexEngine(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
List<String> retList = new ArrayList<>(); List<String> retList = new ArrayList<>();
if ("nfa".equals(engine)) { if ("nfa".equals(engine)) {
Matcher matcher = createPatternMatcher(f_regex, content, sensitive); Matcher matcher = createPatternMatcher(f_regex, content, sensitive);
retList.addAll(extractMatches(s_regex, format, sensitive, matcher)); retList.addAll(extractRegexMatchResults(s_regex, format, sensitive, matcher));
} else { } else {
// DFA不支持格式化输出因此不关注format // DFA不支持格式化输出因此不关注format
String newContent = content; String newContent = content;
@@ -172,44 +204,44 @@ public class RegularMatcher {
newFirstRegex = f_regex.toLowerCase(); newFirstRegex = f_regex.toLowerCase();
} }
AutomatonMatcher autoMatcher = createAutomatonMatcher(newFirstRegex, newContent); AutomatonMatcher autoMatcher = createAutomatonMatcher(newFirstRegex, newContent);
retList.addAll(extractMatches(s_regex, autoMatcher, content)); retList.addAll(extractRegexMatchResults(s_regex, autoMatcher, content));
} }
return retList; return retList;
} }
private List<String> extractMatches(String s_regex, String format, boolean sensitive, Matcher matcher) { private List<String> extractRegexMatchResults(String s_regex, String format, boolean sensitive, Matcher matcher) {
List<String> matches = new ArrayList<>(); List<String> matches = new ArrayList<>();
if (s_regex.isEmpty()) { if (s_regex.isEmpty()) {
matches.addAll(getFormatString(matcher, format)); matches.addAll(formatMatchResults(matcher, format));
} else { } else {
while (matcher.find()) { while (matcher.find()) {
String matchContent = matcher.group(1); String matchContent = matcher.group(1);
if (!matchContent.isEmpty()) { if (!matchContent.isEmpty()) {
matcher = createPatternMatcher(s_regex, matchContent, sensitive); matcher = createPatternMatcher(s_regex, matchContent, sensitive);
matches.addAll(getFormatString(matcher, format)); matches.addAll(formatMatchResults(matcher, format));
} }
} }
} }
return matches; return matches;
} }
private List<String> extractMatches(String s_regex, AutomatonMatcher autoMatcher, String content) { private List<String> extractRegexMatchResults(String s_regex, AutomatonMatcher autoMatcher, String content) {
List<String> matches = new ArrayList<>(); List<String> matches = new ArrayList<>();
if (s_regex.isEmpty()) { if (s_regex.isEmpty()) {
matches.addAll(getFormatString(autoMatcher, content)); matches.addAll(formatMatchResults(autoMatcher, content));
} else { } else {
while (autoMatcher.find()) { while (autoMatcher.find()) {
String s = autoMatcher.group(); String s = autoMatcher.group();
if (!s.isEmpty()) { if (!s.isEmpty()) {
autoMatcher = createAutomatonMatcher(s_regex, getSubString(content, s)); autoMatcher = createAutomatonMatcher(s_regex, extractMatchedContent(content, s));
matches.addAll(getFormatString(autoMatcher, content)); matches.addAll(formatMatchResults(autoMatcher, content));
} }
} }
} }
return matches; return matches;
} }
private List<String> getFormatString(Matcher matcher, String format) { private List<String> formatMatchResults(Matcher matcher, String format) {
List<Integer> indexList = parseIndexesFromString(format); List<Integer> indexList = parseIndexesFromString(format);
List<String> stringList = new ArrayList<>(); List<String> stringList = new ArrayList<>();
@@ -222,20 +254,20 @@ public class RegularMatcher {
return ""; return "";
}).toArray(); }).toArray();
stringList.add(MessageFormat.format(reorderIndex(format), params)); stringList.add(MessageFormat.format(normalizeFormatIndexes(format), params));
} }
} }
return stringList; return stringList;
} }
private List<String> getFormatString(AutomatonMatcher matcher, String content) { private List<String> formatMatchResults(AutomatonMatcher matcher, String content) {
List<String> stringList = new ArrayList<>(); List<String> stringList = new ArrayList<>();
while (matcher.find()) { while (matcher.find()) {
String s = matcher.group(0); String s = matcher.group(0);
if (!s.isEmpty()) { 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) { 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); return pattern.matcher(content);
} }
private AutomatonMatcher createAutomatonMatcher(String regex, String content) { private AutomatonMatcher createAutomatonMatcher(String regex, String content) {
RunAutomaton runAuto = dfaAutomatonCache.computeIfAbsent(regex, k -> {
RegExp regexp = new RegExp(regex); RegExp regexp = new RegExp(regex);
Automaton auto = regexp.toAutomaton(); Automaton auto = regexp.toAutomaton();
RunAutomaton runAuto = new RunAutomaton(auto, true); return new RunAutomaton(auto, true);
});
return runAuto.newMatcher(content); return runAuto.newMatcher(content);
} }
@@ -269,15 +308,16 @@ public class RegularMatcher {
return indexes; return indexes;
} }
private String getSubString(String content, String s) { private String extractMatchedContent(String content, String s) {
byte[] contentByte = api.utilities().byteUtils().convertFromString(content); byte[] contentByte = api.utilities().byteUtils().convertFromString(content);
byte[] sByte = api.utilities().byteUtils().convertFromString(s); byte[] sByte = api.utilities().byteUtils().convertFromString(s);
int startIndex = api.utilities().byteUtils().indexOf(contentByte, sByte, false, 1, contentByte.length); int startIndex = api.utilities().byteUtils().indexOf(contentByte, sByte, false, 1, contentByte.length);
int endIndex = startIndex + s.length(); int endIndex = startIndex + s.length();
return content.substring(startIndex, endIndex); return content.substring(startIndex, endIndex);
} }
private String reorderIndex(String format) { private String normalizeFormatIndexes(String format) {
Pattern pattern = Pattern.compile("\\{(\\d+)}"); Pattern pattern = Pattern.compile("\\{(\\d+)}");
Matcher matcher = pattern.matcher(format); Matcher matcher = pattern.matcher(format);
int count = 0; int count = 0;
@@ -287,6 +327,7 @@ public class RegularMatcher {
format = format.replace(matchStr, newStr); format = format.replace(matchStr, newStr);
count++; count++;
} }
return format; return format;
} }
} }

View File

@@ -4,6 +4,7 @@ import burp.api.montoya.MontoyaApi;
import burp.api.montoya.core.HighlightColor; import burp.api.montoya.core.HighlightColor;
import burp.api.montoya.proxy.websocket.*; import burp.api.montoya.proxy.websocket.*;
import hae.instances.http.utils.MessageProcessor; import hae.instances.http.utils.MessageProcessor;
import hae.utils.ConfigLoader;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -12,9 +13,9 @@ public class WebSocketMessageHandler implements ProxyMessageHandler {
private final MontoyaApi api; private final MontoyaApi api;
private final MessageProcessor messageProcessor; private final MessageProcessor messageProcessor;
public WebSocketMessageHandler(MontoyaApi api) { public WebSocketMessageHandler(MontoyaApi api, ConfigLoader configLoader) {
this.api = api; this.api = api;
this.messageProcessor = new MessageProcessor(api); this.messageProcessor = new MessageProcessor(api, configLoader);
} }
@Override @Override

View File

@@ -3,7 +3,9 @@ package hae.utils;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import hae.Config; import hae.Config;
import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.representer.Representer; import org.yaml.snakeyaml.representer.Representer;
import java.io.*; import java.io.*;
@@ -21,10 +23,7 @@ public class ConfigLoader {
public ConfigLoader(MontoyaApi api) { public ConfigLoader(MontoyaApi api) {
this.api = api; this.api = api;
DumperOptions dop = new DumperOptions(); this.yaml = createSecureYaml();
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Representer representer = new Representer(dop);
this.yaml = new Yaml(representer, dop);
String configPath = determineConfigPath(); String configPath = determineConfigPath();
this.configFilePath = String.format("%s/%s", configPath, "Config.yml"); this.configFilePath = String.format("%s/%s", configPath, "Config.yml");
@@ -54,6 +53,25 @@ public class ConfigLoader {
return configPathFile.exists() && configPathFile.isDirectory(); 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() { private String determineConfigPath() {
// 优先级1用户根目录 // 优先级1用户根目录
String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home")); String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home"));
@@ -79,6 +97,8 @@ public class ConfigLoader {
r.put("ExcludeStatus", getExcludeStatus()); r.put("ExcludeStatus", getExcludeStatus());
r.put("LimitSize", getLimitSize()); r.put("LimitSize", getLimitSize());
r.put("HaEScope", getScope()); r.put("HaEScope", getScope());
r.put("DynamicHeader", getDynamicHeader());
try { try {
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8); Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8);
yaml.dump(r, ws); yaml.dump(r, ws);
@@ -97,10 +117,7 @@ public class ConfigLoader {
try { try {
InputStream inputStream = Files.newInputStream(Paths.get(getRulesFilePath())); InputStream inputStream = Files.newInputStream(Paths.get(getRulesFilePath()));
DumperOptions dop = new DumperOptions(); Map<String, Object> rulesMap = yaml.load(inputStream);
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Representer representer = new Representer(dop);
Map<String, Object> rulesMap = new Yaml(representer, dop).load(inputStream);
Object rulesObj = rulesMap.get("rules"); Object rulesObj = rulesMap.get("rules");
if (rulesObj instanceof List) { if (rulesObj instanceof List) {
@@ -156,6 +173,14 @@ public class ConfigLoader {
setValueToConfig("ExcludeStatus", status); setValueToConfig("ExcludeStatus", status);
} }
public String getDynamicHeader() {
return getValueFromConfig("DynamicHeader", Config.header);
}
public void setDynamicHeader(String header) {
setValueToConfig("DynamicHeader", header);
}
public String getLimitSize() { public String getLimitSize() {
return getValueFromConfig("LimitSize", Config.size); return getValueFromConfig("LimitSize", Config.size);
} }

View File

@@ -64,7 +64,7 @@ public class DataManager {
dataIndex.forEach(index -> { dataIndex.forEach(index -> {
PersistedObject dataObj = persistence.extensionData().getChildObject(index); PersistedObject dataObj = persistence.extensionData().getChildObject(index);
try { 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) { } catch (Exception ignored) {
} }
}); });
@@ -114,7 +114,7 @@ public class DataManager {
} }
} }
} catch (Exception e) { } catch (Exception e) {
api.logging().logToError("处理消息数据时出错: " + e.getMessage() + ", index: " + index); api.logging().logToError("processBatch: " + e.getMessage());
} }
}); });
} }

View File

@@ -25,16 +25,29 @@ public class HttpUtils {
boolean retStatus = false; boolean retStatus = false;
try { try {
String host = StringProcessor.getHostByUrl(request.url()); String host = StringProcessor.getHostByUrl(request.url());
String[] hostList = configLoader.getBlockHost().split("\\|");
boolean isBlockHost = isBlockHost(hostList, host);
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<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|")); List<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
boolean isExcludeSuffix = suffixList.contains(request.fileExtension().toLowerCase()); isExcludeSuffix = suffixList.contains(request.fileExtension().toLowerCase());
}
boolean isToolScope = !configLoader.getScope().contains(toolType); boolean isToolScope = !configLoader.getScope().contains(toolType);
boolean isExcludeStatus = false;
String status = configLoader.getExcludeStatus();
if (!status.isBlank()) {
List<String> statusList = Arrays.asList(configLoader.getExcludeStatus().split("\\|")); List<String> statusList = Arrays.asList(configLoader.getExcludeStatus().split("\\|"));
boolean isExcludeStatus = statusList.contains(String.valueOf(response.statusCode())); isExcludeStatus = statusList.contains(String.valueOf(response.statusCode()));
}
retStatus = isExcludeSuffix || isBlockHost || isToolScope || isExcludeStatus; retStatus = isExcludeSuffix || isBlockHost || isToolScope || isExcludeStatus;
} catch (Exception ignored) { } catch (Exception ignored) {

View File

@@ -2,6 +2,7 @@ package hae.utils.rule;
import burp.api.montoya.MontoyaApi; import burp.api.montoya.MontoyaApi;
import hae.Config; import hae.Config;
import hae.cache.DataCache;
import hae.utils.ConfigLoader; import hae.utils.ConfigLoader;
import hae.utils.rule.model.Group; import hae.utils.rule.model.Group;
import hae.utils.rule.model.Info; import hae.utils.rule.model.Info;
@@ -27,6 +28,8 @@ public class RuleProcessor {
} }
public void rulesFormatAndSave() { public void rulesFormatAndSave() {
DataCache.clear();
DumperOptions dop = new DumperOptions(); DumperOptions dop = new DumperOptions();
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Representer representer = new Representer(dop); Representer representer = new Representer(dop);

View File

@@ -55,6 +55,15 @@ rules:
scope: response body scope: response body
engine: dfa engine: dfa
sensitive: false 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 - group: Maybe Vulnerability
rule: rule:
- name: Java Deserialization - name: Java Deserialization
@@ -102,6 +111,24 @@ rules:
scope: request scope: request
engine: dfa engine: dfa
sensitive: false 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 - group: Basic Information
rule: rule:
- name: Email - name: Email
@@ -244,7 +271,7 @@ rules:
rule: rule:
- name: Linkfinder - name: Linkfinder
loaded: true 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: '' s_regex: ''
format: '{0}' format: '{0}'
color: gray color: gray
@@ -271,7 +298,7 @@ rules:
sensitive: false sensitive: false
- name: URL Schemes - name: URL Schemes
loaded: true 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: '' s_regex: ''
format: '{0}' format: '{0}'
color: yellow color: yellow