2024-05-06 12:56:56 +08:00
|
|
|
|
package hae.instances.http.utils;
|
|
|
|
|
|
|
|
|
|
|
|
import burp.api.montoya.MontoyaApi;
|
2024-12-21 15:34:45 +08:00
|
|
|
|
import burp.api.montoya.persistence.PersistedList;
|
|
|
|
|
|
import burp.api.montoya.persistence.PersistedObject;
|
2023-10-12 21:38:27 +08:00
|
|
|
|
import dk.brics.automaton.Automaton;
|
|
|
|
|
|
import dk.brics.automaton.AutomatonMatcher;
|
|
|
|
|
|
import dk.brics.automaton.RegExp;
|
|
|
|
|
|
import dk.brics.automaton.RunAutomaton;
|
2024-05-06 12:56:56 +08:00
|
|
|
|
import hae.Config;
|
2025-05-06 11:24:17 +08:00
|
|
|
|
import hae.cache.DataCache;
|
|
|
|
|
|
import hae.utils.ConfigLoader;
|
2024-12-21 15:34:45 +08:00
|
|
|
|
import hae.utils.DataManager;
|
2024-05-06 12:56:56 +08:00
|
|
|
|
import hae.utils.string.HashCalculator;
|
|
|
|
|
|
import hae.utils.string.StringProcessor;
|
|
|
|
|
|
|
2024-02-02 19:07:03 +08:00
|
|
|
|
import java.text.MessageFormat;
|
2023-10-18 00:51:01 +08:00
|
|
|
|
import java.util.*;
|
2023-11-27 14:55:28 +08:00
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
2024-05-23 12:00:13 +08:00
|
|
|
|
import java.util.regex.Matcher;
|
|
|
|
|
|
import java.util.regex.Pattern;
|
2023-10-12 21:38:27 +08:00
|
|
|
|
|
2024-05-06 12:56:56 +08:00
|
|
|
|
public class RegularMatcher {
|
2025-05-06 11:24:17 +08:00
|
|
|
|
private static final Map<String, Pattern> nfaPatternCache = new ConcurrentHashMap<>();
|
|
|
|
|
|
private static final Map<String, RunAutomaton> dfaAutomatonCache = new ConcurrentHashMap<>();
|
2024-05-06 12:56:56 +08:00
|
|
|
|
private final MontoyaApi api;
|
2025-05-06 11:24:17 +08:00
|
|
|
|
private final ConfigLoader configLoader;
|
2024-05-06 12:56:56 +08:00
|
|
|
|
|
2025-05-06 11:24:17 +08:00
|
|
|
|
public RegularMatcher(MontoyaApi api, ConfigLoader configLoader) {
|
2024-05-06 12:56:56 +08:00
|
|
|
|
this.api = api;
|
2025-05-06 11:24:17 +08:00
|
|
|
|
this.configLoader = configLoader;
|
2023-10-12 21:38:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-06 11:24:17 +08:00
|
|
|
|
public synchronized static void updateGlobalMatchCache(MontoyaApi api, String host, String name, List<String> dataList, boolean flag) {
|
2025-03-21 21:22:11 +08:00
|
|
|
|
// 添加到全局变量中,便于Databoard检索
|
|
|
|
|
|
if (!Objects.equals(host, "") && host != null) {
|
|
|
|
|
|
Config.globalDataMap.compute(host, (existingHost, existingMap) -> {
|
|
|
|
|
|
Map<String, List<String>> gRuleMap = Optional.ofNullable(existingMap).orElse(new ConcurrentHashMap<>());
|
|
|
|
|
|
|
|
|
|
|
|
gRuleMap.merge(name, new ArrayList<>(dataList), (existingList, newList) -> {
|
|
|
|
|
|
Set<String> combinedSet = new LinkedHashSet<>(existingList);
|
|
|
|
|
|
combinedSet.addAll(newList);
|
|
|
|
|
|
return new ArrayList<>(combinedSet);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (flag) {
|
|
|
|
|
|
// 数据存储在BurpSuite空间内
|
|
|
|
|
|
try {
|
|
|
|
|
|
DataManager dataManager = new DataManager(api);
|
|
|
|
|
|
PersistedObject persistedObject = PersistedObject.persistedObject();
|
|
|
|
|
|
gRuleMap.forEach((kName, vList) -> {
|
|
|
|
|
|
PersistedList<String> persistedList = PersistedList.persistedStringList();
|
|
|
|
|
|
persistedList.addAll(vList);
|
|
|
|
|
|
persistedObject.setStringList(kName, persistedList);
|
|
|
|
|
|
});
|
|
|
|
|
|
dataManager.putData("data", host, persistedObject);
|
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return gRuleMap;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
String[] splitHost = host.split("\\.");
|
|
|
|
|
|
String onlyHost = host.split(":")[0];
|
|
|
|
|
|
|
|
|
|
|
|
String anyHost = (splitHost.length > 2 && !StringProcessor.matchHostIsIp(onlyHost)) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
|
|
|
|
|
|
|
|
|
|
|
|
if (!Config.globalDataMap.containsKey(anyHost) && !anyHost.isEmpty()) {
|
|
|
|
|
|
// 添加通配符Host,实际数据从查询哪里将所有数据提取
|
|
|
|
|
|
Config.globalDataMap.put(anyHost, new HashMap<>());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!Config.globalDataMap.containsKey("*")) {
|
|
|
|
|
|
// 添加通配符全匹配,同上
|
|
|
|
|
|
Config.globalDataMap.put("*", new HashMap<>());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-06 11:24:17 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-06 12:56:56 +08:00
|
|
|
|
String messageIndex = HashCalculator.calculateHash(message.getBytes());
|
2025-05-06 11:24:17 +08:00
|
|
|
|
|
|
|
|
|
|
// 从数据缓存中读取
|
|
|
|
|
|
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 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);
|
2023-10-12 21:38:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-06 11:24:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return finalMap;
|
2024-02-02 19:07:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-06 11:24:17 +08:00
|
|
|
|
private List<String> executeRegexEngine(String f_regex, String s_regex, String content, String format, String engine, boolean sensitive) {
|
2024-02-02 19:07:03 +08:00
|
|
|
|
List<String> retList = new ArrayList<>();
|
|
|
|
|
|
if ("nfa".equals(engine)) {
|
|
|
|
|
|
Matcher matcher = createPatternMatcher(f_regex, content, sensitive);
|
2025-05-06 11:24:17 +08:00
|
|
|
|
retList.addAll(extractRegexMatchResults(s_regex, format, sensitive, matcher));
|
2024-02-02 19:07:03 +08:00
|
|
|
|
} else {
|
2024-03-22 15:34:14 +08:00
|
|
|
|
// DFA不支持格式化输出,因此不关注format
|
2024-02-02 19:07:03 +08:00
|
|
|
|
String newContent = content;
|
|
|
|
|
|
String newFirstRegex = f_regex;
|
|
|
|
|
|
if (!sensitive) {
|
|
|
|
|
|
newContent = content.toLowerCase();
|
|
|
|
|
|
newFirstRegex = f_regex.toLowerCase();
|
|
|
|
|
|
}
|
|
|
|
|
|
AutomatonMatcher autoMatcher = createAutomatonMatcher(newFirstRegex, newContent);
|
2025-05-06 11:24:17 +08:00
|
|
|
|
retList.addAll(extractRegexMatchResults(s_regex, autoMatcher, content));
|
2024-02-02 19:07:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
return retList;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-06 11:24:17 +08:00
|
|
|
|
private List<String> extractRegexMatchResults(String s_regex, String format, boolean sensitive, Matcher matcher) {
|
2024-02-02 19:07:03 +08:00
|
|
|
|
List<String> matches = new ArrayList<>();
|
|
|
|
|
|
if (s_regex.isEmpty()) {
|
2025-05-06 11:24:17 +08:00
|
|
|
|
matches.addAll(formatMatchResults(matcher, format));
|
2024-02-02 19:07:03 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
while (matcher.find()) {
|
2024-03-22 15:34:14 +08:00
|
|
|
|
String matchContent = matcher.group(1);
|
|
|
|
|
|
if (!matchContent.isEmpty()) {
|
|
|
|
|
|
matcher = createPatternMatcher(s_regex, matchContent, sensitive);
|
2025-05-06 11:24:17 +08:00
|
|
|
|
matches.addAll(formatMatchResults(matcher, format));
|
2024-03-22 15:34:14 +08:00
|
|
|
|
}
|
2024-02-02 19:07:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return matches;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-06 11:24:17 +08:00
|
|
|
|
private List<String> extractRegexMatchResults(String s_regex, AutomatonMatcher autoMatcher, String content) {
|
2024-02-02 19:07:03 +08:00
|
|
|
|
List<String> matches = new ArrayList<>();
|
|
|
|
|
|
if (s_regex.isEmpty()) {
|
2025-05-06 11:24:17 +08:00
|
|
|
|
matches.addAll(formatMatchResults(autoMatcher, content));
|
2024-02-02 19:07:03 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
while (autoMatcher.find()) {
|
2024-03-22 15:34:14 +08:00
|
|
|
|
String s = autoMatcher.group();
|
|
|
|
|
|
if (!s.isEmpty()) {
|
2025-05-06 11:24:17 +08:00
|
|
|
|
autoMatcher = createAutomatonMatcher(s_regex, extractMatchedContent(content, s));
|
|
|
|
|
|
matches.addAll(formatMatchResults(autoMatcher, content));
|
2024-03-22 15:34:14 +08:00
|
|
|
|
}
|
2024-02-02 19:07:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return matches;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-06 11:24:17 +08:00
|
|
|
|
private List<String> formatMatchResults(Matcher matcher, String format) {
|
2024-02-02 19:07:03 +08:00
|
|
|
|
List<Integer> indexList = parseIndexesFromString(format);
|
|
|
|
|
|
List<String> stringList = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
while (matcher.find()) {
|
2024-03-22 15:34:14 +08:00
|
|
|
|
if (!matcher.group(1).isEmpty()) {
|
|
|
|
|
|
Object[] params = indexList.stream().map(i -> {
|
2024-05-12 19:02:38 +08:00
|
|
|
|
if (!matcher.group(i + 1).isEmpty()) {
|
|
|
|
|
|
return matcher.group(i + 1);
|
2024-03-22 15:34:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
return "";
|
|
|
|
|
|
}).toArray();
|
|
|
|
|
|
|
2025-05-06 11:24:17 +08:00
|
|
|
|
stringList.add(MessageFormat.format(normalizeFormatIndexes(format), params));
|
2024-03-22 15:34:14 +08:00
|
|
|
|
}
|
2024-02-02 19:07:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return stringList;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-06 11:24:17 +08:00
|
|
|
|
private List<String> formatMatchResults(AutomatonMatcher matcher, String content) {
|
2024-02-02 19:07:03 +08:00
|
|
|
|
List<String> stringList = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
while (matcher.find()) {
|
2024-03-22 15:34:14 +08:00
|
|
|
|
String s = matcher.group(0);
|
|
|
|
|
|
if (!s.isEmpty()) {
|
2025-05-06 11:24:17 +08:00
|
|
|
|
stringList.add(extractMatchedContent(content, s));
|
2024-03-22 15:34:14 +08:00
|
|
|
|
}
|
2024-02-02 19:07:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return stringList;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Matcher createPatternMatcher(String regex, String content, boolean sensitive) {
|
2025-05-06 11:24:17 +08:00
|
|
|
|
Pattern pattern = nfaPatternCache.computeIfAbsent(regex, k -> {
|
|
|
|
|
|
int flags = sensitive ? 0 : Pattern.CASE_INSENSITIVE;
|
|
|
|
|
|
return Pattern.compile(regex, flags);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2024-02-02 19:07:03 +08:00
|
|
|
|
return pattern.matcher(content);
|
|
|
|
|
|
}
|
2023-10-12 21:38:27 +08:00
|
|
|
|
|
2024-02-02 19:07:03 +08:00
|
|
|
|
private AutomatonMatcher createAutomatonMatcher(String regex, String content) {
|
2025-05-06 11:24:17 +08:00
|
|
|
|
RunAutomaton runAuto = dfaAutomatonCache.computeIfAbsent(regex, k -> {
|
|
|
|
|
|
RegExp regexp = new RegExp(regex);
|
|
|
|
|
|
Automaton auto = regexp.toAutomaton();
|
|
|
|
|
|
return new RunAutomaton(auto, true);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2024-02-02 19:07:03 +08:00
|
|
|
|
return runAuto.newMatcher(content);
|
2023-10-12 21:38:27 +08:00
|
|
|
|
}
|
2024-02-02 19:07:03 +08:00
|
|
|
|
|
|
|
|
|
|
private LinkedList<Integer> parseIndexesFromString(String input) {
|
|
|
|
|
|
LinkedList<Integer> indexes = new LinkedList<>();
|
2024-05-23 12:00:13 +08:00
|
|
|
|
Pattern pattern = Pattern.compile("\\{(\\d+)}");
|
2024-02-02 19:07:03 +08:00
|
|
|
|
Matcher matcher = pattern.matcher(input);
|
|
|
|
|
|
|
|
|
|
|
|
while (matcher.find()) {
|
2024-03-22 15:34:14 +08:00
|
|
|
|
String index = matcher.group(1);
|
|
|
|
|
|
if (!index.isEmpty()) {
|
|
|
|
|
|
indexes.add(Integer.valueOf(index));
|
|
|
|
|
|
}
|
2024-02-02 19:07:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return indexes;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-06 11:24:17 +08:00
|
|
|
|
private String extractMatchedContent(String content, String s) {
|
2024-05-06 12:56:56 +08:00
|
|
|
|
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);
|
2024-02-02 19:07:03 +08:00
|
|
|
|
int endIndex = startIndex + s.length();
|
2025-05-06 11:24:17 +08:00
|
|
|
|
|
2024-02-02 19:07:03 +08:00
|
|
|
|
return content.substring(startIndex, endIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-06 11:24:17 +08:00
|
|
|
|
private String normalizeFormatIndexes(String format) {
|
2024-05-23 12:00:13 +08:00
|
|
|
|
Pattern pattern = Pattern.compile("\\{(\\d+)}");
|
2024-02-02 19:07:03 +08:00
|
|
|
|
Matcher matcher = pattern.matcher(format);
|
|
|
|
|
|
int count = 0;
|
|
|
|
|
|
while (matcher.find()) {
|
|
|
|
|
|
String newStr = String.format("{%s}", count);
|
|
|
|
|
|
String matchStr = matcher.group(0);
|
|
|
|
|
|
format = format.replace(matchStr, newStr);
|
|
|
|
|
|
count++;
|
|
|
|
|
|
}
|
2025-05-06 11:24:17 +08:00
|
|
|
|
|
2024-02-02 19:07:03 +08:00
|
|
|
|
return format;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|