Version: 3.0 Update
This commit is contained in:
16
README.md
16
README.md
@@ -6,11 +6,14 @@
|
||||
|
||||
## 项目介绍
|
||||
|
||||
**HaE**是一个基于`BurpSuite Java插件API`开发的辅助型框架式插件,旨在实现对HTTP消息的高亮标记和信息提取。该插件通过自定义正则表达式匹配响应报文或请求报文,并对匹配成功的报文进行标记和提取。
|
||||
**HaE**是一款网络安全(数据安全)领域下的辅助型框架式项目,旨在实现对HTTP消息(包含WebSocket)的高亮标记和信息提取。本项目通过自定义正则表达式匹配响应报文或请求报文,并对匹配成功的报文进行标记和提取。
|
||||
|
||||
随着现代化Web应用采用前后端分离的开发模式,日常漏洞挖掘的过程中,捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用,会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**,借助HaE,您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。
|
||||
> 随着现代化Web应用采用前后端分离的开发模式,日常漏洞挖掘的过程中,捕获的HTTP请求流量也相应增加。若想全面评估一个Web应用,会花费大量时间在无用的报文上。**HaE的出现旨在解决这类情况**,借助HaE,您能够**有效减少**测试时间,将更多精力集中在**有价值且有意义**的报文上,从而**提高漏洞挖掘效率**。
|
||||
|
||||
**注**: 要想灵活的使用`HaE`,你需要掌握正则表达式阅读、编写、修改能力;由于`Java`正则表达式的库并没有`Python`的优雅或方便,所以HaE要求使用者必须用`()`将所需提取的表达式内容包含;例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,如果你要提取这段内容的话就需要变成`(rememberMe=delete)`。
|
||||
**注意事项**:
|
||||
|
||||
1. 由于HaE 3.0版本开始采用`Montoya API`进行开发,因此使用新版HaE需要升级你的BurpSuite版本(>=2023.12.1)。
|
||||
2. 自定义HaE规则必须用左右括号`()`将所需提取的表达式内容包含,例如你要匹配一个**Shiro应用**的响应报文,正常匹配规则为`rememberMe=delete`,在HaE的规则中就需要变成`(rememberMe=delete)`。
|
||||
|
||||
## 使用方法
|
||||
|
||||
@@ -54,9 +57,10 @@ HaE目前的规则一共有8个字段,分别是规则名称、规则正则、
|
||||
|
||||
| 界面名称 | 界面展示 |
|
||||
| ------------------------ | ---------------------------------------------------- |
|
||||
| Rules(规则信息管理) | <img src="images/rules.png" style="width: 80%" /> |
|
||||
| Config(配置信息管理) | <img src="images/config.png" style="width: 80%" /> |
|
||||
| Databoard(数据集合面板) | <img src="images/databoard.png" style="width: 80%" /> |
|
||||
| Rules(规则管理) | <img src="images/rules.png" style="width: 80%" /> |
|
||||
| Config(配置管理) | <img src="images/config.png" style="width: 80%" /> |
|
||||
| Databoard(数据集合) | <img src="images/databoard.png" style="width: 80%" /> |
|
||||
| MarkInfo(数据展示) | <img src="images/markinfo.png" style="width: 80%" /> |
|
||||
|
||||
## 文末随笔
|
||||
|
||||
|
||||
70
build.gradle
70
build.gradle
@@ -1,33 +1,37 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir './src/main/java'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task fatJar(type: Jar) {
|
||||
baseName = project.name + '-all'
|
||||
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
|
||||
with jar
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'net.portswigger.burp.extender:burp-extender-api:1.7.13'
|
||||
compile 'org.jetbrains:annotations:16.0.2'
|
||||
compile group: 'org.yaml', name: 'snakeyaml', version: '1.28'
|
||||
compile 'net.sourceforge.jregex:jregex:1.2_01'
|
||||
compile 'dk.brics.automaton:automaton:1.11-8'
|
||||
}
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
sourceCompatibility = 17
|
||||
targetCompatibility = 17
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir './src/main/java'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'net.portswigger.burp.extensions:montoya-api:2023.12.1'
|
||||
implementation 'org.yaml:snakeyaml:2.0'
|
||||
implementation 'net.sourceforge.jregex:jregex:1.2_01'
|
||||
implementation 'dk.brics.automaton:automaton:1.11-8'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
jar {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
|
||||
from {
|
||||
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 666 KiB After Width: | Height: | Size: 328 KiB |
BIN
images/markinfo.png
Normal file
BIN
images/markinfo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 270 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 120 KiB |
@@ -1,245 +0,0 @@
|
||||
package burp;
|
||||
|
||||
import burp.config.ConfigLoader;
|
||||
import burp.core.processor.ColorProcessor;
|
||||
import burp.core.processor.MessageProcessor;
|
||||
import burp.core.utils.StringHelper;
|
||||
import burp.ui.MainUI;
|
||||
import burp.ui.board.DatatablePanel;
|
||||
import burp.ui.board.MessagePanel;
|
||||
import java.util.*;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
/**
|
||||
* @author EvilChen & 0chencc
|
||||
*/
|
||||
|
||||
public class BurpExtender implements IBurpExtender, IHttpListener, IMessageEditorTabFactory, ITab {
|
||||
private MainUI main;
|
||||
public static PrintWriter stdout;
|
||||
public static IBurpExtenderCallbacks callbacks;
|
||||
public static IExtensionHelpers helpers;
|
||||
ColorProcessor colorProcessor = new ColorProcessor();
|
||||
MessageProcessor messageProcessor = new MessageProcessor();
|
||||
private MessagePanel messagePanel;
|
||||
|
||||
@Override
|
||||
public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks)
|
||||
{
|
||||
BurpExtender.callbacks = callbacks;
|
||||
BurpExtender.helpers = callbacks.getHelpers();
|
||||
|
||||
new ConfigLoader();
|
||||
|
||||
String version = "2.6.1";
|
||||
callbacks.setExtensionName(String.format("HaE (%s) - Highlighter and Extractor", version));
|
||||
|
||||
// 定义输出
|
||||
stdout = new PrintWriter(callbacks.getStdout(), true);
|
||||
stdout.println("[ HACK THE WORLD - TO DO IT ]");
|
||||
stdout.println("[#] Author: EvilChen & 0chencc");
|
||||
stdout.println("[#] Github: https://github.com/gh0stkey/HaE");
|
||||
|
||||
// UI
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
initialize();
|
||||
}
|
||||
});
|
||||
|
||||
callbacks.registerHttpListener(BurpExtender.this);
|
||||
callbacks.registerMessageEditorTabFactory(BurpExtender.this);
|
||||
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
messagePanel = new MessagePanel(callbacks, helpers);
|
||||
main = new MainUI(messagePanel);
|
||||
callbacks.customizeUiComponent(main);
|
||||
callbacks.addSuiteTab(BurpExtender.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTabCaption() {
|
||||
return "HaE";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getUiComponent() {
|
||||
return main;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用processHttpMessage用来做Highlighter
|
||||
*/
|
||||
@Override
|
||||
public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {
|
||||
// 判断是否是响应,且该代码作用域为:REPEATER、INTRUDER、PROXY(分别对应toolFlag 64、32、4)
|
||||
if (toolFlag == 64 || toolFlag == 32 || toolFlag == 4) {
|
||||
if (!messageIsRequest) {
|
||||
IHttpService iHttpService = messageInfo.getHttpService();
|
||||
String host = iHttpService.getHost();
|
||||
|
||||
List<Map<String, String>> result = null;
|
||||
|
||||
String originalColor = messageInfo.getHighlight();
|
||||
String originalComment = messageInfo.getComment();
|
||||
|
||||
try {
|
||||
result = messageProcessor.processMessage(helpers, messageInfo, host, true);
|
||||
|
||||
if (result != null && !result.isEmpty() && result.size() > 0) {
|
||||
List<String> colorList = new ArrayList<>();
|
||||
|
||||
if (originalColor != null) {
|
||||
colorList.add(originalColor);
|
||||
}
|
||||
|
||||
colorList.add(result.get(0).get("color"));
|
||||
String resColor = colorProcessor.retrieveFinalColor(colorProcessor.retrieveColorIndices(colorList));
|
||||
messageInfo.setHighlight(resColor);
|
||||
|
||||
String addComment = String.join(", ", result.get(1).get("comment"));
|
||||
String allComment = !Objects.equals(originalComment, "") ? String.format("%s, %s", originalComment, addComment) : addComment;
|
||||
String resComment = StringHelper.mergeComment(allComment);
|
||||
messageInfo.setComment(resComment);
|
||||
|
||||
messagePanel.add(messageInfo, resComment, resColor);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class MarkInfoTab implements IMessageEditorTab {
|
||||
private final JTabbedPane jTabbedPane = new JTabbedPane();
|
||||
private DatatablePanel dataPanel;
|
||||
private JTable dataTable;
|
||||
private final IMessageEditorController controller;
|
||||
private Map<String, String> extractRequestMap;
|
||||
private Map<String, String> extractResponseMap;
|
||||
private ArrayList<String> titleList = new ArrayList<>();
|
||||
private byte[] message;
|
||||
|
||||
public MarkInfoTab(IMessageEditorController controller, boolean editable) {
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTabCaption() {
|
||||
return "MarkInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getUiComponent() {
|
||||
jTabbedPane.addChangeListener(new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent arg0) {
|
||||
dataTable = ((DatatablePanel)jTabbedPane.getSelectedComponent()).getTable();
|
||||
}
|
||||
});
|
||||
return jTabbedPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(byte[] content, boolean isRequest) {
|
||||
this.message = content;
|
||||
List<Map<String, String>> result = null;
|
||||
if (content.length != 0 && !helpers.bytesToString(content).equals("Loading...")) {
|
||||
try {
|
||||
if (isRequest) {
|
||||
result = messageProcessor.processRequestMessage(helpers, content, "", false);
|
||||
} else {
|
||||
result = messageProcessor.processResponseMessage(helpers, content, "", false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (result != null && !result.isEmpty()) {
|
||||
Map<String, String> dataMap = result.get(0);
|
||||
if (isRequest) {
|
||||
extractRequestMap = dataMap;
|
||||
} else {
|
||||
extractResponseMap = dataMap;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷键复制功能
|
||||
*/
|
||||
@Override
|
||||
public byte[] getSelectedData() {
|
||||
return helpers.stringToBytes(dataPanel.getSelectedData(dataTable));
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用setMessage用来做Extractor
|
||||
*/
|
||||
@Override
|
||||
public void setMessage(byte[] content, boolean isRequest) {
|
||||
if (content.length > 0) {
|
||||
if (isRequest) {
|
||||
makeTable(extractRequestMap);
|
||||
} else {
|
||||
makeTable(extractResponseMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建MarkInfo表单
|
||||
*/
|
||||
public void makeTable(Map<String, String> dataMap) {
|
||||
ArrayList<String> lTitleList = new ArrayList<>();
|
||||
|
||||
dataMap.keySet().forEach(i->{
|
||||
String[] extractData = dataMap.get(i).split("\n");
|
||||
lTitleList.add(i);
|
||||
dataPanel = new DatatablePanel(i, Arrays.asList(extractData));
|
||||
jTabbedPane.addTab(i, dataPanel);
|
||||
});
|
||||
|
||||
/*
|
||||
* 使用removeAll会导致MarkInfo UI出现空白的情况,为了改善用户侧体验,采用remove的方式进行删除;
|
||||
* 采用全局ArrayList的方式遍历删除Tab,以此应对BurpSuite缓存机制导致的MarkInfo UI错误展示。
|
||||
*/
|
||||
titleList.forEach(t->{
|
||||
int indexOfTab = jTabbedPane.indexOfTab(t);
|
||||
if (indexOfTab != -1) {
|
||||
jTabbedPane.removeTabAt(indexOfTab);
|
||||
}
|
||||
});
|
||||
|
||||
titleList = lTitleList;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMessageEditorTab createNewInstance(IMessageEditorController controller, boolean editable) {
|
||||
return new MarkInfoTab(controller, editable);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package burp.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ConfigEntry {
|
||||
public static String excludeSuffix = "3g2|3gp|7z|aac|abw|aif|aifc|aiff|apk|arc|au|avi|azw|bat|bin|bmp|bz|bz2|cmd|cmx|cod|com|csh|css|csv|dll|doc|docx|ear|eot|epub|exe|flac|flv|gif|gz|ico|ics|ief|jar|jfif|jpe|jpeg|jpg|less|m3u|mid|midi|mjs|mkv|mov|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpkg|mpp|mpv2|odp|ods|odt|oga|ogg|ogv|ogx|otf|pbm|pdf|pgm|png|pnm|ppm|ppt|pptx|ra|ram|rar|ras|rgb|rmi|rtf|scss|sh|snd|svg|swf|tar|tif|tiff|ttf|vsd|war|wav|weba|webm|webp|wmv|woff|woff2|xbm|xls|xlsx|xpm|xul|xwd|zip";
|
||||
|
||||
public static String[] scopeArray = new String[] {
|
||||
"any",
|
||||
"any header",
|
||||
"any body",
|
||||
"response",
|
||||
"response header",
|
||||
"response body",
|
||||
"request",
|
||||
"request header",
|
||||
"request body"
|
||||
};
|
||||
|
||||
public static String[] engineArray = new String[] {
|
||||
"nfa",
|
||||
"dfa"
|
||||
};
|
||||
|
||||
public static String[] colorArray = new String[] {
|
||||
"red",
|
||||
"orange",
|
||||
"yellow",
|
||||
"green",
|
||||
"cyan",
|
||||
"blue",
|
||||
"pink",
|
||||
"magenta",
|
||||
"gray"
|
||||
};
|
||||
|
||||
public static Map<String,Object[][]> globalRules = null;
|
||||
|
||||
public static ConcurrentHashMap<String, Map<String, List<String>>> globalDataMap = new ConcurrentHashMap<>();
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package burp.config;
|
||||
|
||||
import burp.BurpExtender;
|
||||
import burp.rule.utils.RuleTool;
|
||||
import burp.rule.utils.YamlTool;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
/**
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class ConfigLoader {
|
||||
private static final Yaml yaml = YamlTool.newStandardYaml();
|
||||
private static final String HaEConfigPath = determineConfigPath();
|
||||
private static final String RulesFilePath = String.format("%s/%s", HaEConfigPath, "Rules.yml");
|
||||
private static final String ConfigFilePath = String.format("%s/%s", HaEConfigPath, "Config.yml");
|
||||
|
||||
public ConfigLoader() {
|
||||
// 构造函数,初始化配置
|
||||
File HaEConfigPathFile = new File(HaEConfigPath);
|
||||
if (!(HaEConfigPathFile.exists() && HaEConfigPathFile.isDirectory())) {
|
||||
HaEConfigPathFile.mkdirs();
|
||||
}
|
||||
|
||||
File configFilePath = new File(ConfigFilePath);
|
||||
if (!(configFilePath.exists() && configFilePath.isFile())) {
|
||||
initConfig();
|
||||
}
|
||||
|
||||
File rulesFilePath = new File(RulesFilePath);
|
||||
if (!(rulesFilePath.exists() && rulesFilePath.isFile())) {
|
||||
initRules();
|
||||
}
|
||||
|
||||
ConfigEntry.globalRules = getRules();
|
||||
}
|
||||
|
||||
private static String determineConfigPath() {
|
||||
// 优先级1:用户根目录
|
||||
String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home"));
|
||||
if (isValidConfigPath(userConfigPath)) {
|
||||
return userConfigPath;
|
||||
}
|
||||
|
||||
// 优先级2:Jar包所在目录
|
||||
String jarPath = BurpExtender.callbacks.getExtensionFilename();
|
||||
String jarDirectory = new File(jarPath).getParent();
|
||||
String jarConfigPath = String.format("%s/.config/HaE", jarDirectory);
|
||||
if (isValidConfigPath(jarConfigPath)) {
|
||||
return jarConfigPath;
|
||||
}
|
||||
|
||||
return userConfigPath;
|
||||
}
|
||||
|
||||
private static boolean isValidConfigPath(String configPath) {
|
||||
File configPathFile = new File(configPath);
|
||||
return configPathFile.exists() && configPathFile.isDirectory();
|
||||
}
|
||||
|
||||
public static void initConfig() {
|
||||
Map<String, Object> r = new LinkedHashMap<>();
|
||||
r.put("excludeSuffix", getExcludeSuffix());
|
||||
try {
|
||||
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(ConfigFilePath)), StandardCharsets.UTF_8);
|
||||
yaml.dump(r, ws);
|
||||
ws.close();
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void initRules() {
|
||||
RuleTool rt = new RuleTool(RulesFilePath);
|
||||
rt.getRulesFromSite();
|
||||
}
|
||||
|
||||
public static String getRulesFilePath() {
|
||||
return RulesFilePath;
|
||||
}
|
||||
|
||||
public static String getExcludeSuffix(){
|
||||
String excludeSuffix = "";
|
||||
File yamlSetting = new File(ConfigFilePath);
|
||||
|
||||
if (yamlSetting.exists() && yamlSetting.isFile()) {
|
||||
try {
|
||||
InputStream inorder = Files.newInputStream(Paths.get(ConfigFilePath));
|
||||
Map<String,Object> r = yaml.load(inorder);
|
||||
excludeSuffix = r.get("excludeSuffix").toString();
|
||||
} catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
excludeSuffix = ConfigEntry.excludeSuffix;
|
||||
}
|
||||
} else {
|
||||
excludeSuffix = ConfigEntry.excludeSuffix;
|
||||
}
|
||||
|
||||
return excludeSuffix;
|
||||
}
|
||||
|
||||
// 获取规则配置
|
||||
public static Map<String, Object[][]> getRules() {
|
||||
Map<String, Object> rulesMap = YamlTool.loadYaml(getRulesFilePath());
|
||||
Map<String, Object[][]> resRule = new HashMap<>();
|
||||
String[] fieldKeys = {"loaded", "name", "f_regex", "s_regex", "format", "color", "scope", "engine", "sensitive"};
|
||||
|
||||
Object rulesObj = rulesMap.get("rules");
|
||||
if (rulesObj instanceof List) {
|
||||
List<Map<String, Object>> groupData = (List<Map<String, Object>>) rulesObj;
|
||||
for (Map<String, Object> groupFields : groupData) {
|
||||
ArrayList<Object[]> data = new ArrayList<>();
|
||||
|
||||
Object ruleObj = groupFields.get("rule");
|
||||
if (ruleObj instanceof List) {
|
||||
List<Map<String, Object>> ruleData = (List<Map<String, Object>>) ruleObj;
|
||||
for (Map<String, Object> ruleFields : ruleData) {
|
||||
Object[] valuesArray = new Object[fieldKeys.length];
|
||||
for (int i = 0; i < fieldKeys.length; i++) {
|
||||
valuesArray[i] = ruleFields.get(fieldKeys[i]);
|
||||
}
|
||||
data.add(valuesArray);
|
||||
}
|
||||
}
|
||||
|
||||
Object[][] dataArray = data.toArray(new Object[data.size()][]);
|
||||
resRule.put(groupFields.get("group").toString(), dataArray);
|
||||
}
|
||||
}
|
||||
return resRule;
|
||||
}
|
||||
|
||||
public static void setExcludeSuffix(String excludeSuffix){
|
||||
Map<String,Object> r = new LinkedHashMap<>();
|
||||
r.put("excludeSuffix", excludeSuffix);
|
||||
try{
|
||||
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(ConfigFilePath)), StandardCharsets.UTF_8);
|
||||
yaml.dump(r, ws);
|
||||
ws.close();
|
||||
}catch (Exception ex){
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package burp.core.processor;
|
||||
|
||||
import burp.config.ConfigEntry;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class ColorProcessor {
|
||||
private String finalColor = "";
|
||||
|
||||
public List<Integer> retrieveColorIndices(List<String> colors){
|
||||
List<Integer> indices = new ArrayList<>();
|
||||
String[] colorArray = ConfigEntry.colorArray;
|
||||
int size = colorArray.length;
|
||||
|
||||
for (String color : colors) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (colorArray[i].equals(color)) {
|
||||
indices.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色升级递归算法
|
||||
*/
|
||||
private void upgradeColors(List<Integer> colorList) {
|
||||
int colorSize = colorList.size();
|
||||
String[] colorArray = ConfigEntry.colorArray;
|
||||
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++;
|
||||
}
|
||||
// 利用HashSet删除重复元素
|
||||
HashSet tmpList = new HashSet(stack);
|
||||
if (stack.size() == tmpList.size()) {
|
||||
stack.sort(Comparator.comparingInt(Integer::intValue));
|
||||
if(stack.get(0) < 0) {
|
||||
this.finalColor = colorArray[0];
|
||||
} else {
|
||||
this.finalColor = colorArray[stack.get(0)];
|
||||
}
|
||||
} else {
|
||||
this.upgradeColors(stack);
|
||||
}
|
||||
}
|
||||
|
||||
public String retrieveFinalColor(List<Integer> colorList) {
|
||||
upgradeColors(colorList);
|
||||
return finalColor;
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package burp.core.processor;
|
||||
|
||||
import burp.BurpExtender;
|
||||
import burp.IExtensionHelpers;
|
||||
import burp.IHttpRequestResponse;
|
||||
import burp.IRequestInfo;
|
||||
import burp.IResponseInfo;
|
||||
import burp.core.utils.MatchTool;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MessageProcessor {
|
||||
private MatchTool matcher = new MatchTool();
|
||||
private DataProcessingUnit dataProcessingUnit = new DataProcessingUnit();
|
||||
private ColorProcessor colorProcessor = new ColorProcessor();
|
||||
|
||||
public List<Map<String, String>> processMessage(IExtensionHelpers helpers, IHttpRequestResponse messageInfo, String host, boolean actionFlag) throws Exception {
|
||||
|
||||
byte[] requestByte = messageInfo.getRequest();
|
||||
byte[] responseByte = messageInfo.getResponse();
|
||||
|
||||
List<Map<String, String>> reqObj = processRequestMessage(helpers, requestByte, host, actionFlag);
|
||||
List<Map<String, String>> resObj = processResponseMessage(helpers, responseByte, host, actionFlag);
|
||||
List<Map<String, String>> mergedList = new ArrayList<>();
|
||||
|
||||
if (reqObj != null && !reqObj.isEmpty()) {
|
||||
if (resObj != null && !resObj.isEmpty()) {
|
||||
List<String> colorList = new ArrayList<>();
|
||||
|
||||
colorList.add(reqObj.get(0).get("color"));
|
||||
colorList.add(resObj.get(0).get("color"));
|
||||
Map<String, String> colorMap = new HashMap<>();
|
||||
colorMap.put("color", colorProcessor.retrieveFinalColor(colorProcessor.retrieveColorIndices(colorList)));
|
||||
|
||||
Map<String, String> commentMap = new HashMap<>();
|
||||
String commentList = String.format("%s, %s", reqObj.get(1).get("comment"), resObj.get(1).get("comment"));
|
||||
commentMap.put("comment", commentList);
|
||||
|
||||
mergedList.add(0, colorMap);
|
||||
mergedList.add(1, commentMap);
|
||||
} else {
|
||||
mergedList = new ArrayList<>(reqObj);
|
||||
}
|
||||
} else if (resObj != null && !resObj.isEmpty()){
|
||||
mergedList = new ArrayList<>(resObj);
|
||||
}
|
||||
|
||||
return mergedList;
|
||||
}
|
||||
|
||||
public List<Map<String, String>> processRequestMessage(IExtensionHelpers helpers, byte[] content, String host, boolean actionFlag) throws Exception {
|
||||
Map<String, Map<String, Object>> obj;
|
||||
|
||||
IRequestInfo requestInfo = helpers.analyzeRequest(content);
|
||||
List<String> requestTmpHeaders = requestInfo.getHeaders();
|
||||
String requestHeaders = String.join("\n", requestTmpHeaders);
|
||||
|
||||
try {
|
||||
String urlString = requestTmpHeaders.get(0).split(" ")[1];
|
||||
urlString = urlString.indexOf("?") > 0 ? urlString.substring(0, urlString.indexOf("?")) : urlString;
|
||||
if (matcher.matchUrlSuffix(urlString)) {
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
int requestBodyOffset = requestInfo.getBodyOffset();
|
||||
byte[] requestBody = Arrays.copyOfRange(content, requestBodyOffset, content.length);
|
||||
obj = dataProcessingUnit.matchContentByRegex(content, requestHeaders, requestBody, "request", host);
|
||||
|
||||
return getDataList(obj, actionFlag);
|
||||
}
|
||||
|
||||
public List<Map<String, String>> processResponseMessage(IExtensionHelpers helpers, byte[] content, String host, boolean actionFlag) throws Exception {
|
||||
Map<String, Map<String, Object>> obj;
|
||||
|
||||
IResponseInfo responseInfo = helpers.analyzeResponse(content);
|
||||
List<String> responseTmpHeaders = responseInfo.getHeaders();
|
||||
String responseHeaders = String.join("\n", responseTmpHeaders);
|
||||
|
||||
int responseBodyOffset = responseInfo.getBodyOffset();
|
||||
byte[] responseBody = Arrays.copyOfRange(content, responseBodyOffset, content.length);
|
||||
|
||||
if (responseBody.length > 1) {
|
||||
try {
|
||||
// TODO: 需要加入文件头校验来排除静态二进制文件
|
||||
String inferredMimeType = String.format("hae.%s", responseInfo.getInferredMimeType());
|
||||
String statedMimeType = String.format("hae.%s", responseInfo.getStatedMimeType());
|
||||
if (matcher.matchUrlSuffix(statedMimeType) || matcher.matchUrlSuffix(inferredMimeType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
obj = dataProcessingUnit.matchContentByRegex(content, responseHeaders, responseBody, "response", host);
|
||||
|
||||
return getDataList(obj, actionFlag);
|
||||
}
|
||||
|
||||
private List<Map<String, String>> getDataList(Map<String, Map<String, Object>> obj, boolean actionFlag) {
|
||||
List<Map<String, String>> highlightList = new ArrayList<>();
|
||||
List<Map<String, String>> extractList = new ArrayList<>();
|
||||
|
||||
if (obj.size() > 0) {
|
||||
if (actionFlag) {
|
||||
List<List<String>> resultList = dataProcessingUnit.extractColorsAndComments(obj);
|
||||
List<String> colorList = resultList.get(0);
|
||||
List<String> commentList = resultList.get(1);
|
||||
if (!colorList.isEmpty() && !commentList.isEmpty()) {
|
||||
String color = colorProcessor.retrieveFinalColor(colorProcessor.retrieveColorIndices(colorList));
|
||||
Map<String, String> colorMap = new HashMap<String, String>() {{
|
||||
put("color", color);
|
||||
}};
|
||||
Map<String, String> commentMap = new HashMap<String, String>() {{
|
||||
put("comment", String.join(", ", commentList));
|
||||
}};
|
||||
highlightList.add(colorMap);
|
||||
highlightList.add(commentMap);
|
||||
}
|
||||
} else {
|
||||
extractList.add(dataProcessingUnit.extractDataFromMap(obj));
|
||||
}
|
||||
}
|
||||
|
||||
return actionFlag ? highlightList : extractList;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package burp.core.utils;
|
||||
|
||||
import jregex.Pattern;
|
||||
import jregex.REFlags;
|
||||
import burp.config.ConfigLoader;
|
||||
|
||||
/**
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class MatchTool {
|
||||
public boolean matchUrlSuffix(String str) {
|
||||
Pattern pattern = new Pattern(String.format("[\\w]+[\\.](%s)", ConfigLoader.getExcludeSuffix()), REFlags.IGNORE_CASE);
|
||||
jregex.Matcher matcher = pattern.matcher(str);
|
||||
return matcher.find();
|
||||
}
|
||||
|
||||
public static boolean matchIP(String str) {
|
||||
return str.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b");
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package burp.rule.utils;
|
||||
|
||||
import burp.*;
|
||||
import java.io.FileOutputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
/**
|
||||
* @author EvilChen
|
||||
*/
|
||||
public class RuleTool {
|
||||
private String rulesFilePath;
|
||||
private boolean isSuccess;
|
||||
|
||||
public RuleTool(String rulesFilePath) {
|
||||
this.rulesFilePath = rulesFilePath;
|
||||
}
|
||||
|
||||
public void getRulesFromSite() {
|
||||
// 以独立线程使用BurpSuite官方请求接口获取规则
|
||||
Thread t = new Thread(()->{
|
||||
try {
|
||||
URL url = new URL("https://cdn.jsdelivr.net/gh/gh0stkey/HaE@gh-pages/Rules.yml");
|
||||
IHttpService iHttpService = BurpExtender.helpers.buildHttpService(url.getHost(), 443, true);
|
||||
IHttpRequestResponse iHttpRequestResponse = BurpExtender.callbacks.makeHttpRequest(iHttpService, BurpExtender.helpers.buildHttpRequest(url));
|
||||
byte[] responseByte = iHttpRequestResponse.getResponse();
|
||||
IResponseInfo iResponseInfo = BurpExtender.helpers.analyzeResponse(responseByte);
|
||||
int bodyOffset = iResponseInfo.getBodyOffset();
|
||||
byte[] responseBodyByte = Arrays.copyOfRange(responseByte, bodyOffset, responseByte.length);
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(this.rulesFilePath);
|
||||
fileOutputStream.write(responseBodyByte);
|
||||
fileOutputStream.close();
|
||||
isSuccess = true;
|
||||
} catch (Exception e) {
|
||||
isSuccess = false;
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
try {
|
||||
t.join(10000);
|
||||
} catch (Exception e) {
|
||||
isSuccess = false;
|
||||
}
|
||||
|
||||
if (isSuccess) {
|
||||
JOptionPane.showMessageDialog(null, "Rules update successfully!", "Info", JOptionPane.INFORMATION_MESSAGE);
|
||||
} else {
|
||||
JOptionPane.showMessageDialog(null, "Rule update failed, please check the network!", "Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package burp.rule.utils;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
/**
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class YamlTool {
|
||||
|
||||
public static Yaml newStandardYaml() {
|
||||
DumperOptions dop = new DumperOptions();
|
||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
Representer representer = new Representer();
|
||||
return new Yaml(representer, dop);
|
||||
}
|
||||
|
||||
public static Map<String, Object> loadYaml(String filePath) {
|
||||
try {
|
||||
InputStream inputStream = Files.newInputStream(Paths.get(filePath));
|
||||
return newStandardYaml().load(inputStream);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,347 +0,0 @@
|
||||
package burp.ui;
|
||||
|
||||
import burp.config.ConfigEntry;
|
||||
import burp.config.ConfigLoader;
|
||||
import burp.rule.RuleProcessor;
|
||||
import burp.ui.board.Databoard;
|
||||
import burp.ui.board.MessagePanel;
|
||||
import burp.ui.rule.RulePane;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.net.URL;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author LinChen && EvilChen
|
||||
*/
|
||||
|
||||
public class MainUI extends JPanel {
|
||||
|
||||
public MainUI(MessagePanel messagePanel) {
|
||||
databoardPanel = new Databoard(messagePanel);
|
||||
initComponents();
|
||||
}
|
||||
|
||||
public void closeTabActionPerformed(ActionEvent e) {
|
||||
if (ruleTabbedPane.getTabCount() > 2 && ruleTabbedPane.getSelectedIndex() != 0) {
|
||||
String title = ruleTabbedPane.getTitleAt(ruleTabbedPane.getSelectedIndex());
|
||||
new RuleProcessor().deleteRuleGroup(title);
|
||||
ruleTabbedPane.remove(ruleTabbedPane.getSelectedIndex());
|
||||
ruleTabbedPane.setSelectedIndex(ruleTabbedPane.getSelectedIndex() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void onlineUpdateActionPerformed(ActionEvent e) {
|
||||
// 添加提示框防止用户误触导致配置更新
|
||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to update rules?", "Info", JOptionPane.YES_NO_OPTION);
|
||||
if (retCode == JOptionPane.YES_OPTION) {
|
||||
ConfigLoader.initRules();
|
||||
reloadRule();
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadRule(){
|
||||
ruleTabbedPane.removeAll();
|
||||
ruleSwitch.setListen(false);
|
||||
Map<String,Object[][]> rules = ConfigLoader.getRules();
|
||||
rules.keySet().forEach(
|
||||
i -> ruleTabbedPane.addTab(
|
||||
i,
|
||||
new RulePane(rules.get(i), ruleTabbedPane)
|
||||
)
|
||||
);
|
||||
ruleTabbedPane.addTab("...", new JLabel());
|
||||
ruleSwitch.setListen(true);
|
||||
}
|
||||
|
||||
private void reloadActionPerformed(ActionEvent e) {
|
||||
reloadRule();
|
||||
}
|
||||
|
||||
private void excludeSuffixSaveActionPerformed(ActionEvent e) {
|
||||
ConfigLoader.setExcludeSuffix(excludeSuffixTextField.getText());
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
JTabbedPane mainTabbedPane = new JTabbedPane();
|
||||
ruleTabbedPane = new JTabbedPane();
|
||||
JPanel rulePanel = new JPanel();
|
||||
rulesPathTextField = new JTextField();
|
||||
JLabel rulesPathLabel = new JLabel();
|
||||
JButton onlineUpdateButton = new JButton();
|
||||
JButton reloadButton = new JButton();
|
||||
JLabel excludeSuffixLabel = new JLabel();
|
||||
excludeSuffixTextField = new JTextField();
|
||||
JButton excludeSuffixSaveButton = new JButton();
|
||||
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout)getLayout()).columnWidths = new int[] {0, 0};
|
||||
((GridBagLayout)getLayout()).rowHeights = new int[] {0, 0};
|
||||
((GridBagLayout)getLayout()).columnWeights = new double[] {1.0, 1.0E-4};
|
||||
((GridBagLayout)getLayout()).rowWeights = new double[] {1.0, 1.0E-4};
|
||||
|
||||
{
|
||||
mainTabbedPane.addTab("Rules", ruleTabbedPane);
|
||||
|
||||
{
|
||||
rulePanel.setLayout(new GridBagLayout());
|
||||
((GridBagLayout) rulePanel.getLayout()).columnWidths = new int[] {0, 0, 0, 0, 0};
|
||||
((GridBagLayout) rulePanel.getLayout()).rowHeights = new int[] {0, 0, 0};
|
||||
((GridBagLayout) rulePanel.getLayout()).columnWeights = new double[] {0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
||||
((GridBagLayout) rulePanel.getLayout()).rowWeights = new double[] {0.0, 0.0, 1.0E-4};
|
||||
|
||||
rulesPathTextField.setEditable(false);
|
||||
rulePanel.add(rulesPathTextField, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(5, 0, 5, 5), 0, 0));
|
||||
|
||||
rulesPathLabel.setText("Rules Path:");
|
||||
rulePanel.add(rulesPathLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.WEST, GridBagConstraints.VERTICAL,
|
||||
new Insets(5, 5, 5, 5), 0, 0));
|
||||
|
||||
onlineUpdateButton.setText("Online Update");
|
||||
onlineUpdateButton.addActionListener(this::onlineUpdateActionPerformed);
|
||||
rulePanel.add(onlineUpdateButton, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(5, 0, 5, 5), 0, 0));
|
||||
|
||||
reloadButton.setText("Reload");
|
||||
reloadButton.addActionListener(this::reloadActionPerformed);
|
||||
rulePanel.add(reloadButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0,
|
||||
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(5, 0, 5, 5), 0, 0));
|
||||
|
||||
excludeSuffixLabel.setText("Exclude Suffix:");
|
||||
rulePanel.add(excludeSuffixLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE,
|
||||
new Insets(0, 5, 5, 5), 0, 0));
|
||||
rulePanel.add(excludeSuffixTextField, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL,
|
||||
new Insets(0, 0, 0, 5), 0, 0));
|
||||
|
||||
excludeSuffixSaveButton.setText("Save");
|
||||
excludeSuffixSaveButton.addActionListener(this::excludeSuffixSaveActionPerformed);
|
||||
rulePanel.add(excludeSuffixSaveButton, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL,
|
||||
new Insets(0, 0, 0, 5), 0, 0));
|
||||
}
|
||||
mainTabbedPane.addTab("Config", rulePanel);
|
||||
mainTabbedPane.addTab("Databoard", this.databoardPanel);
|
||||
}
|
||||
|
||||
// 新增Logo
|
||||
JTabbedPane HaETabbedPane = new JTabbedPane();
|
||||
HaETabbedPane.addTab("", getImageIcon(false), mainTabbedPane);
|
||||
HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations ", null);
|
||||
HaETabbedPane.setEnabledAt(1, false);
|
||||
HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() {
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent e) {
|
||||
boolean isDarkBg = isDarkBg();
|
||||
HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg));
|
||||
}
|
||||
|
||||
private boolean isDarkBg() {
|
||||
Color bg = HaETabbedPane.getBackground();
|
||||
int r = bg.getRed();
|
||||
int g = bg.getGreen();
|
||||
int b = bg.getBlue();
|
||||
int avg = (r + g + b) / 3;
|
||||
|
||||
return avg < 128;
|
||||
}
|
||||
});
|
||||
|
||||
add(HaETabbedPane, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 0, 0, 0), 0, 0));
|
||||
|
||||
ConfigEntry.globalRules.keySet().forEach(i-> ruleTabbedPane.addTab(i, new RulePane(
|
||||
ConfigEntry.globalRules.get(i),
|
||||
ruleTabbedPane)));
|
||||
|
||||
ruleTabbedPane.addTab("...", new JLabel());
|
||||
|
||||
rulesPathTextField.setText(ConfigLoader.getRulesFilePath());
|
||||
excludeSuffixTextField.setText(ConfigLoader.getExcludeSuffix());
|
||||
ruleSwitch = new TabTitleEditListener(ruleTabbedPane);
|
||||
ruleTabbedPane.addChangeListener(ruleSwitch);
|
||||
ruleTabbedPane.addMouseListener(ruleSwitch);
|
||||
deleteMenuItem.addActionListener(this::closeTabActionPerformed);
|
||||
tabMenu.add(deleteMenuItem);
|
||||
}
|
||||
|
||||
private ImageIcon getImageIcon(boolean isDark) {
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
URL imageURL;
|
||||
if (isDark) {
|
||||
imageURL = classLoader.getResource("logo.png");
|
||||
} else {
|
||||
imageURL = classLoader.getResource("logo_black.png");
|
||||
}
|
||||
ImageIcon originalIcon = new ImageIcon(imageURL);
|
||||
Image originalImage = originalIcon.getImage();
|
||||
Image scaledImage = originalImage.getScaledInstance(30, 20, Image.SCALE_FAST);
|
||||
ImageIcon scaledIcon = new ImageIcon(scaledImage);
|
||||
return scaledIcon;
|
||||
}
|
||||
|
||||
private JTabbedPane ruleTabbedPane;
|
||||
private JTextField rulesPathTextField;
|
||||
private JTextField excludeSuffixTextField;
|
||||
private Databoard databoardPanel;
|
||||
protected static JPopupMenu tabMenu = new JPopupMenu();
|
||||
private final JMenuItem deleteMenuItem = new JMenuItem("Delete");
|
||||
private TabTitleEditListener ruleSwitch;
|
||||
}
|
||||
|
||||
class TabTitleEditListener extends MouseAdapter implements ChangeListener, DocumentListener {
|
||||
protected final JTextField ruleEditTextField = new JTextField();
|
||||
protected final JTabbedPane ruleEditTabbedPane;
|
||||
protected int editingIndex = -1;
|
||||
protected int len = -1;
|
||||
protected Boolean listen = true;
|
||||
protected Dimension dim;
|
||||
protected Component tabComponent;
|
||||
protected Boolean isRenameOk = false;
|
||||
protected RuleProcessor ruleProcessor = new RuleProcessor();
|
||||
|
||||
protected final Action startEditing = new AbstractAction() {
|
||||
@Override public void actionPerformed(ActionEvent e) {
|
||||
editingIndex = ruleEditTabbedPane.getSelectedIndex();
|
||||
tabComponent = ruleEditTabbedPane.getTabComponentAt(editingIndex);
|
||||
ruleEditTabbedPane.setTabComponentAt(editingIndex, ruleEditTextField);
|
||||
isRenameOk = true;
|
||||
ruleEditTextField.setVisible(true);
|
||||
ruleEditTextField.setText(ruleEditTabbedPane.getTitleAt(editingIndex));
|
||||
ruleEditTextField.selectAll();
|
||||
ruleEditTextField.requestFocusInWindow();
|
||||
len = ruleEditTextField.getText().length();
|
||||
dim = ruleEditTextField.getPreferredSize();
|
||||
ruleEditTextField.setMinimumSize(dim);
|
||||
}
|
||||
};
|
||||
|
||||
protected final Action renameTabTitle = new AbstractAction() {
|
||||
@Override public void actionPerformed(ActionEvent e) {
|
||||
String title = ruleEditTextField.getText().trim();
|
||||
if (editingIndex >= 0 && !title.isEmpty()) {
|
||||
String oldName = ruleEditTabbedPane.getTitleAt(editingIndex);
|
||||
ruleEditTabbedPane.setTitleAt(editingIndex, title);
|
||||
ruleProcessor.renameRuleGroup(oldName,title);
|
||||
}
|
||||
cancelEditing.actionPerformed(null);
|
||||
}
|
||||
};
|
||||
|
||||
protected final Action cancelEditing = new AbstractAction() {
|
||||
@Override public void actionPerformed(ActionEvent e) {
|
||||
if (editingIndex >= 0) {
|
||||
ruleEditTabbedPane.setTabComponentAt(editingIndex, tabComponent);
|
||||
ruleEditTextField.setVisible(false);
|
||||
editingIndex = -1;
|
||||
len = -1;
|
||||
tabComponent = null;
|
||||
ruleEditTextField.setPreferredSize(null);
|
||||
ruleEditTabbedPane.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected TabTitleEditListener(JTabbedPane tabbedPane) {
|
||||
super();
|
||||
this.ruleEditTabbedPane = tabbedPane;
|
||||
ruleEditTextField.setBorder(BorderFactory.createEmptyBorder());
|
||||
ruleEditTextField.addFocusListener(new FocusAdapter() {
|
||||
@Override public void focusLost(FocusEvent e) {
|
||||
renameTabTitle.actionPerformed(null);
|
||||
}
|
||||
});
|
||||
InputMap im = ruleEditTextField.getInputMap(JComponent.WHEN_FOCUSED);
|
||||
ActionMap am = ruleEditTextField.getActionMap();
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel-editing");
|
||||
am.put("cancel-editing", cancelEditing);
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "rename-tab-title");
|
||||
am.put("rename-tab-title", renameTabTitle);
|
||||
ruleEditTextField.getDocument().addDocumentListener(this);
|
||||
tabbedPane.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "start-editing");
|
||||
tabbedPane.getActionMap().put("start-editing", startEditing);
|
||||
}
|
||||
|
||||
@Override public void stateChanged(ChangeEvent e) {
|
||||
if (e.getSource() instanceof JTabbedPane && listen) {
|
||||
JTabbedPane pane = (JTabbedPane) e.getSource();
|
||||
if (!isRenameOk){
|
||||
if (pane.getSelectedIndex() == pane.getComponentCount()-1){
|
||||
newTab();
|
||||
}
|
||||
}else{
|
||||
if (pane.getSelectedIndex() == pane.getComponentCount()-2){
|
||||
newTab();
|
||||
}
|
||||
}
|
||||
}
|
||||
renameTabTitle.actionPerformed(null);
|
||||
}
|
||||
|
||||
public void newTab(){
|
||||
Object[][] data = new Object[][]{{false, "New Name", "(New Regex)", "", "{0}", "gray", "any", "nfa", false}};
|
||||
insertTab(ruleEditTabbedPane, ruleProcessor.newRule(),data);
|
||||
}
|
||||
|
||||
public void insertTab(JTabbedPane pane,String title,Object[][] data){
|
||||
pane.addTab(title,new RulePane(data,pane));
|
||||
pane.remove(pane.getSelectedIndex());
|
||||
pane.addTab("...",new JLabel());
|
||||
}
|
||||
|
||||
public void setListen(Boolean listen){
|
||||
this.listen = listen;
|
||||
}
|
||||
|
||||
@Override public void insertUpdate(DocumentEvent e) {
|
||||
updateTabSize();
|
||||
}
|
||||
|
||||
@Override public void removeUpdate(DocumentEvent e) {
|
||||
updateTabSize();
|
||||
}
|
||||
|
||||
@Override public void changedUpdate(DocumentEvent e) {}
|
||||
|
||||
@Override public void mouseClicked(MouseEvent e) {
|
||||
switch (e.getButton()){
|
||||
case 1:
|
||||
{
|
||||
Rectangle r = ruleEditTabbedPane.getBoundsAt(ruleEditTabbedPane.getSelectedIndex());
|
||||
boolean isDoubleClick = e.getClickCount() >= 2;
|
||||
if (isDoubleClick && r.contains(e.getPoint())) {
|
||||
startEditing.actionPerformed(null);
|
||||
} else {
|
||||
renameTabTitle.actionPerformed(null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3:{
|
||||
MainUI.tabMenu.show(e.getComponent(),e.getX(),e.getY());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateTabSize() {
|
||||
ruleEditTextField.setPreferredSize(ruleEditTextField.getText().length() > len ? null : dim);
|
||||
ruleEditTabbedPane.revalidate();
|
||||
}
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
package burp.ui.rule;
|
||||
|
||||
import burp.rule.RuleProcessor;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.TableModelEvent;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.awt.*;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* @author LinChen & EvilChen
|
||||
*/
|
||||
|
||||
public class RulePane extends JPanel {
|
||||
private RuleProcessor ruleProcessor = new RuleProcessor();
|
||||
private Boolean isEdit = false;
|
||||
private DefaultTableModel model = createModel();
|
||||
private static final int YES_OPTION = JOptionPane.YES_OPTION;
|
||||
private static final String[] TITLE = {
|
||||
"Loaded", "Name", "F-Regex", "S-Regex", "Format", "Color", "Scope", "Engine", "Sensitive"
|
||||
};
|
||||
|
||||
public RulePane(Object[][] data, JTabbedPane pane) {
|
||||
initComponents(data, pane);
|
||||
}
|
||||
|
||||
private DefaultTableModel createModel() {
|
||||
return new DefaultTableModel() {
|
||||
@Override
|
||||
public Class<?> getColumnClass(int column) {
|
||||
return (column == 0) ? Boolean.class : String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
return column == 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void updateModel() {
|
||||
model = (DefaultTableModel) ruleTable.getModel();
|
||||
}
|
||||
|
||||
private void ruleAddActionPerformed(ActionEvent e, JTabbedPane pane) {
|
||||
RuleSetting ruleSettingPanel = new RuleSetting();
|
||||
ruleSettingPanel.formatTextField.setText("{0}");
|
||||
|
||||
int showState = JOptionPane.showConfirmDialog(null, ruleSettingPanel, "Add Rule", JOptionPane.OK_OPTION);
|
||||
if (showState == YES_OPTION) {
|
||||
Vector<Object> ruleData = new Vector<>();
|
||||
ruleData.add(false);
|
||||
ruleData.add(ruleSettingPanel.ruleNameTextField.getText());
|
||||
ruleData.add(ruleSettingPanel.firstRegexTextField.getText());
|
||||
ruleData.add(ruleSettingPanel.secondRegexTextField.getText());
|
||||
ruleData.add(ruleSettingPanel.formatTextField.getText());
|
||||
ruleData.add(ruleSettingPanel.colorComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleSettingPanel.scopeComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleSettingPanel.engineComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleSettingPanel.sensitiveComboBox.getSelectedItem());
|
||||
model.insertRow(model.getRowCount(), ruleData);
|
||||
updateModel();
|
||||
ruleProcessor.addRule(ruleData, pane.getTitleAt(pane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
|
||||
private void ruleEditActionPerformed(ActionEvent e, JTabbedPane pane){
|
||||
if (ruleTable.getSelectedRowCount() >= 1){
|
||||
RuleSetting ruleSettingPanel = new RuleSetting();
|
||||
ruleSettingPanel.ruleNameTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 1).toString());
|
||||
ruleSettingPanel.firstRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 2).toString());
|
||||
ruleSettingPanel.secondRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 3).toString());
|
||||
ruleSettingPanel.formatTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 4).toString());
|
||||
ruleSettingPanel.colorComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 5).toString());
|
||||
ruleSettingPanel.scopeComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 6).toString());
|
||||
ruleSettingPanel.engineComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 7).toString());
|
||||
ruleSettingPanel.sensitiveComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(),8));
|
||||
|
||||
ruleSettingPanel.formatTextField.setEnabled(
|
||||
ruleSettingPanel.engineComboBox.getSelectedItem().toString().equals("nfa")
|
||||
);
|
||||
|
||||
int showState = JOptionPane.showConfirmDialog(null, ruleSettingPanel, "Edit Rule", JOptionPane.OK_OPTION);
|
||||
if (showState == 0){
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
model.setValueAt(ruleSettingPanel.ruleNameTextField.getText(), select, 1);
|
||||
model.setValueAt(ruleSettingPanel.firstRegexTextField.getText(), select, 2);
|
||||
model.setValueAt(ruleSettingPanel.secondRegexTextField.getText(), select, 3);
|
||||
model.setValueAt(ruleSettingPanel.formatTextField.getText(), select, 4);
|
||||
model.setValueAt(ruleSettingPanel.colorComboBox.getSelectedItem().toString(), select, 5);
|
||||
model.setValueAt(ruleSettingPanel.scopeComboBox.getSelectedItem().toString(), select, 6);
|
||||
model.setValueAt(ruleSettingPanel.engineComboBox.getSelectedItem().toString(), select, 7);
|
||||
model.setValueAt(ruleSettingPanel.sensitiveComboBox.getSelectedItem(), select, 8);
|
||||
model = (DefaultTableModel) ruleTable.getModel();
|
||||
ruleProcessor.changeRule((Vector) model.getDataVector().get(select), select, pane.getTitleAt(pane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ruleRemoveActionPerformed(ActionEvent e, JTabbedPane pane){
|
||||
if (ruleTable.getSelectedRowCount() >= 1){
|
||||
int isOk = JOptionPane.showConfirmDialog(null, "Are you sure you want to delete this rule?", "Info", JOptionPane.OK_OPTION);
|
||||
if (isOk == 0){
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
model.removeRow(select);
|
||||
model = (DefaultTableModel) ruleTable.getModel();
|
||||
ruleProcessor.removeRule(select, pane.getTitleAt(pane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ruleTableChange(TableModelEvent e, JTabbedPane pane) {
|
||||
if (e.getColumn() == 0 && ruleTable.getSelectedRow() != -1 && !isEdit){
|
||||
model = (DefaultTableModel) ruleTable.getModel();
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
ruleProcessor.changeRule((Vector) model.getDataVector().get(select), select, pane.getTitleAt(pane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
|
||||
private void initComponents(Object[][] data, JTabbedPane pane) {
|
||||
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
|
||||
addButton = new JButton();
|
||||
editButton = new JButton();
|
||||
scrollPane = new JScrollPane();
|
||||
ruleTable = new JTable();
|
||||
removeButton = new JButton();
|
||||
|
||||
//======== this ========
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout)getLayout()).columnWidths = new int[] {0, 0, 0};
|
||||
((GridBagLayout)getLayout()).rowHeights = new int[] {0, 0, 0, 0, 0};
|
||||
((GridBagLayout)getLayout()).columnWeights = new double[] {0.0, 1.0, 1.0E-4};
|
||||
((GridBagLayout)getLayout()).rowWeights = new double[] {0.0, 0.0, 0.0, 1.0, 1.0E-4};
|
||||
|
||||
//---- addButton ----
|
||||
addButton.setText("Add");
|
||||
|
||||
addButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
isEdit = true;
|
||||
ruleAddActionPerformed(e, pane);
|
||||
model = (DefaultTableModel) ruleTable.getModel();
|
||||
isEdit = false;
|
||||
}
|
||||
});
|
||||
|
||||
add(addButton, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(15, 5, 3, 2), 0, 0));
|
||||
|
||||
//---- editButton ----
|
||||
editButton.setText("Edit");
|
||||
editButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
isEdit = true;
|
||||
ruleEditActionPerformed(e, pane);
|
||||
model = (DefaultTableModel) ruleTable.getModel();
|
||||
isEdit = false;
|
||||
}
|
||||
});
|
||||
|
||||
add(editButton, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 5, 3, 2), 0, 0));
|
||||
|
||||
//======== scrollPane ========
|
||||
{
|
||||
//---- table ----
|
||||
ruleTable.setShowVerticalLines(false);
|
||||
ruleTable.setVerifyInputWhenFocusTarget(false);
|
||||
ruleTable.setUpdateSelectionOnSort(false);
|
||||
ruleTable.setShowHorizontalLines(false);
|
||||
ruleTable.setModel(new DefaultTableModel());
|
||||
ruleTable.setSurrendersFocusOnKeystroke(true);
|
||||
scrollPane.setViewportView(ruleTable);
|
||||
}
|
||||
|
||||
add(scrollPane, new GridBagConstraints(1, 0, 1, 4, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(15, 5, 5, 5), 0, 0));
|
||||
|
||||
//---- removeButton ----
|
||||
removeButton.setText("Remove");
|
||||
|
||||
removeButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
isEdit = true;
|
||||
ruleRemoveActionPerformed(e, pane);
|
||||
model = (DefaultTableModel) ruleTable.getModel();
|
||||
isEdit = false;
|
||||
}
|
||||
});
|
||||
|
||||
add(removeButton, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 5, 3, 2), 0, 0));
|
||||
|
||||
ruleTable.setModel(model);
|
||||
model.setDataVector(data, TITLE);
|
||||
model.addTableModelListener(e -> ruleTableChange(e, pane));
|
||||
ruleTable.setRowSorter(new TableRowSorter<>(model));
|
||||
}
|
||||
|
||||
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
|
||||
public JButton addButton;
|
||||
public JButton editButton;
|
||||
public JScrollPane scrollPane;
|
||||
public JTable ruleTable;
|
||||
public JButton removeButton;
|
||||
// JFormDesigner - End of variables declaration //GEN-END:variables
|
||||
}
|
||||
|
||||
53
src/main/java/hae/Config.java
Normal file
53
src/main/java/hae/Config.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package hae;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class Config {
|
||||
public static String suffix = "3g2|3gp|7z|aac|abw|aif|aifc|aiff|apk|arc|au|avi|azw|bat|bin|bmp|bz|bz2|cmd|cmx|cod|com|csh|css|csv|dll|doc|docx|ear|eot|epub|exe|flac|flv|gif|gz|ico|ics|ief|jar|jfif|jpe|jpeg|jpg|less|m3u|mid|midi|mjs|mkv|mov|mp2|mp3|mp4|mpa|mpe|mpeg|mpg|mpkg|mpp|mpv2|odp|ods|odt|oga|ogg|ogv|ogx|otf|pbm|pdf|pgm|png|pnm|ppm|ppt|pptx|ra|ram|rar|ras|rgb|rmi|rtf|scss|sh|snd|svg|swf|tar|tif|tiff|ttf|vsd|war|wav|weba|webm|webp|wmv|woff|woff2|xbm|xls|xlsx|xpm|xul|xwd|zip";
|
||||
|
||||
public static String[] scope = new String[] {
|
||||
"any",
|
||||
"any header",
|
||||
"any body",
|
||||
"response",
|
||||
"response header",
|
||||
"response body",
|
||||
"request",
|
||||
"request header",
|
||||
"request body"
|
||||
};
|
||||
|
||||
public static String[] ruleFields = {
|
||||
"Loaded", "Name", "F-Regex", "S-Regex", "Format", "Color", "Scope", "Engine", "Sensitive"
|
||||
};
|
||||
|
||||
public static Object[][] ruleTemplate = new Object[][] {
|
||||
{
|
||||
false, "New Name", "(First Regex)", "(Second Regex)", "{0}", "gray", "any", "nfa", false
|
||||
}
|
||||
};
|
||||
|
||||
public static String[] engine = new String[] {
|
||||
"nfa",
|
||||
"dfa"
|
||||
};
|
||||
|
||||
public static String[] color = new String[] {
|
||||
"red",
|
||||
"orange",
|
||||
"yellow",
|
||||
"green",
|
||||
"cyan",
|
||||
"blue",
|
||||
"pink",
|
||||
"magenta",
|
||||
"gray"
|
||||
};
|
||||
|
||||
public static Map<String, Object[][]> globalRules = new HashMap<>();
|
||||
|
||||
public static ConcurrentHashMap<String, Map<String, List<String>>> globalDataMap = new ConcurrentHashMap<>();
|
||||
}
|
||||
47
src/main/java/hae/HaE.java
Normal file
47
src/main/java/hae/HaE.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package hae;
|
||||
|
||||
import burp.api.montoya.BurpExtension;
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.logging.Logging;
|
||||
import hae.component.Main;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.instances.editor.RequestEditor;
|
||||
import hae.instances.editor.ResponseEditor;
|
||||
import hae.instances.editor.WebSocketEditor;
|
||||
import hae.instances.http.HttpMessageHandler;
|
||||
import hae.instances.websocket.WebSocketMessageHandler;
|
||||
import hae.utils.config.ConfigLoader;
|
||||
|
||||
public class HaE implements BurpExtension {
|
||||
@Override
|
||||
public void initialize(MontoyaApi api) {
|
||||
// 设置扩展名称
|
||||
String version = "3.0";
|
||||
api.extension().setName(String.format("HaE (%s) - Highlighter and Extractor", version));
|
||||
|
||||
// 加载扩展后输出的项目信息
|
||||
Logging logging = api.logging();
|
||||
logging.logToOutput("[ HACK THE WORLD - TO DO IT ]");
|
||||
logging.logToOutput("[#] Author: EvilChen && 0chencc");
|
||||
logging.logToOutput("[#] Github: https://github.com/gh0stkey/HaE");
|
||||
|
||||
// 配置文件加载
|
||||
ConfigLoader configLoader = new ConfigLoader(api);
|
||||
|
||||
MessageTableModel messageTableModel = new MessageTableModel(api);
|
||||
|
||||
// 注册Tab页(用于查询数据)
|
||||
api.userInterface().registerSuiteTab("HaE", new Main(api, configLoader, messageTableModel));
|
||||
|
||||
// 注册HTTP处理器
|
||||
api.http().registerHttpHandler(new HttpMessageHandler(api, messageTableModel));
|
||||
|
||||
// 注册WebSocket处理器
|
||||
api.proxy().registerWebSocketCreationHandler(proxyWebSocketCreation -> proxyWebSocketCreation.proxyWebSocket().registerProxyMessageHandler(new WebSocketMessageHandler(api)));
|
||||
|
||||
// 注册消息编辑框(用于展示数据)
|
||||
api.userInterface().registerHttpRequestEditorProvider(new RequestEditor(api));
|
||||
api.userInterface().registerHttpResponseEditorProvider(new ResponseEditor(api));
|
||||
api.userInterface().registerWebSocketMessageEditorProvider(new WebSocketEditor(api));
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,8 @@
|
||||
package burp.core;
|
||||
package hae.cache;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class GlobalCachePool {
|
||||
// 用于缓存匹配结果,以请求/响应的MD5 Hash作为索引
|
||||
public class CachePool {
|
||||
private static final Map<String, Map<String, Map<String, Object>>> cache = new HashMap<>();
|
||||
|
||||
public static void addToCache(String key, Map<String, Map<String, Object>> value) {
|
||||
@@ -22,5 +16,4 @@ public class GlobalCachePool {
|
||||
public static void removeFromCache(String key) {
|
||||
cache.remove(key);
|
||||
}
|
||||
|
||||
}
|
||||
87
src/main/java/hae/component/Main.java
Normal file
87
src/main/java/hae/component/Main.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package hae.component;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.component.board.Databoard;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.component.config.Config;
|
||||
import hae.component.rule.Rules;
|
||||
import hae.utils.config.ConfigLoader;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.net.URL;
|
||||
|
||||
public class Main extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final MessageTableModel messageTableModel;
|
||||
|
||||
public Main(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.messageTableModel = messageTableModel;
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout)getLayout()).columnWidths = new int[] {0, 0};
|
||||
((GridBagLayout)getLayout()).rowHeights = new int[] {0, 0};
|
||||
((GridBagLayout)getLayout()).columnWeights = new double[] {1.0, 1.0E-4};
|
||||
((GridBagLayout)getLayout()).rowWeights = new double[] {1.0, 1.0E-4};
|
||||
|
||||
JTabbedPane mainTabbedPane = new JTabbedPane();
|
||||
|
||||
// 新增Logo
|
||||
JTabbedPane HaETabbedPane = new JTabbedPane();
|
||||
HaETabbedPane.addTab("", getImageIcon(false), mainTabbedPane);
|
||||
// 中文Slogan:赋能白帽,高效作战
|
||||
HaETabbedPane.addTab(" Highlighter and Extractor - Empower ethical hacker for efficient operations. ", null);
|
||||
HaETabbedPane.setEnabledAt(1, false);
|
||||
HaETabbedPane.addPropertyChangeListener("background", new PropertyChangeListener() {
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent e) {
|
||||
boolean isDarkBg = isDarkBg();
|
||||
HaETabbedPane.setIconAt(0, getImageIcon(isDarkBg));
|
||||
}
|
||||
|
||||
private boolean isDarkBg() {
|
||||
Color bg = HaETabbedPane.getBackground();
|
||||
int r = bg.getRed();
|
||||
int g = bg.getGreen();
|
||||
int b = bg.getBlue();
|
||||
int avg = (r + g + b) / 3;
|
||||
|
||||
return avg < 128;
|
||||
}
|
||||
});
|
||||
|
||||
add(HaETabbedPane, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 0, 0, 0), 0, 0));
|
||||
|
||||
// 依次添加Rules、Config、Databoard
|
||||
Rules rules = new Rules(api, configLoader);
|
||||
mainTabbedPane.addTab("Rules", rules);
|
||||
mainTabbedPane.addTab("Config", new Config(api, configLoader, rules));
|
||||
mainTabbedPane.addTab("Databoard", new Databoard(api, configLoader, messageTableModel));
|
||||
}
|
||||
|
||||
private ImageIcon getImageIcon(boolean isDark) {
|
||||
ClassLoader classLoader = getClass().getClassLoader();
|
||||
URL imageURL;
|
||||
if (isDark) {
|
||||
imageURL = classLoader.getResource("logo.png");
|
||||
} else {
|
||||
imageURL = classLoader.getResource("logo_black.png");
|
||||
}
|
||||
ImageIcon originalIcon = new ImageIcon(imageURL);
|
||||
Image originalImage = originalIcon.getImage();
|
||||
Image scaledImage = originalImage.getScaledInstance(30, 20, Image.SCALE_FAST);
|
||||
ImageIcon scaledIcon = new ImageIcon(scaledImage);
|
||||
return scaledIcon;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
package burp.ui.board;
|
||||
package hae.component.board;
|
||||
|
||||
import burp.config.ConfigEntry;
|
||||
import burp.core.utils.StringHelper;
|
||||
import burp.ui.board.MessagePanel.Table;
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.Config;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.utils.string.StringProcessor;
|
||||
import hae.utils.config.ConfigLoader;
|
||||
import hae.component.board.message.MessageTableModel.MessageTable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javax.swing.event.*;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
import javax.swing.table.TableModel;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
@@ -16,98 +18,55 @@ import java.awt.event.*;
|
||||
import java.util.List;
|
||||
import javax.swing.*;
|
||||
|
||||
/**
|
||||
* @author LinChen && EvilChen
|
||||
*/
|
||||
|
||||
public class Databoard extends JPanel {
|
||||
private static Boolean isMatchHost = false;
|
||||
private JLabel hostLabel;
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final MessageTableModel messageTableModel;
|
||||
private JTextField hostTextField;
|
||||
private JTabbedPane dataTabbedPane;
|
||||
private JButton clearButton;
|
||||
private JSplitPane splitPane;
|
||||
private MessagePanel messagePanel;
|
||||
private Table table;
|
||||
private SwingWorker<Object, Void> currentWorker;
|
||||
private MessageTable messageTable;
|
||||
|
||||
private static Boolean isMatchHost = false;
|
||||
private DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel();
|
||||
private JComboBox hostComboBox = new JComboBox(comboBoxModel);
|
||||
private ChangeListener changeListenerInstance = new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
int selectedIndex = dataTabbedPane.getSelectedIndex();
|
||||
String selectedTitle = "";
|
||||
|
||||
if (selectedIndex != -1) {
|
||||
selectedTitle = dataTabbedPane.getTitleAt(selectedIndex);
|
||||
}
|
||||
public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.messageTableModel = messageTableModel;
|
||||
|
||||
applyHostFilter(selectedTitle);
|
||||
}
|
||||
};
|
||||
|
||||
public Databoard(MessagePanel messagePanel) {
|
||||
this.messagePanel = messagePanel;
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void cleanUI() {
|
||||
dataTabbedPane.removeAll();
|
||||
splitPane.setVisible(false);
|
||||
}
|
||||
|
||||
private void clearActionPerformed(ActionEvent e) {
|
||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to clear data?", "Info",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
if (retCode == JOptionPane.YES_OPTION) {
|
||||
cleanUI();
|
||||
|
||||
String host = hostTextField.getText();
|
||||
String cleanedHost = StringHelper.replaceFirstOccurrence(host, "*.", "");
|
||||
|
||||
if (host.contains("*")) {
|
||||
ConfigEntry.globalDataMap.keySet().removeIf(i -> i.contains(cleanedHost) || cleanedHost.contains("*"));
|
||||
} else {
|
||||
ConfigEntry.globalDataMap.remove(host);
|
||||
}
|
||||
|
||||
messagePanel.deleteByHost(cleanedHost);
|
||||
}
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
hostLabel = new JLabel();
|
||||
hostTextField = new JTextField();
|
||||
dataTabbedPane = new JTabbedPane(JTabbedPane.TOP);
|
||||
clearButton = new JButton();
|
||||
|
||||
//======== this ========
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout)getLayout()).columnWidths = new int[] {25, 0, 0, 0, 20, 0};
|
||||
((GridBagLayout)getLayout()).columnWidths = new int[] {25, 0, 0, 0,20, 0};
|
||||
((GridBagLayout)getLayout()).rowHeights = new int[] {0, 65, 20, 0};
|
||||
((GridBagLayout)getLayout()).columnWeights = new double[] {0.0, 0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
||||
((GridBagLayout)getLayout()).rowWeights = new double[] {0.0, 1.0, 0.0, 1.0E-4};
|
||||
|
||||
//---- hostLabel ----
|
||||
hostLabel.setText("Host:");
|
||||
add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
add(hostTextField, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
clearButton.setText("Clear");
|
||||
clearButton.addActionListener(this::clearActionPerformed);
|
||||
add(clearButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
JLabel hostLabel = new JLabel("Host:");
|
||||
|
||||
hostComboBox.setMaximumRowCount(5);
|
||||
add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
JButton clearButton = new JButton("Clear");
|
||||
JButton actionButton = new JButton("Action");
|
||||
JPanel menuPanel = new JPanel(new GridLayout(1, 1));
|
||||
menuPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
menuPanel.add(clearButton);
|
||||
menu.add(menuPanel);
|
||||
|
||||
hostTextField = new JTextField();
|
||||
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||
splitPane.setVisible(false);
|
||||
add(splitPane, new GridBagConstraints(1, 1, 3, 3, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
dataTabbedPane = new JTabbedPane(JTabbedPane.TOP);
|
||||
|
||||
actionButton.addActionListener(e -> {
|
||||
int x = 0;
|
||||
int y = actionButton.getHeight();
|
||||
menu.show(actionButton, x, y);
|
||||
});
|
||||
|
||||
clearButton.addActionListener(this::clearActionPerformed);
|
||||
|
||||
splitPane.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
@@ -116,12 +75,27 @@ public class Databoard extends JPanel {
|
||||
}
|
||||
});
|
||||
|
||||
splitPane.setVisible(false);
|
||||
|
||||
add(hostLabel, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
add(hostTextField, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
add(actionButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
add(splitPane, new GridBagConstraints(1, 1, 3, 3, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
hostComboBox.setMaximumRowCount(5);
|
||||
add(hostComboBox, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(8, 0, 5, 5), 0, 0));
|
||||
|
||||
setAutoMatch();
|
||||
}
|
||||
|
||||
private void resizePanel() {
|
||||
splitPane.setDividerLocation(0.4);
|
||||
TableColumnModel columnModel = table.getColumnModel();
|
||||
TableColumnModel columnModel = messageTable.getColumnModel();
|
||||
int totalWidth = (int) (getWidth() * 0.6);
|
||||
columnModel.getColumn(0).setPreferredWidth((int) (totalWidth * 0.1));
|
||||
columnModel.getColumn(1).setPreferredWidth((int) (totalWidth * 0.3));
|
||||
@@ -131,16 +105,7 @@ public class Databoard extends JPanel {
|
||||
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
|
||||
}
|
||||
|
||||
private static List<String> getHostByList() {
|
||||
return new ArrayList<>(ConfigEntry.globalDataMap.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入自动匹配
|
||||
*/
|
||||
private void setAutoMatch() {
|
||||
populateComboBoxModel();
|
||||
|
||||
hostComboBox.setSelectedItem(null);
|
||||
hostComboBox.addActionListener(this::handleComboBoxAction);
|
||||
|
||||
@@ -170,12 +135,6 @@ public class Databoard extends JPanel {
|
||||
});
|
||||
}
|
||||
|
||||
private void populateComboBoxModel() {
|
||||
for (String host : getHostByList()) {
|
||||
comboBoxModel.addElement(host);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleComboBoxAction(ActionEvent e) {
|
||||
if (!isMatchHost && hostComboBox.getSelectedItem() != null) {
|
||||
String selectedHost = hostComboBox.getSelectedItem().toString();
|
||||
@@ -183,7 +142,7 @@ public class Databoard extends JPanel {
|
||||
populateTabbedPaneByHost(selectedHost);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void handleKeyEvents(KeyEvent e) {
|
||||
isMatchHost = true;
|
||||
int keyCode = e.getKeyCode();
|
||||
@@ -213,7 +172,6 @@ public class Databoard extends JPanel {
|
||||
isMatchHost = true;
|
||||
comboBoxModel.removeAllElements();
|
||||
String input = hostTextField.getText().toLowerCase();
|
||||
|
||||
if (!input.isEmpty()) {
|
||||
for (String host : getHostByList()) {
|
||||
String lowerCaseHost = host.toLowerCase();
|
||||
@@ -232,36 +190,20 @@ public class Databoard extends JPanel {
|
||||
isMatchHost = false;
|
||||
}
|
||||
|
||||
private void applyHostFilter(String filterText) {
|
||||
TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) table.getRowSorter();
|
||||
|
||||
String cleanedText = StringHelper.replaceFirstOccurrence(filterText, "*.", "");
|
||||
|
||||
if (cleanedText.contains("*")) {
|
||||
cleanedText = "";
|
||||
}
|
||||
|
||||
RowFilter<TableModel, Integer> filter = RowFilter.regexFilter(cleanedText, 1);
|
||||
sorter.setRowFilter(filter);
|
||||
|
||||
messagePanel.applyHostFilter(filterText);
|
||||
}
|
||||
|
||||
private void populateTabbedPaneByHost(String selectedHost) {
|
||||
if (!Objects.equals(selectedHost, "")) {
|
||||
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = ConfigEntry.globalDataMap;
|
||||
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
|
||||
Map<String, List<String>> selectedDataMap;
|
||||
|
||||
dataTabbedPane.removeAll();
|
||||
dataTabbedPane.setPreferredSize(new Dimension(500,0));
|
||||
dataTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
|
||||
dataTabbedPane.removeChangeListener(changeListenerInstance);
|
||||
splitPane.setLeftComponent(dataTabbedPane);
|
||||
|
||||
if (selectedHost.contains("*")) {
|
||||
// 通配符数据
|
||||
selectedDataMap = new HashMap<>();
|
||||
String hostPattern = StringHelper.replaceFirstOccurrence(selectedHost, "*.", "");
|
||||
String hostPattern = StringProcessor.replaceFirstOccurrence(selectedHost, "*.", "");
|
||||
for (String key : dataMap.keySet()) {
|
||||
if (key.contains(hostPattern) || selectedHost.equals("*")) {
|
||||
Map<String, List<String>> ruleMap = dataMap.get(key);
|
||||
@@ -282,71 +224,62 @@ public class Databoard extends JPanel {
|
||||
selectedDataMap = dataMap.get(selectedHost);
|
||||
}
|
||||
|
||||
if (selectedHost.equals("**")) {
|
||||
if (currentWorker != null && !currentWorker.isDone()) {
|
||||
currentWorker.cancel(true);
|
||||
}
|
||||
for (ConcurrentHashMap.Entry<String, Map<String, List<String>>> entry : dataMap.entrySet()) {
|
||||
JTabbedPane newTabbedPane = new JTabbedPane();
|
||||
newTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
|
||||
for (Map.Entry<String, List<String>> entrySet : entry.getValue().entrySet()) {
|
||||
currentWorker = new SwingWorker<Object, Void>() {
|
||||
@Override
|
||||
protected Object[] doInBackground() throws Exception {
|
||||
String tabTitle = String.format("%s (%s)", entrySet.getKey(),
|
||||
entrySet.getValue().size());
|
||||
DatatablePanel datatablePanel = new DatatablePanel(entrySet.getKey(),
|
||||
entrySet.getValue());
|
||||
datatablePanel.setTableListener(messagePanel);
|
||||
return new Object[] {tabTitle, datatablePanel};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
if (!isCancelled()) {
|
||||
try {
|
||||
Object[] result = (Object[]) get();
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
newTabbedPane.addTab(result[0].toString(), (DatatablePanel) result[1]);
|
||||
dataTabbedPane.addTab(entry.getKey(), newTabbedPane);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
currentWorker.execute();
|
||||
}
|
||||
}
|
||||
|
||||
dataTabbedPane.addChangeListener(changeListenerInstance);
|
||||
} else {
|
||||
for (Map.Entry<String, List<String>> entry : selectedDataMap.entrySet()) {
|
||||
String tabTitle = String.format("%s (%s)", entry.getKey(), entry.getValue().size());
|
||||
DatatablePanel datatablePanel = new DatatablePanel(entry.getKey(), entry.getValue());
|
||||
datatablePanel.setTableListener(messagePanel);
|
||||
dataTabbedPane.addTab(tabTitle, datatablePanel);
|
||||
}
|
||||
for (Map.Entry<String, List<String>> entry : selectedDataMap.entrySet()) {
|
||||
String tabTitle = String.format("%s (%s)", entry.getKey(), entry.getValue().size());
|
||||
Datatable datatablePanel = new Datatable(api, entry.getKey(), entry.getValue());
|
||||
datatablePanel.setTableListener(messageTableModel);
|
||||
dataTabbedPane.addTab(tabTitle, datatablePanel);
|
||||
}
|
||||
|
||||
// 展示请求消息表单
|
||||
JSplitPane messageSplitPane = this.messagePanel.getPanel();
|
||||
JSplitPane messageSplitPane = messageTableModel.getSplitPane();
|
||||
this.splitPane.setRightComponent(messageSplitPane);
|
||||
table = this.messagePanel.getTable();
|
||||
messageTable = messageTableModel.getMessageTable();
|
||||
|
||||
resizePanel();
|
||||
splitPane.setVisible(true);
|
||||
|
||||
applyHostFilter(selectedHost);
|
||||
|
||||
// 主动调用一次stateChanged,使得dataTabbedPane可以精准展示内容
|
||||
if (selectedHost.equals("**")) {
|
||||
changeListenerInstance.stateChanged(null);
|
||||
}
|
||||
|
||||
hostTextField.setText(selectedHost);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyHostFilter(String filterText) {
|
||||
TableRowSorter<TableModel> sorter = (TableRowSorter<TableModel>) messageTable.getRowSorter();
|
||||
|
||||
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
||||
|
||||
if (cleanedText.contains("*")) {
|
||||
cleanedText = "";
|
||||
}
|
||||
|
||||
RowFilter<TableModel, Integer> filter = RowFilter.regexFilter(cleanedText, 1);
|
||||
sorter.setRowFilter(filter);
|
||||
|
||||
messageTableModel.applyHostFilter(filterText);
|
||||
}
|
||||
|
||||
private List<String> getHostByList() {
|
||||
return new ArrayList<>(Config.globalDataMap.keySet());
|
||||
}
|
||||
|
||||
private void clearActionPerformed(ActionEvent e) {
|
||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to clear data?", "Info",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
String host = hostTextField.getText();
|
||||
if (retCode == JOptionPane.YES_OPTION && !host.isEmpty()) {
|
||||
dataTabbedPane.removeAll();
|
||||
splitPane.setVisible(false);
|
||||
|
||||
String cleanedHost = StringProcessor.replaceFirstOccurrence(host, "*.", "");
|
||||
|
||||
if (host.contains("*")) {
|
||||
Config.globalDataMap.keySet().removeIf(i -> i.contains(cleanedHost) || cleanedHost.contains("*"));
|
||||
} else {
|
||||
Config.globalDataMap.remove(host);
|
||||
}
|
||||
|
||||
messageTableModel.deleteByHost(cleanedHost);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,48 @@
|
||||
package burp.ui.board;
|
||||
package hae.component.board;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import jregex.Pattern;
|
||||
import jregex.REFlags;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Comparator;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.RowFilter;
|
||||
import javax.swing.ScrollPaneConstants;
|
||||
import javax.swing.TransferHandler;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import javax.swing.*;
|
||||
import java.awt.datatransfer.*;
|
||||
import javax.swing.event.*;
|
||||
import javax.swing.table.*;
|
||||
|
||||
public class DatatablePanel extends JPanel {
|
||||
private final JTable table;
|
||||
private final DefaultTableModel model;
|
||||
public class Datatable extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final JTable dataTable;
|
||||
private final DefaultTableModel dataTableModel;
|
||||
private final JTextField searchField;
|
||||
private TableRowSorter<DefaultTableModel> sorter;
|
||||
private JScrollPane scrollPane;
|
||||
private String tableName;
|
||||
private JCheckBox searchMode = new JCheckBox("Reverse search");
|
||||
private final TableRowSorter<DefaultTableModel> sorter;
|
||||
private final JCheckBox searchMode = new JCheckBox("Reverse search");
|
||||
private final String tabName;
|
||||
|
||||
public Datatable(MontoyaApi api, String tabName, List<String> dataList) {
|
||||
this.api = api;
|
||||
this.tabName = tabName;
|
||||
|
||||
public DatatablePanel(String tableName, List<String> list) {
|
||||
this.tableName = tableName;
|
||||
|
||||
String[] columnNames = {"#", "Information"};
|
||||
model = new DefaultTableModel(columnNames, 0);
|
||||
table = new JTable(model);
|
||||
sorter = new TableRowSorter<>(model);
|
||||
|
||||
dataTableModel = new DefaultTableModel(columnNames, 0);
|
||||
dataTable = new JTable(dataTableModel);
|
||||
sorter = new TableRowSorter<>(dataTableModel);
|
||||
|
||||
searchField = new JTextField();
|
||||
|
||||
initComponents(dataList);
|
||||
}
|
||||
|
||||
private void initComponents(List<String> dataList) {
|
||||
// 设置ID排序
|
||||
sorter.setComparator(0, new Comparator<Integer>() {
|
||||
@Override
|
||||
@@ -61,38 +51,19 @@ public class DatatablePanel extends JPanel {
|
||||
}
|
||||
});
|
||||
|
||||
table.setRowSorter(sorter);
|
||||
TableColumn idColumn = table.getColumnModel().getColumn(0);
|
||||
dataTable.setRowSorter(sorter);
|
||||
TableColumn idColumn = dataTable.getColumnModel().getColumn(0);
|
||||
idColumn.setMaxWidth(50);
|
||||
|
||||
for (String item : list) {
|
||||
for (String item : dataList) {
|
||||
if (!item.isEmpty()) {
|
||||
addRowToTable(model, new Object[]{item});
|
||||
addRowToTable(new Object[]{item});
|
||||
}
|
||||
}
|
||||
|
||||
String defaultText = "Search";
|
||||
searchField = new JTextField(defaultText);
|
||||
|
||||
// 设置灰色默认文本Search
|
||||
searchField.setForeground(Color.GRAY);
|
||||
searchField.addFocusListener(new FocusListener() {
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
if (searchField.getText().equals(defaultText)) {
|
||||
searchField.setText("");
|
||||
searchField.setForeground(Color.BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
if (searchField.getText().isEmpty()) {
|
||||
searchField.setForeground(Color.GRAY);
|
||||
searchField.setText(defaultText);
|
||||
}
|
||||
}
|
||||
});
|
||||
// 设置灰色默认文本
|
||||
String searchText = "Search";
|
||||
addPlaceholder(searchField, searchText);
|
||||
|
||||
// 监听输入框内容输入、更新、删除
|
||||
searchField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
@@ -114,14 +85,10 @@ public class DatatablePanel extends JPanel {
|
||||
});
|
||||
|
||||
// 设置布局
|
||||
scrollPane = new JScrollPane(table);
|
||||
JScrollPane scrollPane = new JScrollPane(dataTable);
|
||||
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
|
||||
searchMode.addItemListener(new ItemListener() {
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
performSearch();
|
||||
}
|
||||
});
|
||||
searchMode.addItemListener(e -> performSearch());
|
||||
|
||||
setLayout(new BorderLayout(0, 5));
|
||||
|
||||
@@ -137,31 +104,62 @@ public class DatatablePanel extends JPanel {
|
||||
menu.add(menuPanel);
|
||||
|
||||
JButton settingsButton = new JButton("Settings");
|
||||
settingsButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
int x = settingsButton.getX();
|
||||
int y = settingsButton.getY() - menu.getPreferredSize().height;
|
||||
menu.show(settingsButton, x, y);
|
||||
}
|
||||
settingsButton.addActionListener(e -> {
|
||||
int x = settingsButton.getX();
|
||||
int y = settingsButton.getY() - menu.getPreferredSize().height;
|
||||
menu.show(settingsButton, x, y);
|
||||
});
|
||||
|
||||
optionsPanel.add(settingsButton);
|
||||
optionsPanel.add(Box.createHorizontalStrut(5));
|
||||
optionsPanel.add(searchField);
|
||||
|
||||
dataTable.setTransferHandler(new TransferHandler() {
|
||||
@Override
|
||||
public void exportToClipboard(JComponent comp, Clipboard clip, int action) throws IllegalStateException {
|
||||
if (comp instanceof JTable) {
|
||||
StringSelection stringSelection = new StringSelection(getSelectedData(
|
||||
(JTable) comp));
|
||||
clip.setContents(stringSelection, null);
|
||||
} else {
|
||||
super.exportToClipboard(comp, clip, action);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add(scrollPane, BorderLayout.CENTER);
|
||||
add(optionsPanel, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
private static void addRowToTable(DefaultTableModel model, Object[] data) {
|
||||
// 获取当前ID
|
||||
int rowCount = model.getRowCount();
|
||||
int id = rowCount > 0 ? (Integer) model.getValueAt(rowCount - 1, 0) + 1 : 1;
|
||||
public static void addPlaceholder(JTextField textField, String placeholderText) {
|
||||
textField.setForeground(Color.GRAY);
|
||||
textField.setText(placeholderText);
|
||||
textField.addFocusListener(new FocusListener() {
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
if (textField.getText().equals(placeholderText)) {
|
||||
textField.setText("");
|
||||
textField.setForeground(Color.BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
if (textField.getText().isEmpty()) {
|
||||
textField.setForeground(Color.GRAY);
|
||||
textField.setText(placeholderText);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addRowToTable(Object[] data) {
|
||||
int rowCount = dataTableModel.getRowCount();
|
||||
int id = rowCount > 0 ? (Integer) dataTableModel.getValueAt(rowCount - 1, 0) + 1 : 1;
|
||||
Object[] rowData = new Object[data.length + 1];
|
||||
rowData[0] = id; // 设置ID列的值
|
||||
System.arraycopy(data, 0, rowData, 1, data.length); // 拷贝其余数据
|
||||
model.addRow(rowData); // 添加行
|
||||
rowData[0] = id;
|
||||
System.arraycopy(data, 0, rowData, 1, data.length);
|
||||
dataTableModel.addRow(rowData);
|
||||
}
|
||||
|
||||
private void performSearch() {
|
||||
@@ -188,38 +186,7 @@ public class DatatablePanel extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
public void setTableListener(MessagePanel messagePanel) {
|
||||
table.setDefaultEditor(Object.class, null);
|
||||
|
||||
// 表格内容双击事件
|
||||
table.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
int selectedRow = table.getSelectedRow();
|
||||
if (selectedRow != -1) {
|
||||
String rowData = table.getValueAt(selectedRow, 1).toString();
|
||||
messagePanel.applyMessageFilter(tableName, rowData);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
table.setTransferHandler(new TransferHandler() {
|
||||
@Override
|
||||
public void exportToClipboard(JComponent comp, Clipboard clip, int action) throws IllegalStateException {
|
||||
if (comp instanceof JTable) {
|
||||
StringSelection stringSelection = new StringSelection(getSelectedData(
|
||||
(JTable) comp));
|
||||
clip.setContents(stringSelection, null);
|
||||
} else {
|
||||
super.exportToClipboard(comp, clip, action);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String getSelectedData(JTable table) {
|
||||
public static String getSelectedData(JTable table) {
|
||||
int[] selectRows = table.getSelectedRows();
|
||||
StringBuilder selectData = new StringBuilder();
|
||||
for (int row : selectRows) {
|
||||
@@ -227,14 +194,33 @@ public class DatatablePanel extends JPanel {
|
||||
}
|
||||
|
||||
// 便于单行复制,去除最后一个换行符
|
||||
if (selectData.length() > 0){
|
||||
if (!selectData.isEmpty()){
|
||||
selectData.deleteCharAt(selectData.length() - 1);
|
||||
}
|
||||
|
||||
return selectData.toString();
|
||||
}
|
||||
|
||||
public JTable getTable() {
|
||||
return this.table;
|
||||
public JTable getDataTable() {
|
||||
return this.dataTable;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTableListener(MessageTableModel messagePanel) {
|
||||
dataTable.setDefaultEditor(Object.class, null);
|
||||
|
||||
// 表格内容双击事件
|
||||
dataTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2) {
|
||||
int selectedRow = dataTable.getSelectedRow();
|
||||
if (selectedRow != -1) {
|
||||
String rowData = dataTable.getValueAt(selectedRow, 1).toString();
|
||||
messagePanel.applyMessageFilter(tabName, rowData);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
package burp.ui.board;
|
||||
package hae.component.board.message;
|
||||
|
||||
import burp.IHttpRequestResponsePersisted;
|
||||
import java.net.URL;
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
|
||||
public class LogEntry {
|
||||
public class MessageEntry {
|
||||
|
||||
private final String comment;
|
||||
private final IHttpRequestResponsePersisted requestResponse;
|
||||
private final URL url;
|
||||
private final HttpRequestResponse requestResponse;
|
||||
private final String url;
|
||||
private final String length;
|
||||
private final String status;
|
||||
private final String color;
|
||||
private final String method;
|
||||
|
||||
LogEntry(IHttpRequestResponsePersisted requestResponse, String method, URL url, String comment, String length, String color, String status) {
|
||||
MessageEntry(HttpRequestResponse requestResponse, String method, String url, String comment, String length, String color, String status) {
|
||||
this.requestResponse = requestResponse;
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
@@ -27,7 +26,7 @@ public class LogEntry {
|
||||
return this.color;
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
public String getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
@@ -47,7 +46,7 @@ public class LogEntry {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public IHttpRequestResponsePersisted getRequestResponse() {
|
||||
public HttpRequestResponse getRequestResponse() {
|
||||
return this.requestResponse;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package burp.ui.board;
|
||||
package hae.component.board.message;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
@@ -8,13 +8,13 @@ import java.util.Map;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
|
||||
public class ColorRenderer extends DefaultTableCellRenderer {
|
||||
public class MessageRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
private List<LogEntry> log;
|
||||
private List<MessageEntry> log;
|
||||
private Map<String, Color> colorMap = new HashMap<>();
|
||||
private JTable table; // 保存对表格的引用
|
||||
|
||||
public ColorRenderer(List<LogEntry> log, JTable table) {
|
||||
public MessageRenderer(List<MessageEntry> log, JTable table) {
|
||||
this.log = log;
|
||||
// 与BurpSuite的颜色保持一致
|
||||
this.colorMap.put("red", new Color(0xFF, 0x64, 0x64));
|
||||
@@ -31,13 +31,13 @@ public class ColorRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
|
||||
boolean hasFocus, int row, int column) {
|
||||
boolean hasFocus, int row, int column) {
|
||||
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
LogEntry logEntry = log.get(table.convertRowIndexToModel(row)); // 使用convertRowIndexToModel方法转换行索引
|
||||
MessageEntry messageEntry = log.get(table.convertRowIndexToModel(row)); // 使用convertRowIndexToModel方法转换行索引
|
||||
|
||||
// 设置颜色
|
||||
String colorByLog = logEntry.getColor();
|
||||
String colorByLog = messageEntry.getColor();
|
||||
Color color = colorMap.get(colorByLog);
|
||||
|
||||
if (isSelected) {
|
||||
@@ -1,28 +1,22 @@
|
||||
package burp.ui.board;
|
||||
package hae.component.board.message;
|
||||
|
||||
import burp.IBurpExtenderCallbacks;
|
||||
import burp.IExtensionHelpers;
|
||||
import burp.IHttpRequestResponse;
|
||||
import burp.IHttpRequestResponsePersisted;
|
||||
import burp.IHttpService;
|
||||
import burp.IMessageEditor;
|
||||
import burp.IMessageEditorController;
|
||||
import burp.IRequestInfo;
|
||||
import burp.config.ConfigEntry;
|
||||
import burp.core.GlobalCachePool;
|
||||
import burp.core.utils.HashCalculator;
|
||||
import burp.core.utils.StringHelper;
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.core.ByteArray;
|
||||
import burp.api.montoya.http.message.HttpHeader;
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||
import burp.api.montoya.ui.UserInterface;
|
||||
import burp.api.montoya.ui.editor.HttpRequestEditor;
|
||||
import burp.api.montoya.ui.editor.HttpResponseEditor;
|
||||
import hae.Config;
|
||||
import hae.cache.CachePool;
|
||||
import hae.utils.string.HashCalculator;
|
||||
import hae.utils.string.StringProcessor;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.*;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
@@ -32,34 +26,37 @@ import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.TableModel;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author EvilChen
|
||||
*/
|
||||
import static burp.api.montoya.ui.editor.EditorOptions.READ_ONLY;
|
||||
|
||||
public class MessagePanel extends AbstractTableModel implements IMessageEditorController {
|
||||
private JSplitPane splitPane;
|
||||
private IMessageEditor requestViewer;
|
||||
private IMessageEditor responseViewer;
|
||||
private final IBurpExtenderCallbacks callbacks;
|
||||
private final List<LogEntry> log = new ArrayList<LogEntry>();
|
||||
private final List<LogEntry> filteredLog = new ArrayList<LogEntry>();
|
||||
private IHttpRequestResponse currentlyDisplayedItem;
|
||||
private final IExtensionHelpers helpers;
|
||||
private final Table logTable;
|
||||
public class MessageTableModel extends AbstractTableModel {
|
||||
private final MontoyaApi api;
|
||||
private final MessageTable messageTable;
|
||||
private final JTabbedPane messageTab;
|
||||
private final JSplitPane splitPane;
|
||||
private final List<MessageEntry> log = new ArrayList<MessageEntry>();
|
||||
private LinkedList<MessageEntry> filteredLog;
|
||||
|
||||
public MessagePanel(IBurpExtenderCallbacks callbacks, IExtensionHelpers helpers) {
|
||||
this.callbacks = callbacks;
|
||||
this.helpers = helpers;
|
||||
public MessageTableModel(MontoyaApi api) {
|
||||
this.filteredLog = new LinkedList<>();
|
||||
this.api = api;
|
||||
|
||||
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||||
messageTab = new JTabbedPane();
|
||||
UserInterface userInterface = api.userInterface();
|
||||
HttpRequestEditor requestViewer = userInterface.createHttpRequestEditor(READ_ONLY);
|
||||
HttpResponseEditor responseViewer = userInterface.createHttpResponseEditor(READ_ONLY);
|
||||
messageTab.addTab("Request", requestViewer.uiComponent());
|
||||
messageTab.addTab("Response", responseViewer.uiComponent());
|
||||
|
||||
logTable = new Table(MessagePanel.this);
|
||||
logTable.setDefaultRenderer(Object.class, new ColorRenderer(filteredLog, logTable));
|
||||
logTable.setAutoCreateRowSorter(true);
|
||||
// 请求条目表格
|
||||
messageTable = new MessageTable(MessageTableModel.this, requestViewer, responseViewer);
|
||||
messageTable.setDefaultRenderer(Object.class, new MessageRenderer(filteredLog, messageTable));
|
||||
messageTable.setAutoCreateRowSorter(true);
|
||||
|
||||
// Length字段根据大小进行排序
|
||||
TableRowSorter<DefaultTableModel> sorter = (TableRowSorter<DefaultTableModel>) logTable.getRowSorter();
|
||||
TableRowSorter<DefaultTableModel> sorter = (TableRowSorter<DefaultTableModel>) messageTable.getRowSorter();
|
||||
sorter.setComparator(4, new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String s1, String s2) {
|
||||
@@ -78,151 +75,136 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo
|
||||
return Integer.compare(index1, index2);
|
||||
}
|
||||
private int getIndex(String color) {
|
||||
for (int i = 0; i < ConfigEntry.colorArray.length; i++) {
|
||||
if (ConfigEntry.colorArray[i].equals(color)) {
|
||||
for (int i = 0; i < Config.color.length; i++) {
|
||||
if (Config.color[i].equals(color)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
});
|
||||
messageTable.setRowSorter(sorter);
|
||||
messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||||
|
||||
logTable.setRowSorter(sorter);
|
||||
logTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(logTable);
|
||||
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||||
// 请求/相应文本框
|
||||
JScrollPane scrollPane = new JScrollPane(messageTable);
|
||||
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
|
||||
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
splitPane.setLeftComponent(scrollPane);
|
||||
|
||||
JTabbedPane tabs = new JTabbedPane();
|
||||
requestViewer = callbacks.createMessageEditor(MessagePanel.this, false);
|
||||
|
||||
responseViewer = callbacks.createMessageEditor(MessagePanel.this, false);
|
||||
tabs.addTab("Request", requestViewer.getComponent());
|
||||
tabs.addTab("Response", responseViewer.getComponent());
|
||||
splitPane.setRightComponent(tabs);
|
||||
splitPane.setRightComponent(messageTab);
|
||||
}
|
||||
|
||||
public JSplitPane getPanel() {
|
||||
return splitPane;
|
||||
}
|
||||
public void add(HttpRequestResponse messageInfo, String comment, String color) {
|
||||
synchronized(log) {
|
||||
HttpRequest httpRequest = messageInfo.request();
|
||||
String url = httpRequest.url();
|
||||
String method = httpRequest.method();
|
||||
|
||||
public Table getTable() {
|
||||
return logTable;
|
||||
}
|
||||
HttpResponse httpResponse = messageInfo.response();
|
||||
String status = String.valueOf(httpResponse.statusCode());
|
||||
String length = String.valueOf(httpResponse.body().length());
|
||||
|
||||
public List<LogEntry> getLogs() {
|
||||
return log;
|
||||
}
|
||||
MessageEntry logEntry = new MessageEntry(messageInfo, method, url, comment, length, color, status);
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return filteredLog.size();
|
||||
}
|
||||
try {
|
||||
// 比较Hash,如若存在重复的请求或响应,则不放入消息内容里
|
||||
byte[] reqByteA = httpRequest.toByteArray().getBytes();
|
||||
byte[] resByteA = httpResponse.toByteArray().getBytes();
|
||||
boolean isDuplicate = false;
|
||||
|
||||
@Override
|
||||
public int getColumnCount()
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
if (log.size() > 0) {
|
||||
for (MessageEntry entry : log) {
|
||||
HttpRequestResponse reqResMessage = entry.getRequestResponse();
|
||||
byte[] reqByteB = reqResMessage.request().toByteArray().getBytes();
|
||||
byte[] resByteB = reqResMessage.response().toByteArray().getBytes();
|
||||
try {
|
||||
// 通过URL、请求和响应报文、匹配数据内容,多维度进行对比
|
||||
if ((entry.getUrl().toString().equals(url.toString()) || (Arrays.equals(reqByteB, reqByteA) || Arrays.equals(resByteB, resByteA))) && (areMapsEqual(getCacheData(reqByteB), getCacheData(reqByteA)) && areMapsEqual(getCacheData(resByteB), getCacheData(resByteA)))) {
|
||||
isDuplicate = true;
|
||||
break;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int columnIndex)
|
||||
{
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0:
|
||||
return "Method";
|
||||
case 1:
|
||||
return "URL";
|
||||
case 2:
|
||||
return "Comment";
|
||||
case 3:
|
||||
return "Status";
|
||||
case 4:
|
||||
return "Length";
|
||||
case 5:
|
||||
return "Color";
|
||||
default:
|
||||
return "";
|
||||
if (!isDuplicate) {
|
||||
log.add(logEntry);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass(int columnIndex)
|
||||
{
|
||||
return String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
if (filteredLog.isEmpty()) {
|
||||
return "";
|
||||
public void deleteByHost(String filterText) {
|
||||
filteredLog.clear();
|
||||
List<Integer> rowsToRemove = new ArrayList<>();
|
||||
for (int i = 0; i < log.size(); i++) {
|
||||
MessageEntry entry = log.get(i);
|
||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
||||
if (!host.isEmpty()) {
|
||||
if (StringProcessor.matchFromEnd(host, filterText) || filterText.contains("*")) {
|
||||
rowsToRemove.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
LogEntry logEntry = filteredLog.get(rowIndex);
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0:
|
||||
return logEntry.getMethod();
|
||||
case 1:
|
||||
return logEntry.getUrl().toString();
|
||||
case 2:
|
||||
return logEntry.getComment();
|
||||
case 3:
|
||||
return logEntry.getStatus();
|
||||
case 4:
|
||||
return logEntry.getLength();
|
||||
case 5:
|
||||
return logEntry.getColor();
|
||||
default:
|
||||
return "";
|
||||
|
||||
for (int i = rowsToRemove.size() - 1; i >= 0; i--) {
|
||||
int row = rowsToRemove.get(i);
|
||||
log.remove(row);
|
||||
}
|
||||
|
||||
if (!rowsToRemove.isEmpty()) {
|
||||
int[] rows = rowsToRemove.stream().mapToInt(Integer::intValue).toArray();
|
||||
fireTableRowsDeleted(rows[0], rows[rows.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
public void applyHostFilter(String filterText) {
|
||||
filteredLog.clear();
|
||||
fireTableDataChanged();
|
||||
String cleanedText = StringHelper.replaceFirstOccurrence(filterText, "*.", "");
|
||||
String cleanedText = StringProcessor.replaceFirstOccurrence(filterText, "*.", "");
|
||||
|
||||
for (LogEntry entry : log) {
|
||||
String host = entry.getUrl().getHost();
|
||||
if (filterText.contains("*.") && StringHelper.matchFromEnd(host, cleanedText)) {
|
||||
filteredLog.add(entry);
|
||||
} else if (host.equals(filterText) || filterText.contains("*")) {
|
||||
filteredLog.add(entry);
|
||||
for (MessageEntry entry : log) {
|
||||
String host = StringProcessor.getHostByUrl(entry.getUrl());
|
||||
if (!host.isEmpty()) {
|
||||
if (filterText.contains("*.") && StringProcessor.matchFromEnd(host, cleanedText)) {
|
||||
filteredLog.add(entry);
|
||||
} else if (host.equals(filterText) || filterText.contains("*")) {
|
||||
filteredLog.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
public void applyMessageFilter(String tableName, String filterText) {
|
||||
filteredLog.clear();
|
||||
for (LogEntry entry : log) {
|
||||
IHttpRequestResponsePersisted requestResponse = entry.getRequestResponse();
|
||||
byte[] requestByte = requestResponse.getRequest();
|
||||
byte[] responseByte = requestResponse.getResponse();
|
||||
for (MessageEntry entry : log) {
|
||||
HttpRequestResponse requestResponse = entry.getRequestResponse();
|
||||
HttpRequest httpRequest = requestResponse.request();
|
||||
HttpResponse httpResponse = requestResponse.response();
|
||||
|
||||
String requestString = new String(requestResponse.getRequest(), StandardCharsets.UTF_8);
|
||||
String responseString = new String(requestResponse.getResponse(), StandardCharsets.UTF_8);
|
||||
String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String requestHeaders = httpRequest.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
List<String> requestTmpHeaders = helpers.analyzeRequest(requestByte).getHeaders();
|
||||
String requestHeaders = new String(String.join("\n", requestTmpHeaders).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
|
||||
int requestBodyOffset = helpers.analyzeRequest(requestByte).getBodyOffset();
|
||||
String requestBody = new String(Arrays.copyOfRange(requestByte, requestBodyOffset, requestByte.length), StandardCharsets.UTF_8);
|
||||
|
||||
List<String> responseTmpHeaders = helpers.analyzeResponse(responseByte).getHeaders();
|
||||
String responseHeaders = new String(String.join("\n", responseTmpHeaders).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
|
||||
int responseBodyOffset = helpers.analyzeResponse(responseByte).getBodyOffset();
|
||||
String responseBody = new String(Arrays.copyOfRange(responseByte, responseBodyOffset, responseByte.length), StandardCharsets.UTF_8);
|
||||
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"));
|
||||
|
||||
// 标志变量,表示是否满足过滤条件
|
||||
AtomicBoolean isMatched = new AtomicBoolean(false);
|
||||
|
||||
ConfigEntry.globalRules.keySet().forEach(i -> {
|
||||
for (Object[] objects : ConfigEntry.globalRules.get(i)) {
|
||||
Config.globalRules.keySet().forEach(i -> {
|
||||
for (Object[] objects : Config.globalRules.get(i)) {
|
||||
String name = objects[1].toString();
|
||||
String format = objects[4].toString();
|
||||
String scope = objects[6].toString();
|
||||
@@ -276,8 +258,9 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo
|
||||
filteredLog.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
fireTableDataChanged();
|
||||
logTable.lastSelectedIndex = -1;
|
||||
messageTable.lastSelectedIndex = -1;
|
||||
}
|
||||
|
||||
private boolean matchingString(String format, String filterText, String target) {
|
||||
@@ -300,92 +283,9 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo
|
||||
return isMatch;
|
||||
}
|
||||
|
||||
public void deleteByHost(String filterText) {
|
||||
filteredLog.clear();
|
||||
List<Integer> rowsToRemove = new ArrayList<>();
|
||||
for (int i = 0; i < log.size(); i++) {
|
||||
LogEntry entry = log.get(i);
|
||||
String host = entry.getUrl().getHost();
|
||||
if (StringHelper.matchFromEnd(host, filterText) || filterText.contains("*")) {
|
||||
rowsToRemove.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = rowsToRemove.size() - 1; i >= 0; i--) {
|
||||
int row = rowsToRemove.get(i);
|
||||
log.remove(row);
|
||||
}
|
||||
|
||||
if (!rowsToRemove.isEmpty()) {
|
||||
int[] rows = rowsToRemove.stream().mapToInt(Integer::intValue).toArray();
|
||||
fireTableRowsDeleted(rows[0], rows[rows.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRequest()
|
||||
{
|
||||
return currentlyDisplayedItem.getRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getResponse()
|
||||
{
|
||||
return currentlyDisplayedItem.getResponse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHttpService getHttpService()
|
||||
{
|
||||
return currentlyDisplayedItem.getHttpService();
|
||||
}
|
||||
|
||||
public void add(IHttpRequestResponse messageInfo, String comment, String color) {
|
||||
synchronized(log) {
|
||||
IRequestInfo iRequestInfo = helpers.analyzeRequest(messageInfo);
|
||||
URL url = iRequestInfo.getUrl();
|
||||
String method = iRequestInfo.getMethod();
|
||||
String status = String.valueOf(helpers.analyzeResponse(messageInfo.getResponse()).getStatusCode());
|
||||
String length = String.valueOf(messageInfo.getResponse().length);
|
||||
LogEntry logEntry = new LogEntry(callbacks.saveBuffersToTempFiles(messageInfo), method, url, comment, length, color, status);
|
||||
|
||||
try {
|
||||
// 比较Hash,如若存在重复的请求或响应,则不放入消息内容里
|
||||
byte[] reqByteA = messageInfo.getRequest();
|
||||
byte[] resByteA = messageInfo.getResponse();
|
||||
boolean isDuplicate = false;
|
||||
|
||||
if (log.size() > 0) {
|
||||
for (LogEntry entry : log) {
|
||||
IHttpRequestResponsePersisted reqResMessage = entry.getRequestResponse();
|
||||
byte[] reqByteB = reqResMessage.getRequest();
|
||||
byte[] resByteB = reqResMessage.getResponse();
|
||||
try {
|
||||
// 通过URL、请求和响应报文、匹配数据内容,多维度进行对比
|
||||
if ((entry.getUrl().toString().equals(url.toString()) || (Arrays.equals(reqByteB, reqByteA) || Arrays.equals(resByteB, resByteA))) && (areMapsEqual(getCacheData(reqByteB), getCacheData(reqByteA)) && areMapsEqual(getCacheData(resByteB), getCacheData(resByteA)))) {
|
||||
isDuplicate = true;
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDuplicate) {
|
||||
log.add(logEntry);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Map<String, Map<String, Object>> getCacheData(byte[] content)
|
||||
throws NoSuchAlgorithmException {
|
||||
private Map<String, Map<String, Object>> getCacheData(byte[] content) {
|
||||
String hashIndex = HashCalculator.calculateHash(content);
|
||||
return GlobalCachePool.getFromCache(hashIndex);
|
||||
return CachePool.getFromCache(hashIndex);
|
||||
}
|
||||
|
||||
private boolean areMapsEqual(Map<String, Map<String, Object>> map1, Map<String, Map<String, Object>> map2) {
|
||||
@@ -433,15 +333,78 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo
|
||||
return true;
|
||||
}
|
||||
|
||||
public class Table extends JTable {
|
||||
LogEntry logEntry;
|
||||
|
||||
public JSplitPane getSplitPane()
|
||||
{
|
||||
return splitPane;
|
||||
}
|
||||
|
||||
public MessageTable getMessageTable()
|
||||
{
|
||||
return messageTable;
|
||||
}
|
||||
|
||||
public List<MessageEntry> getLogs() {
|
||||
return log;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return filteredLog.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
if (filteredLog.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
MessageEntry messageEntry = filteredLog.get(rowIndex);
|
||||
|
||||
return switch (columnIndex) {
|
||||
case 0 -> messageEntry.getMethod();
|
||||
case 1 -> messageEntry.getUrl();
|
||||
case 2 -> messageEntry.getComment();
|
||||
case 3 -> messageEntry.getStatus();
|
||||
case 4 -> messageEntry.getLength();
|
||||
case 5 -> messageEntry.getColor();
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int columnIndex)
|
||||
{
|
||||
return switch (columnIndex) {
|
||||
case 0 -> "Method";
|
||||
case 1 -> "URL";
|
||||
case 2 -> "Comment";
|
||||
case 3 -> "Status";
|
||||
case 4 -> "Length";
|
||||
case 5 -> "Color";
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
|
||||
public class MessageTable extends JTable {
|
||||
private MessageEntry MessageEntry;
|
||||
private SwingWorker<Object, Void> currentWorker;
|
||||
// 设置响应报文返回的最大长度为3MB
|
||||
private final int MAX_LENGTH = 3145728;
|
||||
private int lastSelectedIndex = -1;
|
||||
private final HttpRequestEditor requestEditor;
|
||||
private final HttpResponseEditor responseEditor;
|
||||
|
||||
public Table(TableModel tableModel) {
|
||||
super(tableModel);
|
||||
public MessageTable(TableModel messageTableModel, HttpRequestEditor requestEditor, HttpResponseEditor responseEditor) {
|
||||
super(messageTableModel);
|
||||
this.requestEditor = requestEditor;
|
||||
this.responseEditor = responseEditor;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -450,39 +413,36 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo
|
||||
int selectedIndex = convertRowIndexToModel(row);
|
||||
if (lastSelectedIndex != selectedIndex) {
|
||||
lastSelectedIndex = selectedIndex;
|
||||
logEntry = filteredLog.get(selectedIndex);
|
||||
MessageEntry = filteredLog.get(selectedIndex);
|
||||
|
||||
requestViewer.setMessage("Loading...".getBytes(), true);
|
||||
responseViewer.setMessage("Loading...".getBytes(), false);
|
||||
currentlyDisplayedItem = logEntry.getRequestResponse();
|
||||
requestEditor.setRequest(HttpRequest.httpRequest("Loading..."));
|
||||
responseEditor.setResponse(HttpResponse.httpResponse("Loading..."));
|
||||
|
||||
if (currentWorker != null && !currentWorker.isDone()) {
|
||||
currentWorker.cancel(true);
|
||||
}
|
||||
|
||||
currentWorker = new SwingWorker<Object, Void>() {
|
||||
currentWorker = new SwingWorker<>() {
|
||||
@Override
|
||||
protected byte[][] doInBackground() throws Exception {
|
||||
byte[] requestByte = logEntry.getRequestResponse().getRequest();
|
||||
byte[] responseByte = logEntry.getRequestResponse().getResponse();
|
||||
protected ByteArray[] doInBackground() {
|
||||
ByteArray requestByte = MessageEntry.getRequestResponse().request().toByteArray();
|
||||
ByteArray responseByte = MessageEntry.getRequestResponse().response().toByteArray();
|
||||
|
||||
if (responseByte.length > MAX_LENGTH) {
|
||||
if (responseByte.length() > MAX_LENGTH) {
|
||||
String ellipsis = "\r\n......";
|
||||
responseByte = Arrays.copyOf(responseByte, MAX_LENGTH + ellipsis.length());
|
||||
byte[] ellipsisBytes = ellipsis.getBytes();
|
||||
System.arraycopy(ellipsisBytes, 0, responseByte, MAX_LENGTH, ellipsisBytes.length);
|
||||
responseByte = responseByte.subArray(0, MAX_LENGTH).withAppended(ellipsis);
|
||||
}
|
||||
|
||||
return new byte[][] {requestByte, responseByte};
|
||||
return new ByteArray[]{requestByte, responseByte};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
if (!isCancelled()) {
|
||||
try {
|
||||
byte[][] result = (byte[][]) get();
|
||||
requestViewer.setMessage(result[0], true);
|
||||
responseViewer.setMessage(result[1], false);
|
||||
ByteArray[] result = (ByteArray[]) get();
|
||||
requestEditor.setRequest(HttpRequest.httpRequest(MessageEntry.getRequestResponse().httpService(), result[0]));
|
||||
responseEditor.setResponse(HttpResponse.httpResponse(result[1]));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@@ -493,5 +453,4 @@ public class MessagePanel extends AbstractTableModel implements IMessageEditorCo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
89
src/main/java/hae/component/config/Config.java
Normal file
89
src/main/java/hae/component/config/Config.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package hae.component.config;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.component.rule.Rules;
|
||||
import hae.utils.config.ConfigLoader;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
public class Config extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final Rules rules;
|
||||
|
||||
public Config(MontoyaApi api, ConfigLoader configLoader, Rules rules) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.rules = rules;
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout) getLayout()).columnWidths = new int[] {0, 0, 0, 0, 0};
|
||||
((GridBagLayout) getLayout()).rowHeights = new int[] {0, 0, 0};
|
||||
((GridBagLayout) getLayout()).columnWeights = new double[] {0.0, 1.0, 0.0, 0.0, 1.0E-4};
|
||||
((GridBagLayout) getLayout()).rowWeights = new double[] {0.0, 0.0, 1.0E-4};
|
||||
|
||||
JLabel rulesFilePathLabel = new JLabel("Rules Path:");
|
||||
JTextField rulesFilePathTextField = new JTextField();
|
||||
JButton onlineUpdateButton = new JButton("Update");
|
||||
JLabel excludeSuffixLabel = new JLabel("Exclude Suffix:");
|
||||
JTextField excludeSuffixTextField = new JTextField();
|
||||
JButton excludeSuffixSaveButton = new JButton("Save");
|
||||
JButton reloadButton = new JButton("Reload");
|
||||
|
||||
rulesFilePathTextField.setEditable(false);
|
||||
|
||||
onlineUpdateButton.addActionListener(this::onlineUpdateActionPerformed);
|
||||
excludeSuffixSaveButton.addActionListener(e -> excludeSuffixSaveActionPerformed(e, excludeSuffixTextField.getText()));
|
||||
reloadButton.addActionListener(this::reloadActionPerformed);
|
||||
|
||||
rulesFilePathTextField.setText(configLoader.getRulesFilePath());
|
||||
excludeSuffixTextField.setText(configLoader.getExcludeSuffix());
|
||||
|
||||
add(rulesFilePathTextField, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(5, 0, 5, 5), 0, 0));
|
||||
add(rulesFilePathLabel, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.WEST, GridBagConstraints.VERTICAL,
|
||||
new Insets(5, 5, 5, 5), 0, 0));
|
||||
add(onlineUpdateButton, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(5, 0, 5, 5), 0, 0));
|
||||
add(reloadButton, new GridBagConstraints(3, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(5, 0, 5, 5), 0, 0));
|
||||
add(excludeSuffixLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE,
|
||||
new Insets(0, 5, 5, 5), 0, 0));
|
||||
add(excludeSuffixTextField, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL,
|
||||
new Insets(0, 0, 0, 5), 0, 0));
|
||||
add(excludeSuffixSaveButton, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL,
|
||||
new Insets(0, 0, 0, 5), 0, 0));
|
||||
}
|
||||
|
||||
private void onlineUpdateActionPerformed(ActionEvent e) {
|
||||
// 添加提示框防止用户误触导致配置更新
|
||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to update rules?", "Info", JOptionPane.YES_NO_OPTION);
|
||||
if (retCode == JOptionPane.YES_OPTION) {
|
||||
configLoader.initRules();
|
||||
reloadActionPerformed(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void excludeSuffixSaveActionPerformed(ActionEvent e, String suffix) {
|
||||
if (!suffix.equals(configLoader.getExcludeSuffix()) && !suffix.isEmpty()) {
|
||||
configLoader.setExcludeSuffix(suffix);
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadActionPerformed(ActionEvent e) {
|
||||
rules.reloadRuleGroup();
|
||||
}
|
||||
}
|
||||
@@ -1,82 +1,79 @@
|
||||
package burp.ui.rule;
|
||||
|
||||
import java.awt.*;
|
||||
import javax.swing.*;
|
||||
import burp.config.ConfigEntry;
|
||||
|
||||
/**
|
||||
* @author LinChen & EvilChen
|
||||
*/
|
||||
|
||||
public class RuleSetting extends JPanel {
|
||||
public JTextField firstRegexTextField;
|
||||
public JTextField secondRegexTextField;
|
||||
public JTextField formatTextField;
|
||||
public JTextField ruleNameTextField;
|
||||
public JComboBox<String> scopeComboBox;
|
||||
public JComboBox<String> engineComboBox;
|
||||
public JComboBox<String> colorComboBox;
|
||||
public JComboBox<Boolean> sensitiveComboBox;
|
||||
|
||||
public RuleSetting() {
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new GridBagLayout());
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.fill = GridBagConstraints.BOTH;
|
||||
|
||||
addLabel("Name:", 0, c);
|
||||
ruleNameTextField = addTextField(0, c);
|
||||
|
||||
addLabel("F-Regex:", 1, c);
|
||||
firstRegexTextField = addTextField(1, c);
|
||||
|
||||
addLabel("S-Regex:", 2, c);
|
||||
secondRegexTextField = addTextField(2, c);
|
||||
|
||||
addLabel("Format:", 3, c);
|
||||
formatTextField = addTextField(3, c);
|
||||
|
||||
addLabel("Scope:", 4, c);
|
||||
scopeComboBox = addComboBox(ConfigEntry.scopeArray, 4, c);
|
||||
|
||||
addLabel("Engine:", 5, c);
|
||||
engineComboBox = addComboBox(ConfigEntry.engineArray, 5, c);
|
||||
engineComboBox.addActionListener(e -> {
|
||||
boolean isNfa = "nfa".equals(engineComboBox.getSelectedItem().toString());
|
||||
formatTextField.setEnabled(isNfa);
|
||||
formatTextField.setText(isNfa ? formatTextField.getText() : "{0}");
|
||||
});
|
||||
|
||||
addLabel("Color:", 6, c);
|
||||
colorComboBox = addComboBox(ConfigEntry.colorArray, 6, c);
|
||||
|
||||
addLabel("Sensitive:", 7, c);
|
||||
sensitiveComboBox = addComboBox(new Boolean[]{true, false}, 7, c);
|
||||
}
|
||||
|
||||
private void addLabel(String text, int y, GridBagConstraints c) {
|
||||
JLabel label = new JLabel(text);
|
||||
c.gridx = 0;
|
||||
c.gridy = y;
|
||||
add(label, c);
|
||||
}
|
||||
|
||||
private JTextField addTextField(int y, GridBagConstraints c) {
|
||||
JTextField textField = new JTextField(35);
|
||||
c.gridx = 1;
|
||||
c.gridy = y;
|
||||
add(textField, c);
|
||||
return textField;
|
||||
}
|
||||
|
||||
private <T> JComboBox<T> addComboBox(T[] items, int y, GridBagConstraints c) {
|
||||
JComboBox<T> comboBox = new JComboBox<>(items);
|
||||
c.gridx = 1;
|
||||
c.gridy = y;
|
||||
add(comboBox, c);
|
||||
return comboBox;
|
||||
}
|
||||
}
|
||||
package hae.component.rule;
|
||||
|
||||
import hae.Config;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
public class Display extends JPanel {
|
||||
public JTextField firstRegexTextField;
|
||||
public JTextField secondRegexTextField;
|
||||
public JTextField formatTextField;
|
||||
public JTextField ruleNameTextField;
|
||||
public JComboBox<String> scopeComboBox;
|
||||
public JComboBox<String> engineComboBox;
|
||||
public JComboBox<String> colorComboBox;
|
||||
public JComboBox<Boolean> sensitiveComboBox;
|
||||
|
||||
public Display() {
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new GridBagLayout());
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.fill = GridBagConstraints.BOTH;
|
||||
|
||||
addLabel("Name:", 0, c);
|
||||
ruleNameTextField = addTextField(0, c);
|
||||
|
||||
addLabel("F-Regex:", 1, c);
|
||||
firstRegexTextField = addTextField(1, c);
|
||||
|
||||
addLabel("S-Regex:", 2, c);
|
||||
secondRegexTextField = addTextField(2, c);
|
||||
|
||||
addLabel("Format:", 3, c);
|
||||
formatTextField = addTextField(3, c);
|
||||
|
||||
addLabel("Scope:", 4, c);
|
||||
scopeComboBox = addComboBox(Config.scope, 4, c);
|
||||
|
||||
addLabel("Engine:", 5, c);
|
||||
engineComboBox = addComboBox(Config.engine, 5, c);
|
||||
engineComboBox.addActionListener(e -> {
|
||||
boolean isNfa = "nfa".equals(engineComboBox.getSelectedItem().toString());
|
||||
formatTextField.setEnabled(isNfa);
|
||||
formatTextField.setText(isNfa ? formatTextField.getText() : "{0}");
|
||||
});
|
||||
|
||||
addLabel("Color:", 6, c);
|
||||
colorComboBox = addComboBox(Config.color, 6, c);
|
||||
|
||||
addLabel("Sensitive:", 7, c);
|
||||
sensitiveComboBox = addComboBox(new Boolean[]{true, false}, 7, c);
|
||||
}
|
||||
|
||||
private void addLabel(String text, int y, GridBagConstraints c) {
|
||||
JLabel label = new JLabel(text);
|
||||
c.gridx = 0;
|
||||
c.gridy = y;
|
||||
add(label, c);
|
||||
}
|
||||
|
||||
private JTextField addTextField(int y, GridBagConstraints c) {
|
||||
JTextField textField = new JTextField(35);
|
||||
c.gridx = 1;
|
||||
c.gridy = y;
|
||||
add(textField, c);
|
||||
return textField;
|
||||
}
|
||||
|
||||
private <T> JComboBox<T> addComboBox(T[] items, int y, GridBagConstraints c) {
|
||||
JComboBox<T> comboBox = new JComboBox<>(items);
|
||||
c.gridx = 1;
|
||||
c.gridy = y;
|
||||
add(comboBox, c);
|
||||
return comboBox;
|
||||
}
|
||||
}
|
||||
164
src/main/java/hae/component/rule/Rule.java
Normal file
164
src/main/java/hae/component/rule/Rule.java
Normal file
@@ -0,0 +1,164 @@
|
||||
package hae.component.rule;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.table.TableRowSorter;
|
||||
import java.util.Vector;
|
||||
|
||||
import hae.Config;
|
||||
import hae.utils.config.ConfigLoader;
|
||||
import hae.utils.rule.RuleProcessor;
|
||||
|
||||
import static javax.swing.JOptionPane.YES_OPTION;
|
||||
|
||||
public class Rule extends JPanel {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final RuleProcessor ruleProcessor;
|
||||
private final JTabbedPane tabbedPane;
|
||||
|
||||
public Rule(MontoyaApi api, ConfigLoader configLoader, Object[][] data, JTabbedPane tabbedPane) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.ruleProcessor = new RuleProcessor(api, configLoader);
|
||||
this.tabbedPane = tabbedPane;
|
||||
|
||||
initComponents(data);
|
||||
}
|
||||
|
||||
private void initComponents(Object[][] data) {
|
||||
setLayout(new GridBagLayout());
|
||||
((GridBagLayout)getLayout()).columnWidths = new int[] {0, 0, 0};
|
||||
((GridBagLayout)getLayout()).rowHeights = new int[] {0, 0, 0, 0, 0};
|
||||
((GridBagLayout)getLayout()).columnWeights = new double[] {0.0, 1.0, 1.0E-4};
|
||||
((GridBagLayout)getLayout()).rowWeights = new double[] {0.0, 0.0, 0.0, 1.0, 1.0E-4};
|
||||
|
||||
JButton addButton = new JButton("Add");
|
||||
JButton editButton = new JButton("Edit");
|
||||
JButton removeButton = new JButton("Remove");
|
||||
|
||||
JTable ruleTable = new JTable();
|
||||
JScrollPane scrollPane = new JScrollPane();
|
||||
|
||||
ruleTable.setShowVerticalLines(false);
|
||||
ruleTable.setShowHorizontalLines(false);
|
||||
ruleTable.setVerifyInputWhenFocusTarget(false);
|
||||
ruleTable.setUpdateSelectionOnSort(false);
|
||||
ruleTable.setSurrendersFocusOnKeystroke(true);
|
||||
scrollPane.setViewportView(ruleTable);
|
||||
|
||||
// 按钮监听事件
|
||||
addButton.addActionListener(e -> ruleAddActionPerformed(e, ruleTable, tabbedPane));
|
||||
editButton.addActionListener(e -> ruleEditActionPerformed(e, ruleTable, tabbedPane));
|
||||
removeButton.addActionListener(e -> ruleRemoveActionPerformed(e, ruleTable, tabbedPane));
|
||||
|
||||
// 表格
|
||||
DefaultTableModel model = new DefaultTableModel() {
|
||||
@Override
|
||||
public Class<?> getColumnClass(int column) {
|
||||
return (column == 0) ? Boolean.class : String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
return column == 0;
|
||||
}
|
||||
};
|
||||
|
||||
ruleTable.setModel(model);
|
||||
ruleTable.setRowSorter(new TableRowSorter<>(model));
|
||||
|
||||
model.setDataVector(data, Config.ruleFields);
|
||||
model.addTableModelListener(e -> {
|
||||
if (e.getColumn() == 0 && ruleTable.getSelectedRow() != -1){
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
ruleProcessor.changeRule(model.getDataVector().get(select), select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
}
|
||||
});
|
||||
|
||||
add(addButton, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(15, 5, 3, 2), 0, 0));
|
||||
add(editButton, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 5, 3, 2), 0, 0));
|
||||
add(removeButton, new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(0, 5, 3, 2), 0, 0));
|
||||
add(scrollPane, new GridBagConstraints(1, 0, 1, 4, 0.0, 0.0,
|
||||
GridBagConstraints.CENTER, GridBagConstraints.BOTH,
|
||||
new Insets(15, 5, 5, 5), 0, 0));
|
||||
}
|
||||
|
||||
private void ruleAddActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane) {
|
||||
Display ruleDisplay = new Display();
|
||||
ruleDisplay.formatTextField.setText("{0}");
|
||||
|
||||
int showState = JOptionPane.showConfirmDialog(null, ruleDisplay, "Add Rule", JOptionPane.OK_OPTION);
|
||||
if (showState == YES_OPTION) {
|
||||
Vector<Object> ruleData = new Vector<>();
|
||||
ruleData.add(false);
|
||||
ruleData.add(ruleDisplay.ruleNameTextField.getText());
|
||||
ruleData.add(ruleDisplay.firstRegexTextField.getText());
|
||||
ruleData.add(ruleDisplay.secondRegexTextField.getText());
|
||||
ruleData.add(ruleDisplay.formatTextField.getText());
|
||||
ruleData.add(ruleDisplay.colorComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleDisplay.scopeComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleDisplay.engineComboBox.getSelectedItem().toString());
|
||||
ruleData.add(ruleDisplay.sensitiveComboBox.getSelectedItem());
|
||||
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
model.insertRow(model.getRowCount(), ruleData);
|
||||
ruleProcessor.addRule(ruleData, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
|
||||
private void ruleEditActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane){
|
||||
if (ruleTable.getSelectedRowCount() >= 1){
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
Display ruleDisplay = new Display();
|
||||
|
||||
ruleDisplay.ruleNameTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 1).toString());
|
||||
ruleDisplay.firstRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 2).toString());
|
||||
ruleDisplay.secondRegexTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 3).toString());
|
||||
ruleDisplay.formatTextField.setText(ruleTable.getValueAt(ruleTable.getSelectedRow(), 4).toString());
|
||||
ruleDisplay.colorComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 5).toString());
|
||||
ruleDisplay.scopeComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 6).toString());
|
||||
ruleDisplay.engineComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(), 7).toString());
|
||||
ruleDisplay.sensitiveComboBox.setSelectedItem(ruleTable.getValueAt(ruleTable.getSelectedRow(),8));
|
||||
|
||||
ruleDisplay.formatTextField.setEnabled(ruleDisplay.engineComboBox.getSelectedItem().toString().equals("nfa"));
|
||||
|
||||
int showState = JOptionPane.showConfirmDialog(null, ruleDisplay, "Edit Rule", JOptionPane.OK_OPTION);
|
||||
if (showState == 0){
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
model.setValueAt(ruleDisplay.ruleNameTextField.getText(), select, 1);
|
||||
model.setValueAt(ruleDisplay.firstRegexTextField.getText(), select, 2);
|
||||
model.setValueAt(ruleDisplay.secondRegexTextField.getText(), select, 3);
|
||||
model.setValueAt(ruleDisplay.formatTextField.getText(), select, 4);
|
||||
model.setValueAt(ruleDisplay.colorComboBox.getSelectedItem().toString(), select, 5);
|
||||
model.setValueAt(ruleDisplay.scopeComboBox.getSelectedItem().toString(), select, 6);
|
||||
model.setValueAt(ruleDisplay.engineComboBox.getSelectedItem().toString(), select, 7);
|
||||
model.setValueAt(ruleDisplay.sensitiveComboBox.getSelectedItem(), select, 8);
|
||||
model = (DefaultTableModel) ruleTable.getModel();
|
||||
ruleProcessor.changeRule(model.getDataVector().get(select), select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ruleRemoveActionPerformed(ActionEvent e, JTable ruleTable, JTabbedPane tabbedPane){
|
||||
if (ruleTable.getSelectedRowCount() >= 1){
|
||||
if (JOptionPane.showConfirmDialog(null, "Are you sure you want to delete this rule?", "Info", JOptionPane.OK_OPTION) == 0){
|
||||
DefaultTableModel model = (DefaultTableModel) ruleTable.getModel();
|
||||
int select = ruleTable.convertRowIndexToModel(ruleTable.getSelectedRow());
|
||||
|
||||
model.removeRow(select);
|
||||
ruleProcessor.removeRule(select, tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
156
src/main/java/hae/component/rule/Rules.java
Normal file
156
src/main/java/hae/component/rule/Rules.java
Normal file
@@ -0,0 +1,156 @@
|
||||
package hae.component.rule;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.Config;
|
||||
import hae.utils.config.ConfigLoader;
|
||||
import hae.utils.rule.RuleProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
public class Rules extends JTabbedPane {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
private final RuleProcessor ruleProcessor;
|
||||
private final JTextField ruleGroupNameTextField;
|
||||
|
||||
private Component tabComponent;
|
||||
private int selectedIndex;
|
||||
|
||||
public Rules(MontoyaApi api, ConfigLoader configLoader) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
this.ruleProcessor = new RuleProcessor(api, configLoader);
|
||||
this.ruleGroupNameTextField = new JTextField();
|
||||
|
||||
initComponents();
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
reloadRuleGroup();
|
||||
|
||||
JTabbedPane tabbedPane = this;
|
||||
|
||||
JMenuItem deleteMenuItem = new JMenuItem("Delete");
|
||||
JPopupMenu popupMenu = new JPopupMenu();
|
||||
popupMenu.add(deleteMenuItem);
|
||||
|
||||
deleteMenuItem.addActionListener(this::deleteRuleGroupActionPerformed);
|
||||
|
||||
ruleGroupNameTextField.setBorder(BorderFactory.createEmptyBorder());
|
||||
ruleGroupNameTextField.addFocusListener(new FocusAdapter() {
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
renameTitleActionPerformed.actionPerformed(null);
|
||||
}
|
||||
});
|
||||
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
int index = getSelectedIndex();
|
||||
Rectangle r = getBoundsAt(index);
|
||||
if (r.contains(e.getPoint()) && index >= 0) {
|
||||
switch (e.getButton()) {
|
||||
case MouseEvent.BUTTON1:
|
||||
if (e.getClickCount() == 2) {
|
||||
selectedIndex = index;
|
||||
tabComponent = getTabComponentAt(selectedIndex);
|
||||
String ruleGroupName = getTitleAt(selectedIndex);
|
||||
|
||||
if (!"...".equals(ruleGroupName)) {
|
||||
setTabComponentAt(selectedIndex, ruleGroupNameTextField);
|
||||
ruleGroupNameTextField.setVisible(true);
|
||||
ruleGroupNameTextField.setText(ruleGroupName);
|
||||
ruleGroupNameTextField.selectAll();
|
||||
ruleGroupNameTextField.requestFocusInWindow();
|
||||
ruleGroupNameTextField.setMinimumSize(ruleGroupNameTextField.getPreferredSize());
|
||||
}
|
||||
} else if (e.getClickCount() == 1) {
|
||||
if ("...".equals(getTitleAt(getSelectedIndex()))) {
|
||||
String title = ruleProcessor.newRule();
|
||||
Rule newRule = new Rule(api, configLoader, Config.ruleTemplate, tabbedPane);
|
||||
insertTab(title, null, newRule, null, getTabCount() - 1);
|
||||
setSelectedIndex(getTabCount() - 2);
|
||||
} else {
|
||||
renameTitleActionPerformed.actionPerformed(null);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MouseEvent.BUTTON3:
|
||||
if (!"...".equals(getTitleAt(getSelectedIndex()))) {
|
||||
popupMenu.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
InputMap im = ruleGroupNameTextField.getInputMap(JComponent.WHEN_FOCUSED);
|
||||
ActionMap am = ruleGroupNameTextField.getActionMap();
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel");
|
||||
am.put("cancel", cancelActionPerformed);
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "rename");
|
||||
am.put("rename", renameTitleActionPerformed);
|
||||
}
|
||||
|
||||
public void reloadRuleGroup() {
|
||||
removeAll();
|
||||
Config.globalRules.keySet().forEach(i-> addTab(i, new Rule(api, configLoader, hae.Config.globalRules.get(i), this)));
|
||||
addTab("...", null);
|
||||
}
|
||||
|
||||
private void deleteRuleGroupActionPerformed(ActionEvent e) {
|
||||
if (getTabCount() > 2) {
|
||||
int retCode = JOptionPane.showConfirmDialog(null, "Do you want to delete this rule group?", "Info",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
if (retCode == JOptionPane.YES_OPTION) {
|
||||
String title = getTitleAt(getSelectedIndex());
|
||||
ruleProcessor.deleteRuleGroup(title);
|
||||
remove(getSelectedIndex());
|
||||
setSelectedIndex(getSelectedIndex() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Action renameTitleActionPerformed = new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
String title = ruleGroupNameTextField.getText();
|
||||
if (!title.isEmpty() && selectedIndex >= 0) {
|
||||
String oldName = getTitleAt(selectedIndex);
|
||||
setTitleAt(selectedIndex, title);
|
||||
|
||||
if (!oldName.equals(title)) {
|
||||
ruleProcessor.renameRuleGroup(oldName, title);
|
||||
}
|
||||
}
|
||||
cancelActionPerformed.actionPerformed(null);
|
||||
}
|
||||
};
|
||||
|
||||
private Action cancelActionPerformed = new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (selectedIndex >= 0) {
|
||||
setTabComponentAt(selectedIndex, tabComponent);
|
||||
|
||||
ruleGroupNameTextField.setVisible(false);
|
||||
ruleGroupNameTextField.setPreferredSize(null);
|
||||
selectedIndex = -1;
|
||||
tabComponent = null;
|
||||
|
||||
requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
115
src/main/java/hae/instances/editor/RequestEditor.java
Normal file
115
src/main/java/hae/instances/editor/RequestEditor.java
Normal file
@@ -0,0 +1,115 @@
|
||||
package hae.instances.editor;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.ui.editor.extension.EditorCreationContext;
|
||||
import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpRequestEditor;
|
||||
import burp.api.montoya.ui.editor.extension.HttpRequestEditorProvider;
|
||||
import burp.api.montoya.core.ByteArray;
|
||||
import burp.api.montoya.core.Range;
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||
import burp.api.montoya.ui.Selection;
|
||||
import hae.component.board.Datatable;
|
||||
import hae.instances.http.utils.MessageProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class RequestEditor implements HttpRequestEditorProvider {
|
||||
private final MontoyaApi api;
|
||||
|
||||
public RequestEditor(MontoyaApi api) {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionProvidedHttpRequestEditor provideHttpRequestEditor(EditorCreationContext editorCreationContext) {
|
||||
return new Editor(api, editorCreationContext);
|
||||
}
|
||||
|
||||
private static class Editor implements ExtensionProvidedHttpRequestEditor {
|
||||
private final MontoyaApi api;
|
||||
private final EditorCreationContext creationContext;
|
||||
private final MessageProcessor messageProcessor;
|
||||
private HttpRequestResponse requestResponse;
|
||||
|
||||
private JTabbedPane jTabbedPane = new JTabbedPane();
|
||||
|
||||
public Editor(MontoyaApi api, EditorCreationContext creationContext)
|
||||
{
|
||||
this.api = api;
|
||||
this.creationContext = creationContext;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpRequest getRequest() {
|
||||
return requestResponse.request();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestResponse(HttpRequestResponse requestResponse) {
|
||||
this.requestResponse = requestResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isEnabledFor(HttpRequestResponse requestResponse) {
|
||||
HttpRequest request = requestResponse.request();
|
||||
if (request != null && !request.bodyToString().equals("Loading...")) {
|
||||
List<Map<String, String>> result = messageProcessor.processRequest("", request, false);
|
||||
jTabbedPane = generateTabbedPaneFromResultMap(api, result);
|
||||
return jTabbedPane.getTabCount() > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String caption() {
|
||||
return "MarkInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component uiComponent() {
|
||||
return jTabbedPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Selection selectedData() {
|
||||
return new Selection() {
|
||||
@Override
|
||||
public ByteArray contents() {
|
||||
return ByteArray.byteArray(Datatable.getSelectedData(((Datatable) jTabbedPane.getSelectedComponent()).getDataTable()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range offsets() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static JTabbedPane generateTabbedPaneFromResultMap(MontoyaApi api, List<Map<String, String>> result) {
|
||||
JTabbedPane tabbedPane = new JTabbedPane();
|
||||
if (result != null && !result.isEmpty() && result.size() > 0) {
|
||||
Map<String, String> dataMap = result.get(0);
|
||||
if (dataMap != null && !dataMap.isEmpty() && dataMap.size() > 0) {
|
||||
dataMap.keySet().forEach(i->{
|
||||
String[] extractData = dataMap.get(i).split("\n");
|
||||
Datatable dataPanel = new Datatable(api, i, Arrays.asList(extractData));
|
||||
tabbedPane.addTab(i, dataPanel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return tabbedPane;
|
||||
}
|
||||
}
|
||||
98
src/main/java/hae/instances/editor/ResponseEditor.java
Normal file
98
src/main/java/hae/instances/editor/ResponseEditor.java
Normal file
@@ -0,0 +1,98 @@
|
||||
package hae.instances.editor;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
import burp.api.montoya.http.message.responses.HttpResponse;
|
||||
import burp.api.montoya.ui.editor.extension.EditorCreationContext;
|
||||
import burp.api.montoya.ui.editor.extension.ExtensionProvidedHttpResponseEditor;
|
||||
import burp.api.montoya.ui.editor.extension.HttpResponseEditorProvider;
|
||||
import burp.api.montoya.core.ByteArray;
|
||||
import burp.api.montoya.core.Range;
|
||||
import burp.api.montoya.ui.Selection;
|
||||
import hae.component.board.Datatable;
|
||||
import hae.instances.http.utils.MessageProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ResponseEditor implements HttpResponseEditorProvider {
|
||||
private final MontoyaApi api;
|
||||
|
||||
public ResponseEditor(MontoyaApi api) {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionProvidedHttpResponseEditor provideHttpResponseEditor(EditorCreationContext editorCreationContext) {
|
||||
return new Editor(api, editorCreationContext);
|
||||
}
|
||||
|
||||
private static class Editor implements ExtensionProvidedHttpResponseEditor {
|
||||
private final MontoyaApi api;
|
||||
private final EditorCreationContext creationContext;
|
||||
private final MessageProcessor messageProcessor;
|
||||
private HttpRequestResponse requestResponse;
|
||||
|
||||
private JTabbedPane jTabbedPane = new JTabbedPane();
|
||||
|
||||
public Editor(MontoyaApi api, EditorCreationContext creationContext)
|
||||
{
|
||||
this.api = api;
|
||||
this.creationContext = creationContext;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse getResponse() {
|
||||
return requestResponse.response();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestResponse(HttpRequestResponse requestResponse) {
|
||||
this.requestResponse = requestResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean isEnabledFor(HttpRequestResponse requestResponse) {
|
||||
HttpResponse request = requestResponse.response();
|
||||
if (request != null && !request.bodyToString().equals("Loading...")) {
|
||||
List<Map<String, String>> result = messageProcessor.processResponse("", request, false);
|
||||
jTabbedPane = RequestEditor.generateTabbedPaneFromResultMap(api, result);
|
||||
return jTabbedPane.getTabCount() > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String caption() {
|
||||
return "MarkInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component uiComponent() {
|
||||
return jTabbedPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Selection selectedData() {
|
||||
return new Selection() {
|
||||
@Override
|
||||
public ByteArray contents() {
|
||||
return ByteArray.byteArray(Datatable.getSelectedData(((Datatable) jTabbedPane.getSelectedComponent()).getDataTable()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range offsets() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
94
src/main/java/hae/instances/editor/WebSocketEditor.java
Normal file
94
src/main/java/hae/instances/editor/WebSocketEditor.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package hae.instances.editor;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.core.ByteArray;
|
||||
import burp.api.montoya.core.Range;
|
||||
import burp.api.montoya.ui.Selection;
|
||||
import burp.api.montoya.ui.contextmenu.WebSocketMessage;
|
||||
import burp.api.montoya.ui.editor.extension.*;
|
||||
import hae.component.board.Datatable;
|
||||
import hae.instances.http.utils.MessageProcessor;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class WebSocketEditor implements WebSocketMessageEditorProvider {
|
||||
private final MontoyaApi api;
|
||||
|
||||
public WebSocketEditor(MontoyaApi api) {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionProvidedWebSocketMessageEditor provideMessageEditor(EditorCreationContext editorCreationContext) {
|
||||
return new Editor(api, editorCreationContext);
|
||||
}
|
||||
|
||||
private static class Editor implements ExtensionProvidedWebSocketMessageEditor {
|
||||
private final MontoyaApi api;
|
||||
private final EditorCreationContext creationContext;
|
||||
private final MessageProcessor messageProcessor;
|
||||
private ByteArray message;
|
||||
|
||||
private JTabbedPane jTabbedPane = new JTabbedPane();
|
||||
|
||||
public Editor(MontoyaApi api, EditorCreationContext creationContext) {
|
||||
this.api = api;
|
||||
this.creationContext = creationContext;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteArray getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessage(WebSocketMessage webSocketMessage) {
|
||||
this.message = webSocketMessage.payload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledFor(WebSocketMessage webSocketMessage) {
|
||||
String websocketMessage = webSocketMessage.payload().toString();
|
||||
if (!websocketMessage.isEmpty()) {
|
||||
List<Map<String, String>> result = messageProcessor.processMessage("", websocketMessage, false);
|
||||
jTabbedPane = RequestEditor.generateTabbedPaneFromResultMap(api, result);
|
||||
return jTabbedPane.getTabCount() > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String caption() {
|
||||
return "MarkInfo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component uiComponent() {
|
||||
return jTabbedPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Selection selectedData() {
|
||||
return new Selection() {
|
||||
@Override
|
||||
public ByteArray contents() {
|
||||
return ByteArray.byteArray(Datatable.getSelectedData(((Datatable) jTabbedPane.getSelectedComponent()).getDataTable()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range offsets() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/main/java/hae/instances/http/HttpMessageHandler.java
Normal file
87
src/main/java/hae/instances/http/HttpMessageHandler.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package hae.instances.http;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.core.Annotations;
|
||||
import burp.api.montoya.core.HighlightColor;
|
||||
import burp.api.montoya.http.handler.*;
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||
import hae.Config;
|
||||
import hae.component.board.message.MessageTableModel;
|
||||
import hae.instances.http.utils.MessageProcessor;
|
||||
import hae.utils.string.StringProcessor;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class HttpMessageHandler implements HttpHandler {
|
||||
private final MontoyaApi api;
|
||||
private MessageTableModel messageTableModel;
|
||||
private final MessageProcessor messageProcessor;
|
||||
private String host;
|
||||
|
||||
// Montoya API对HTTP消息的处理分为了请求和响应,因此此处设置高亮和标记需要使用全局变量的方式,以此兼顾请求和响应
|
||||
// 同时采用 ThreadLocal 来保证多线程并发的情况下全局变量的安全性
|
||||
private final ThreadLocal<List<String>> colorList = ThreadLocal.withInitial(ArrayList::new);
|
||||
private final ThreadLocal<List<String>> commentList = ThreadLocal.withInitial(ArrayList::new);
|
||||
private final ThreadLocal<Boolean> matches = ThreadLocal.withInitial(() -> false);
|
||||
private final ThreadLocal<HttpRequest> httpRequest = new ThreadLocal<>();
|
||||
|
||||
public HttpMessageHandler(MontoyaApi api, MessageTableModel messageTableModel) {
|
||||
this.api = api;
|
||||
this.messageTableModel = messageTableModel;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent httpRequestToBeSent) {
|
||||
colorList.get().clear();
|
||||
commentList.get().clear();
|
||||
|
||||
Annotations annotations = httpRequestToBeSent.annotations();
|
||||
|
||||
httpRequest.set(httpRequestToBeSent);
|
||||
|
||||
host = StringProcessor.getHostByUrl(httpRequestToBeSent.url());
|
||||
|
||||
List<String> suffixList = Arrays.asList(Config.suffix.split("\\|"));
|
||||
matches.set(suffixList.contains(httpRequestToBeSent.fileExtension()));
|
||||
|
||||
if (!matches.get()) {
|
||||
List<Map<String, String>> result = messageProcessor.processRequest(host, httpRequestToBeSent, true);
|
||||
setColorAndCommentList(result);
|
||||
}
|
||||
|
||||
return RequestToBeSentAction.continueWith(httpRequestToBeSent, annotations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived httpResponseReceived) {
|
||||
Annotations annotations = httpResponseReceived.annotations();
|
||||
|
||||
if (!matches.get()) {
|
||||
List<Map<String, String>> result = messageProcessor.processResponse(host, httpResponseReceived, true);
|
||||
setColorAndCommentList(result);
|
||||
// 设置高亮颜色和注释
|
||||
if (!colorList.get().isEmpty() && !commentList.get().isEmpty()) {
|
||||
String color = messageProcessor.retrieveFinalColor(messageProcessor.retrieveColorIndices(colorList.get()));
|
||||
annotations.setHighlightColor(HighlightColor.highlightColor(color));
|
||||
String comment = StringProcessor.mergeComment(String.join(", ", commentList.get()));
|
||||
annotations.setNotes(comment);
|
||||
|
||||
HttpRequestResponse httpRequestResponse = HttpRequestResponse.httpRequestResponse(httpRequest.get(), httpResponseReceived);
|
||||
|
||||
// 添加到Databoard
|
||||
messageTableModel.add(httpRequestResponse, comment, color);
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseReceivedAction.continueWith(httpResponseReceived, annotations);
|
||||
}
|
||||
|
||||
private void setColorAndCommentList(List<Map<String, String>> result) {
|
||||
if (result != null && !result.isEmpty() && result.size() > 0) {
|
||||
colorList.get().add(result.get(0).get("color"));
|
||||
commentList.get().add(result.get(1).get("comment"));
|
||||
}
|
||||
}
|
||||
}
|
||||
172
src/main/java/hae/instances/http/utils/MessageProcessor.java
Normal file
172
src/main/java/hae/instances/http/utils/MessageProcessor.java
Normal file
@@ -0,0 +1,172 @@
|
||||
package hae.instances.http.utils;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
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 java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
public class MessageProcessor {
|
||||
private final MontoyaApi api;
|
||||
private final RegularMatcher regularMatcher;
|
||||
|
||||
private String finalColor = "";
|
||||
|
||||
public MessageProcessor(MontoyaApi api) {
|
||||
this.api = api;
|
||||
this.regularMatcher = new RegularMatcher(api);
|
||||
}
|
||||
|
||||
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);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return getDataList(obj, flag);
|
||||
}
|
||||
|
||||
public List<Map<String, String>> processResponse(String host, HttpResponse httpResponse, boolean flag) {
|
||||
Map<String, Map<String, Object>> obj = null;
|
||||
try {
|
||||
String response = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||
String body = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String header = httpResponse.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
obj = regularMatcher.match(host, "response", response, header, body);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return getDataList(obj, flag);
|
||||
}
|
||||
|
||||
public List<Map<String, String>> processRequest(String host, HttpRequest httpRequest, boolean flag) {
|
||||
Map<String, Map<String, Object>> obj = null;
|
||||
|
||||
try {
|
||||
String request = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
|
||||
String body = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
|
||||
String header = httpRequest.headers().stream()
|
||||
.map(HttpHeader::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
obj = regularMatcher.match(host, "request", request, header, body);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return getDataList(obj, flag);
|
||||
}
|
||||
|
||||
private List<Map<String, String>> getDataList(Map<String, Map<String, Object>> obj, boolean actionFlag) {
|
||||
List<Map<String, String>> highlightList = new ArrayList<>();
|
||||
List<Map<String, String>> extractList = new ArrayList<>();
|
||||
|
||||
if (obj != null && !obj.isEmpty() && obj.size() > 0) {
|
||||
if (actionFlag) {
|
||||
List<List<String>> resultList = extractColorsAndComments(obj);
|
||||
List<String> colorList = resultList.get(0);
|
||||
List<String> commentList = resultList.get(1);
|
||||
if (!colorList.isEmpty() && !commentList.isEmpty()) {
|
||||
String color = retrieveFinalColor(retrieveColorIndices(colorList));
|
||||
Map<String, String> colorMap = new HashMap<String, String>() {{
|
||||
put("color", color);
|
||||
}};
|
||||
Map<String, String> commentMap = new HashMap<String, String>() {{
|
||||
put("comment", String.join(", ", commentList));
|
||||
}};
|
||||
highlightList.add(colorMap);
|
||||
highlightList.add(commentMap);
|
||||
}
|
||||
} else {
|
||||
extractList.add(extractDataFromMap(obj));
|
||||
}
|
||||
}
|
||||
|
||||
return actionFlag ? highlightList : extractList;
|
||||
}
|
||||
|
||||
private Map<String, String> extractDataFromMap(Map<String, Map<String, Object>> inputData) {
|
||||
Map<String, String> extractedData = new HashMap<>();
|
||||
inputData.keySet().forEach(key -> {
|
||||
Map<String, Object> tempMap = inputData.get(key);
|
||||
String data = tempMap.get("data").toString();
|
||||
extractedData.put(key, data);
|
||||
});
|
||||
return extractedData;
|
||||
}
|
||||
|
||||
private List<List<String>> extractColorsAndComments(Map<String, Map<String, Object>> inputData) {
|
||||
List<String> colorList = new ArrayList<>();
|
||||
List<String> commentList = new ArrayList<>();
|
||||
inputData.keySet().forEach(key -> {
|
||||
Map<String, Object> tempMap = inputData.get(key);
|
||||
String color = tempMap.get("color").toString();
|
||||
colorList.add(color);
|
||||
commentList.add(key);
|
||||
});
|
||||
List<List<String>> result = new ArrayList<>();
|
||||
result.add(colorList);
|
||||
result.add(commentList);
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Integer> retrieveColorIndices(List<String> colors){
|
||||
List<Integer> indices = new ArrayList<>();
|
||||
String[] colorArray = Config.color;
|
||||
int size = colorArray.length;
|
||||
|
||||
for (String color : colors) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (colorArray[i].equals(color)) {
|
||||
indices.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
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++;
|
||||
}
|
||||
// 利用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)];
|
||||
}
|
||||
} else {
|
||||
upgradeColors(stack);
|
||||
}
|
||||
}
|
||||
|
||||
public String retrieveFinalColor(List<Integer> colorList) {
|
||||
upgradeColors(colorList);
|
||||
return finalColor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +1,40 @@
|
||||
package burp.core.processor;
|
||||
package hae.instances.http.utils;
|
||||
|
||||
import burp.BurpExtender;
|
||||
import burp.core.GlobalCachePool;
|
||||
import burp.core.utils.HashCalculator;
|
||||
import burp.core.utils.MatchTool;
|
||||
import burp.config.ConfigEntry;
|
||||
import burp.core.utils.StringHelper;
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import dk.brics.automaton.Automaton;
|
||||
import dk.brics.automaton.AutomatonMatcher;
|
||||
import dk.brics.automaton.RegExp;
|
||||
import dk.brics.automaton.RunAutomaton;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import hae.Config;
|
||||
import hae.cache.CachePool;
|
||||
import hae.utils.string.HashCalculator;
|
||||
import hae.utils.string.StringProcessor;
|
||||
import jregex.Matcher;
|
||||
import jregex.Pattern;
|
||||
|
||||
/**
|
||||
* @author EvilChen
|
||||
*/
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class RegularMatcher {
|
||||
private final MontoyaApi api;
|
||||
|
||||
public RegularMatcher(MontoyaApi api) {
|
||||
this.api = api;
|
||||
|
||||
public class DataProcessingUnit {
|
||||
public Map<String, String> extractDataFromMap(Map<String, Map<String, Object>> inputData) {
|
||||
Map<String, String> extractedData = new HashMap<>();
|
||||
inputData.keySet().forEach(key -> {
|
||||
Map<String, Object> tempMap = inputData.get(key);
|
||||
String data = tempMap.get("data").toString();
|
||||
extractedData.put(key, data);
|
||||
});
|
||||
return extractedData;
|
||||
}
|
||||
|
||||
public List<List<String>> extractColorsAndComments(Map<String, Map<String, Object>> inputData) {
|
||||
List<String> colorList = new ArrayList<>();
|
||||
List<String> commentList = new ArrayList<>();
|
||||
inputData.keySet().forEach(key -> {
|
||||
Map<String, Object> tempMap = inputData.get(key);
|
||||
String color = tempMap.get("color").toString();
|
||||
colorList.add(color);
|
||||
commentList.add(key);
|
||||
});
|
||||
List<List<String>> result = new ArrayList<>();
|
||||
result.add(colorList);
|
||||
result.add(commentList);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Map<String, Object>> matchContentByRegex(byte[] content, String headers, byte[] body, String scopeString, String host)
|
||||
throws NoSuchAlgorithmException {
|
||||
public Map<String, Map<String, Object>> match(String host, String type, String message, String header, String body) {
|
||||
// 先从缓存池里判断是否有已经匹配好的结果
|
||||
String messageIndex = HashCalculator.calculateHash(content);
|
||||
Map<String, Map<String, Object>> map = GlobalCachePool.getFromCache(messageIndex);
|
||||
String messageIndex = HashCalculator.calculateHash(message.getBytes());
|
||||
Map<String, Map<String, Object>> map = CachePool.getFromCache(messageIndex);
|
||||
if (map != null) {
|
||||
return map;
|
||||
} else {
|
||||
// 最终返回的结果
|
||||
Map<String, Map<String, Object>> finalMap = new HashMap<>();
|
||||
ConfigEntry.globalRules.keySet().parallelStream().forEach(i -> {
|
||||
for (Object[] objects : ConfigEntry.globalRules.get(i)) {
|
||||
Config.globalRules.keySet().parallelStream().forEach(i -> {
|
||||
for (Object[] objects : Config.globalRules.get(i)) {
|
||||
// 多线程执行,一定程度上减少阻塞现象
|
||||
String matchContent = "";
|
||||
// 遍历获取规则
|
||||
@@ -78,22 +52,22 @@ public class DataProcessingUnit {
|
||||
boolean sensitive = (Boolean) objects[8];
|
||||
|
||||
// 判断规则是否开启与作用域
|
||||
if (loaded && (scope.contains(scopeString) || scope.contains("any"))) {
|
||||
if (loaded && (scope.contains(type) || scope.contains("any") || type.equals("any"))) {
|
||||
switch (scope) {
|
||||
case "any":
|
||||
case "request":
|
||||
case "response":
|
||||
matchContent = new String(content, StandardCharsets.UTF_8);
|
||||
matchContent = message;
|
||||
break;
|
||||
case "any header":
|
||||
case "request header":
|
||||
case "response header":
|
||||
matchContent = headers;
|
||||
matchContent = header;
|
||||
break;
|
||||
case "any body":
|
||||
case "request body":
|
||||
case "response body":
|
||||
matchContent = new String(body, StandardCharsets.UTF_8);
|
||||
matchContent = body;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -102,8 +76,7 @@ public class DataProcessingUnit {
|
||||
try {
|
||||
result.addAll(matchByRegex(f_regex, s_regex, matchContent, format, engine, sensitive));
|
||||
} catch (Exception e) {
|
||||
BurpExtender.stdout.println(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex));
|
||||
e.printStackTrace();
|
||||
api.logging().logToError(String.format("[x] Error Info:\nName: %s\nRegex: %s", name, f_regex));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -121,8 +94,8 @@ public class DataProcessingUnit {
|
||||
// 添加到全局变量中,便于Databoard检索
|
||||
if (!Objects.equals(host, "") && host != null) {
|
||||
List<String> dataList = Arrays.asList(dataStr.split("\n"));
|
||||
if (ConfigEntry.globalDataMap.containsKey(host)) {
|
||||
ConcurrentHashMap<String, List<String>> gRuleMap = new ConcurrentHashMap<>(ConfigEntry.globalDataMap.get(host));
|
||||
if (Config.globalDataMap.containsKey(host)) {
|
||||
ConcurrentHashMap<String, List<String>> gRuleMap = new ConcurrentHashMap<>(Config.globalDataMap.get(host));
|
||||
if (gRuleMap.containsKey(name)) {
|
||||
// gDataList为不可变列表,因此需要重新创建一个列表以便于使用addAll方法
|
||||
List<String> gDataList = gRuleMap.get(name);
|
||||
@@ -134,39 +107,35 @@ public class DataProcessingUnit {
|
||||
} else {
|
||||
gRuleMap.put(name, dataList);
|
||||
}
|
||||
ConfigEntry.globalDataMap.remove(host);
|
||||
ConfigEntry.globalDataMap.put(host, gRuleMap);
|
||||
Config.globalDataMap.remove(host);
|
||||
Config.globalDataMap.put(host, gRuleMap);
|
||||
} else {
|
||||
Map<String, List<String>> ruleMap = new HashMap<>();
|
||||
ruleMap.put(name, dataList);
|
||||
// 添加单一Host
|
||||
ConfigEntry.globalDataMap.put(host, ruleMap);
|
||||
Config.globalDataMap.put(host, ruleMap);
|
||||
}
|
||||
|
||||
String[] splitHost = host.split("\\.");
|
||||
String onlyHost = host.split(":")[0];
|
||||
|
||||
String anyHost = (splitHost.length > 2 && !MatchTool.matchIP(host)) ? StringHelper.replaceFirstOccurrence(host, splitHost[0], "*") : "";
|
||||
String anyHost = (splitHost.length > 2 && !onlyHost.matches("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b")) ? StringProcessor.replaceFirstOccurrence(onlyHost, splitHost[0], "*") : "";
|
||||
|
||||
if (!ConfigEntry.globalDataMap.containsKey(anyHost) && anyHost.length() > 0) {
|
||||
if (!Config.globalDataMap.containsKey(anyHost) && anyHost.length() > 0) {
|
||||
// 添加通配符Host,实际数据从查询哪里将所有数据提取
|
||||
ConfigEntry.globalDataMap.put(anyHost, new HashMap<>());
|
||||
Config.globalDataMap.put(anyHost, new HashMap<>());
|
||||
}
|
||||
|
||||
if (!ConfigEntry.globalDataMap.containsKey("*")) {
|
||||
if (!Config.globalDataMap.containsKey("*")) {
|
||||
// 添加通配符全匹配,同上
|
||||
ConfigEntry.globalDataMap.put("*", new HashMap<>());
|
||||
}
|
||||
|
||||
if (!ConfigEntry.globalDataMap.containsKey("**")) {
|
||||
// 添加通配符全匹配,同上
|
||||
ConfigEntry.globalDataMap.put("**", new HashMap<>());
|
||||
Config.globalDataMap.put("*", new HashMap<>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
GlobalCachePool.addToCache(messageIndex, finalMap);
|
||||
CachePool.addToCache(messageIndex, finalMap);
|
||||
return finalMap;
|
||||
}
|
||||
}
|
||||
@@ -222,7 +191,7 @@ public class DataProcessingUnit {
|
||||
return matches;
|
||||
}
|
||||
|
||||
public List<String> getFormatString(Matcher matcher, String format) {
|
||||
private List<String> getFormatString(Matcher matcher, String format) {
|
||||
List<Integer> indexList = parseIndexesFromString(format);
|
||||
List<String> stringList = new ArrayList<>();
|
||||
|
||||
@@ -242,7 +211,7 @@ public class DataProcessingUnit {
|
||||
return stringList;
|
||||
}
|
||||
|
||||
public List<String> getFormatString(AutomatonMatcher matcher, String content) {
|
||||
private List<String> getFormatString(AutomatonMatcher matcher, String content) {
|
||||
List<String> stringList = new ArrayList<>();
|
||||
|
||||
while (matcher.find()) {
|
||||
@@ -283,9 +252,9 @@ public class DataProcessingUnit {
|
||||
}
|
||||
|
||||
private String getSubString(String content, String s) {
|
||||
byte[] contentByte = BurpExtender.helpers.stringToBytes(content);
|
||||
byte[] sByte = BurpExtender.helpers.stringToBytes(s);
|
||||
int startIndex = BurpExtender.helpers.indexOf(contentByte, sByte, false, 1, contentByte.length);
|
||||
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);
|
||||
}
|
||||
@@ -303,4 +272,3 @@ public class DataProcessingUnit {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package hae.instances.websocket;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.core.HighlightColor;
|
||||
import burp.api.montoya.proxy.websocket.*;
|
||||
import hae.instances.http.utils.MessageProcessor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class WebSocketMessageHandler implements ProxyMessageHandler {
|
||||
private final MontoyaApi api;
|
||||
private final MessageProcessor messageProcessor;
|
||||
|
||||
public WebSocketMessageHandler(MontoyaApi api) {
|
||||
this.api = api;
|
||||
this.messageProcessor = new MessageProcessor(api);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextMessageReceivedAction handleTextMessageReceived(InterceptedTextMessage interceptedTextMessage) {
|
||||
String message = interceptedTextMessage.payload();
|
||||
List<Map<String, String>> result = messageProcessor.processMessage("", message, true);
|
||||
|
||||
if (result != null && !result.isEmpty() && result.size() > 0) {
|
||||
interceptedTextMessage.annotations().setHighlightColor(HighlightColor.highlightColor(result.get(0).get("color")));
|
||||
interceptedTextMessage.annotations().setNotes(result.get(1).get("comment"));
|
||||
}
|
||||
|
||||
return TextMessageReceivedAction.continueWith(interceptedTextMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextMessageToBeSentAction handleTextMessageToBeSent(InterceptedTextMessage interceptedTextMessage) {
|
||||
return TextMessageToBeSentAction.continueWith(interceptedTextMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryMessageReceivedAction handleBinaryMessageReceived(InterceptedBinaryMessage interceptedBinaryMessage) {
|
||||
return BinaryMessageReceivedAction.continueWith(interceptedBinaryMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryMessageToBeSentAction handleBinaryMessageToBeSent(InterceptedBinaryMessage interceptedBinaryMessage) {
|
||||
return BinaryMessageToBeSentAction.continueWith(interceptedBinaryMessage);
|
||||
}
|
||||
}
|
||||
195
src/main/java/hae/utils/config/ConfigLoader.java
Normal file
195
src/main/java/hae/utils/config/ConfigLoader.java
Normal file
@@ -0,0 +1,195 @@
|
||||
package hae.utils.config;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import burp.api.montoya.http.message.HttpRequestResponse;
|
||||
import burp.api.montoya.http.message.requests.HttpRequest;
|
||||
import hae.Config;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
public class ConfigLoader {
|
||||
private final MontoyaApi api;
|
||||
private final Yaml yaml;
|
||||
private final String configFilePath;
|
||||
private final String rulesFilePath;
|
||||
|
||||
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);
|
||||
|
||||
String configPath = determineConfigPath();
|
||||
this.configFilePath = String.format("%s/%s", configPath, "Config.yml");
|
||||
this.rulesFilePath = String.format("%s/%s", configPath, "Rules.yml");
|
||||
|
||||
// 构造函数,初始化配置
|
||||
File HaEConfigPathFile = new File(configPath);
|
||||
if (!(HaEConfigPathFile.exists() && HaEConfigPathFile.isDirectory())) {
|
||||
HaEConfigPathFile.mkdirs();
|
||||
}
|
||||
|
||||
File configFilePath = new File(this.configFilePath);
|
||||
if (!(configFilePath.exists() && configFilePath.isFile())) {
|
||||
initConfig();
|
||||
}
|
||||
|
||||
File rulesFilePath = new File(this.rulesFilePath);
|
||||
if (!(rulesFilePath.exists() && rulesFilePath.isFile())) {
|
||||
initRules();
|
||||
}
|
||||
|
||||
Config.globalRules = getRules();
|
||||
}
|
||||
|
||||
private String determineConfigPath() {
|
||||
// 优先级1:用户根目录
|
||||
String userConfigPath = String.format("%s/.config/HaE", System.getProperty("user.home"));
|
||||
if (isValidConfigPath(userConfigPath)) {
|
||||
return userConfigPath;
|
||||
}
|
||||
|
||||
// 优先级2:Jar包所在目录
|
||||
String jarPath = api.extension().filename();
|
||||
String jarDirectory = new File(jarPath).getParent();
|
||||
String jarConfigPath = String.format("%s/.config/HaE", jarDirectory);
|
||||
if (isValidConfigPath(jarConfigPath)) {
|
||||
return jarConfigPath;
|
||||
}
|
||||
|
||||
return userConfigPath;
|
||||
}
|
||||
|
||||
private static boolean isValidConfigPath(String configPath) {
|
||||
File configPathFile = new File(configPath);
|
||||
return configPathFile.exists() && configPathFile.isDirectory();
|
||||
}
|
||||
|
||||
public void initConfig() {
|
||||
Map<String, Object> r = new LinkedHashMap<>();
|
||||
r.put("excludeSuffix", getExcludeSuffix());
|
||||
try {
|
||||
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8);
|
||||
yaml.dump(r, ws);
|
||||
ws.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public String getRulesFilePath() {
|
||||
return rulesFilePath;
|
||||
}
|
||||
|
||||
public String getExcludeSuffix() {
|
||||
File yamlSetting = new File(configFilePath);
|
||||
if (!yamlSetting.exists() || !yamlSetting.isFile()) {
|
||||
return Config.suffix;
|
||||
}
|
||||
|
||||
try (InputStream inorder = Files.newInputStream(Paths.get(configFilePath))) {
|
||||
Map<String, Object> r = new Yaml().load(inorder);
|
||||
|
||||
if (r.containsKey("excludeSuffix")) {
|
||||
return r.get("excludeSuffix").toString();
|
||||
}
|
||||
}catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return Config.suffix;
|
||||
}
|
||||
|
||||
// 获取规则配置
|
||||
public Map<String, Object[][]> getRules() {
|
||||
Map<String, Object[][]> rules = new HashMap<>();
|
||||
|
||||
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);
|
||||
|
||||
String[] fieldKeys = {"loaded", "name", "f_regex", "s_regex", "format", "color", "scope", "engine", "sensitive"};
|
||||
|
||||
Object rulesObj = rulesMap.get("rules");
|
||||
if (rulesObj instanceof List) {
|
||||
List<Map<String, Object>> groupData = (List<Map<String, Object>>) rulesObj;
|
||||
for (Map<String, Object> groupFields : groupData) {
|
||||
ArrayList<Object[]> data = new ArrayList<>();
|
||||
|
||||
Object ruleObj = groupFields.get("rule");
|
||||
if (ruleObj instanceof List) {
|
||||
List<Map<String, Object>> ruleData = (List<Map<String, Object>>) ruleObj;
|
||||
for (Map<String, Object> ruleFields : ruleData) {
|
||||
Object[] valuesArray = new Object[fieldKeys.length];
|
||||
for (int i = 0; i < fieldKeys.length; i++) {
|
||||
valuesArray[i] = ruleFields.get(fieldKeys[i]);
|
||||
}
|
||||
data.add(valuesArray);
|
||||
}
|
||||
}
|
||||
|
||||
Object[][] dataArray = data.toArray(new Object[data.size()][]);
|
||||
rules.put(groupFields.get("group").toString(), dataArray);
|
||||
}
|
||||
}
|
||||
|
||||
return rules;
|
||||
} catch (Exception ignored){
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
public void setExcludeSuffix(String excludeSuffix) {
|
||||
Map<String,Object> r = new LinkedHashMap<>();
|
||||
r.put("excludeSuffix", excludeSuffix);
|
||||
try{
|
||||
Writer ws = new OutputStreamWriter(Files.newOutputStream(Paths.get(configFilePath)), StandardCharsets.UTF_8);
|
||||
yaml.dump(r, ws);
|
||||
ws.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public void initRules() {
|
||||
Thread t = new Thread() {
|
||||
public void run() {
|
||||
pullRules();
|
||||
}
|
||||
};
|
||||
t.start();
|
||||
try {
|
||||
t.join(10000);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private void pullRules() {
|
||||
try {
|
||||
String url = "https://raw.githubusercontent.com/gh0stkey/HaE/gh-pages/Rules.yml";
|
||||
HttpRequest httpRequest = HttpRequest.httpRequestFromUrl(url);
|
||||
HttpRequestResponse requestResponse = api.http().sendRequest(httpRequest);
|
||||
String responseBody = requestResponse.response().bodyToString();
|
||||
if (responseBody.contains("rules")) {
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(rulesFilePath);
|
||||
fileOutputStream.write(responseBody.getBytes());
|
||||
fileOutputStream.close();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
api.extension().unload();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +1,109 @@
|
||||
package burp.rule;
|
||||
|
||||
import burp.config.ConfigEntry;
|
||||
import burp.config.ConfigLoader;
|
||||
import burp.rule.model.Rule;
|
||||
import burp.rule.model.RuleGroup;
|
||||
import burp.rule.utils.YamlTool;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.stream.Collectors;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import java.io.File;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class RuleProcessor {
|
||||
public void rulesFormatAndSave() {
|
||||
Yaml yaml = YamlTool.newStandardYaml();
|
||||
List<RuleGroup> ruleGroupList = new ArrayList<>();
|
||||
|
||||
ConfigEntry.globalRules.forEach((k, v) -> {
|
||||
List<Rule> ruleList = Arrays.stream(v)
|
||||
.map(objects -> new Rule(
|
||||
(boolean) objects[0],
|
||||
(String) objects[1],
|
||||
(String) objects[2],
|
||||
(String) objects[3],
|
||||
(String) objects[4],
|
||||
(String) objects[5],
|
||||
(String) objects[6],
|
||||
(String) objects[7],
|
||||
(boolean) objects[8]))
|
||||
.collect(Collectors.toList());
|
||||
ruleGroupList.add(new RuleGroup(k, ruleList));
|
||||
});
|
||||
|
||||
List<Map<String, Object>> outputGroupsMap = ruleGroupList.stream()
|
||||
.map(RuleGroup::getFields)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<String, Object> outputMap = new LinkedHashMap<>();
|
||||
outputMap.put("rules", outputGroupsMap);
|
||||
|
||||
File f = new File(ConfigLoader.getRulesFilePath());
|
||||
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(f.toPath()), StandardCharsets.UTF_8)) {
|
||||
yaml.dump(outputMap, ws);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void changeRule(Vector data, int select, String type) {
|
||||
ConfigEntry.globalRules.get(type)[select] = data.toArray();
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
|
||||
public void addRule(Vector data, String type) {
|
||||
ArrayList<Object[]> x = new ArrayList<>(Arrays.asList(ConfigEntry.globalRules.get(type)));
|
||||
x.add(data.toArray());
|
||||
ConfigEntry.globalRules.put(type,x.toArray(new Object[x.size()][]));
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
public void removeRule(int select,String type) {
|
||||
ArrayList<Object[]> x = new ArrayList<>(Arrays.asList(ConfigEntry.globalRules.get(type)));
|
||||
x.remove(select);
|
||||
ConfigEntry.globalRules.put(type,x.toArray(new Object[x.size()][]));
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
|
||||
public void renameRuleGroup(String oldName, String newName) {
|
||||
ConfigEntry.globalRules.put(newName, ConfigEntry.globalRules.remove(oldName));
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
|
||||
public void deleteRuleGroup(String Rules) {
|
||||
ConfigEntry.globalRules.remove(Rules);
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
|
||||
public String newRule() {
|
||||
int i = 0;
|
||||
String name = "New ";
|
||||
Object[][] data = new Object[][] {
|
||||
{
|
||||
false, "New Name", "(First Regex)", "(Second Regex)", "{0}", "gray", "any", "nfa", false
|
||||
}
|
||||
};
|
||||
|
||||
while (ConfigEntry.globalRules.containsKey(name + i)) {
|
||||
i++;
|
||||
}
|
||||
|
||||
ConfigEntry.globalRules.put(name + i, data);
|
||||
this.rulesFormatAndSave();
|
||||
return name + i;
|
||||
}
|
||||
}
|
||||
package hae.utils.rule;
|
||||
|
||||
import burp.api.montoya.MontoyaApi;
|
||||
import hae.Config;
|
||||
import hae.utils.rule.model.Group;
|
||||
import hae.utils.rule.model.Info;
|
||||
import hae.utils.config.ConfigLoader;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class RuleProcessor {
|
||||
private final MontoyaApi api;
|
||||
private final ConfigLoader configLoader;
|
||||
|
||||
public RuleProcessor(MontoyaApi api, ConfigLoader configLoader) {
|
||||
this.api = api;
|
||||
this.configLoader = configLoader;
|
||||
}
|
||||
|
||||
public void rulesFormatAndSave() {
|
||||
DumperOptions dop = new DumperOptions();
|
||||
dop.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
Representer representer = new Representer(dop);
|
||||
Yaml yaml = new Yaml(representer, dop);
|
||||
|
||||
List<Group> ruleGroupList = new ArrayList<>();
|
||||
|
||||
Config.globalRules.forEach((k, v) -> {
|
||||
List<Info> ruleList = Arrays.stream(v)
|
||||
.map(objects -> new Info(
|
||||
(boolean) objects[0],
|
||||
(String) objects[1],
|
||||
(String) objects[2],
|
||||
(String) objects[3],
|
||||
(String) objects[4],
|
||||
(String) objects[5],
|
||||
(String) objects[6],
|
||||
(String) objects[7],
|
||||
(boolean) objects[8]))
|
||||
.collect(Collectors.toList());
|
||||
ruleGroupList.add(new Group(k, ruleList));
|
||||
});
|
||||
|
||||
List<Map<String, Object>> outputGroupsMap = ruleGroupList.stream()
|
||||
.map(Group::getFields)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<String, Object> outputMap = new LinkedHashMap<>();
|
||||
outputMap.put("rules", outputGroupsMap);
|
||||
|
||||
File f = new File(configLoader.getRulesFilePath());
|
||||
try (Writer ws = new OutputStreamWriter(Files.newOutputStream(f.toPath()), StandardCharsets.UTF_8)) {
|
||||
yaml.dump(outputMap, ws);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public void changeRule(Vector data, int select, String type) {
|
||||
Config.globalRules.get(type)[select] = data.toArray();
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
|
||||
public void addRule(Vector data, String type) {
|
||||
ArrayList<Object[]> x = new ArrayList<>(Arrays.asList(Config.globalRules.get(type)));
|
||||
x.add(data.toArray());
|
||||
Config.globalRules.put(type,x.toArray(new Object[x.size()][]));
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
public void removeRule(int select,String type) {
|
||||
ArrayList<Object[]> x = new ArrayList<>(Arrays.asList(Config.globalRules.get(type)));
|
||||
x.remove(select);
|
||||
Config.globalRules.put(type,x.toArray(new Object[x.size()][]));
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
|
||||
public void renameRuleGroup(String oldName, String newName) {
|
||||
Config.globalRules.put(newName, Config.globalRules.remove(oldName));
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
|
||||
public void deleteRuleGroup(String Rules) {
|
||||
Config.globalRules.remove(Rules);
|
||||
this.rulesFormatAndSave();
|
||||
}
|
||||
|
||||
public String newRule() {
|
||||
int i = 0;
|
||||
String name = "New ";
|
||||
|
||||
while (Config.globalRules.containsKey(name + i)) {
|
||||
i++;
|
||||
}
|
||||
|
||||
Config.globalRules.put(name + i, Config.ruleTemplate);
|
||||
this.rulesFormatAndSave();
|
||||
return name + i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,29 @@
|
||||
package burp.rule.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class RuleGroup {
|
||||
private Map<String, Object> fields;
|
||||
|
||||
public RuleGroup(String groupName, List<Rule> rules) {
|
||||
List<Map<String, Object>> ruleList = new ArrayList<>();
|
||||
for (Rule rule : rules) {
|
||||
ruleList.add(rule.getFields());
|
||||
}
|
||||
|
||||
fields = new LinkedHashMap<>();
|
||||
fields.put("group", groupName);
|
||||
fields.put("rule", ruleList);
|
||||
}
|
||||
|
||||
public RuleGroup() {
|
||||
|
||||
}
|
||||
|
||||
public Map<String, Object> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void loadFields(Map<String, Object> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
}
|
||||
package hae.utils.rule.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Group {
|
||||
private Map<String, Object> fields;
|
||||
|
||||
public Group(String groupName, List<Info> rules) {
|
||||
List<Map<String, Object>> ruleList = new ArrayList<>();
|
||||
for (Info rule : rules) {
|
||||
ruleList.add(rule.getFields());
|
||||
}
|
||||
|
||||
fields = new LinkedHashMap<>();
|
||||
fields.put("group", groupName);
|
||||
fields.put("rule", ruleList);
|
||||
}
|
||||
|
||||
public Map<String, Object> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void loadFields(Map<String, Object> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,29 @@
|
||||
package burp.rule.model;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class Rule {
|
||||
private Map<String, Object> fields;
|
||||
|
||||
public Rule(boolean loaded, String name, String f_regex, String s_regex, String format, String color, String scope, String engine, boolean sensitive) {
|
||||
fields = new LinkedHashMap<>();
|
||||
fields.put("name", name);
|
||||
fields.put("loaded", loaded);
|
||||
fields.put("f_regex", f_regex);
|
||||
fields.put("s_regex", s_regex);
|
||||
fields.put("format", format);
|
||||
fields.put("color", color);
|
||||
fields.put("scope", scope);
|
||||
fields.put("engine", engine);
|
||||
fields.put("sensitive", sensitive);
|
||||
}
|
||||
|
||||
public Rule() {
|
||||
|
||||
}
|
||||
|
||||
public Map<String, Object> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void loadFields(Map<String, Object> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
package hae.utils.rule.model;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Info {
|
||||
private Map<String, Object> fields;
|
||||
|
||||
public Info(boolean loaded, String name, String f_regex, String s_regex, String format, String color, String scope, String engine, boolean sensitive) {
|
||||
fields = new LinkedHashMap<>();
|
||||
fields.put("name", name);
|
||||
fields.put("loaded", loaded);
|
||||
fields.put("f_regex", f_regex);
|
||||
fields.put("s_regex", s_regex);
|
||||
fields.put("format", format);
|
||||
fields.put("color", color);
|
||||
fields.put("scope", scope);
|
||||
fields.put("engine", engine);
|
||||
fields.put("sensitive", sensitive);
|
||||
}
|
||||
|
||||
public Map<String, Object> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void loadFields(Map<String, Object> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,17 @@
|
||||
package burp.core.utils;
|
||||
|
||||
import burp.BurpExtender;
|
||||
package hae.utils.string;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* @author EvilChen
|
||||
*/
|
||||
|
||||
public class HashCalculator {
|
||||
public static String calculateHash(byte[] bytes) throws NoSuchAlgorithmException {
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
byte[] hashBytes = digest.digest(bytes);
|
||||
return bytesToHex(hashBytes);
|
||||
public static String calculateHash(byte[] bytes){
|
||||
MessageDigest digest;
|
||||
try {
|
||||
digest = MessageDigest.getInstance("MD5");
|
||||
byte[] hashBytes = digest.digest(bytes);
|
||||
return bytesToHex(hashBytes);
|
||||
} catch (Exception ignored) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
@@ -1,9 +1,10 @@
|
||||
package burp.core.utils;
|
||||
package hae.utils.string;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class StringHelper {
|
||||
public class StringProcessor {
|
||||
public static String replaceFirstOccurrence(String original, String find, String replace) {
|
||||
int index = original.indexOf(find);
|
||||
if (index != -1) {
|
||||
@@ -36,6 +37,39 @@ public class StringHelper {
|
||||
return comment;
|
||||
}
|
||||
|
||||
Map<String, Integer> itemCounts = getStringIntegerMap(comment);
|
||||
|
||||
StringBuilder mergedItems = new StringBuilder();
|
||||
|
||||
for (Map.Entry<String, Integer> entry : itemCounts.entrySet()) {
|
||||
String itemName = entry.getKey();
|
||||
int count = entry.getValue();
|
||||
if (count != 0) {
|
||||
mergedItems.append(itemName).append(" (").append(count).append("), ");
|
||||
}
|
||||
}
|
||||
|
||||
return mergedItems.substring(0, mergedItems.length() - 2);
|
||||
}
|
||||
|
||||
public static String getHostByUrl(String url) {
|
||||
String host = "";
|
||||
|
||||
try {
|
||||
URL u = new URL(url);
|
||||
int port = u.getPort();
|
||||
if (port == -1) {
|
||||
host = u.getHost();
|
||||
} else {
|
||||
host = String.format("%s:%s", u.getHost(), port);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
private static Map<String, Integer> getStringIntegerMap(String comment) {
|
||||
Map<String, Integer> itemCounts = new HashMap<>();
|
||||
String[] items = comment.split(", ");
|
||||
|
||||
@@ -51,16 +85,7 @@ public class StringHelper {
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder mergedItems = new StringBuilder();
|
||||
|
||||
for (Map.Entry<String, Integer> entry : itemCounts.entrySet()) {
|
||||
String itemName = entry.getKey();
|
||||
int count = entry.getValue();
|
||||
if (count != 0) {
|
||||
mergedItems.append(itemName).append(" (").append(count).append("), ");
|
||||
}
|
||||
}
|
||||
|
||||
return mergedItems.substring(0, mergedItems.length() - 2);
|
||||
return itemCounts;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user