Version: 4.2 Update
Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Map<String, String>> processMessage(String host, String message, boolean flag) {
|
||||
Map<String, Map<String, Object>> 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<Integer> colorList) {
|
||||
int colorSize = colorList.size();
|
||||
String[] colorArray = Config.color;
|
||||
colorList.sort(Comparator.comparingInt(Integer::intValue));
|
||||
int i = 0;
|
||||
List<Integer> 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<Integer> 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<Integer> 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<Integer> colorList) {
|
||||
upgradeColors(colorList);
|
||||
return finalColor;
|
||||
return upgradeColors(colorList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String, Pattern> nfaPatternCache = new ConcurrentHashMap<>();
|
||||
private static final Map<String, RunAutomaton> 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<String> dataList, boolean flag) {
|
||||
public synchronized static void updateGlobalMatchCache(MontoyaApi api, String host, String name, List<String> 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<String, Map<String, Object>> match(String host, String type, String message, String header, String body) {
|
||||
// 先从缓存池里判断是否有已经匹配好的结果
|
||||
String messageIndex = HashCalculator.calculateHash(message.getBytes());
|
||||
Map<String, Map<String, Object>> map = MessageCache.get(messageIndex);
|
||||
if (map != null) {
|
||||
return map;
|
||||
} else {
|
||||
// 最终返回的结果
|
||||
Map<String, Map<String, Object>> finalMap = new HashMap<>();
|
||||
Config.globalRules.keySet().parallelStream().forEach(i -> {
|
||||
for (Object[] objects : Config.globalRules.get(i)) {
|
||||
// 多线程执行,一定程度上减少阻塞现象
|
||||
String matchContent = "";
|
||||
// 遍历获取规则
|
||||
List<String> result;
|
||||
Map<String, Object> tmpMap = new HashMap<>();
|
||||
public Map<String, Map<String, Object>> 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<String, Map<String, Object>> dataCacheMap = DataCache.get(messageIndex);
|
||||
|
||||
// 存在则返回
|
||||
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 List<String> matchByRegex(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
||||
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<>();
|
||||
|
||||
Config.globalRules.keySet().parallelStream().forEach(i -> {
|
||||
for (Object[] objects : Config.globalRules.get(i)) {
|
||||
String matchContent = "";
|
||||
// 遍历获取规则
|
||||
List<String> result;
|
||||
Map<String, Object> 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<String> executeRegexEngine(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
||||
List<String> 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<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<>();
|
||||
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<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<>();
|
||||
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<String> getFormatString(Matcher matcher, String format) {
|
||||
private List<String> formatMatchResults(Matcher matcher, String format) {
|
||||
List<Integer> indexList = parseIndexesFromString(format);
|
||||
List<String> 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<String> getFormatString(AutomatonMatcher matcher, String content) {
|
||||
private List<String> formatMatchResults(AutomatonMatcher matcher, String content) {
|
||||
List<String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String, Object> rulesMap = new Yaml(representer, dop).load(inputStream);
|
||||
Map<String, Object> 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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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<String> 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<String> suffixList = Arrays.asList(configLoader.getExcludeSuffix().split("\\|"));
|
||||
isExcludeSuffix = suffixList.contains(request.fileExtension().toLowerCase());
|
||||
}
|
||||
|
||||
boolean isToolScope = !configLoader.getScope().contains(toolType);
|
||||
|
||||
List<String> 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<String> statusList = Arrays.asList(configLoader.getExcludeStatus().split("\\|"));
|
||||
isExcludeStatus = statusList.contains(String.valueOf(response.statusCode()));
|
||||
}
|
||||
|
||||
retStatus = isExcludeSuffix || isBlockHost || isToolScope || isExcludeStatus;
|
||||
} catch (Exception ignored) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user