465 lines
17 KiB
Java
465 lines
17 KiB
Java
package burp.ui.board;
|
||
|
||
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 java.net.URL;
|
||
import java.nio.charset.StandardCharsets;
|
||
import java.security.NoSuchAlgorithmException;
|
||
import java.util.ArrayList;
|
||
import java.util.Arrays;
|
||
import java.util.Comparator;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import javax.swing.JScrollPane;
|
||
import javax.swing.JSplitPane;
|
||
import javax.swing.JTabbedPane;
|
||
import javax.swing.JTable;
|
||
import javax.swing.SwingWorker;
|
||
import javax.swing.table.AbstractTableModel;
|
||
import javax.swing.table.DefaultTableModel;
|
||
import javax.swing.table.TableModel;
|
||
import javax.swing.table.TableRowSorter;
|
||
|
||
/**
|
||
* @author EvilChen
|
||
*/
|
||
|
||
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 Table logTable;
|
||
|
||
public MessagePanel(IBurpExtenderCallbacks callbacks, IExtensionHelpers helpers) {
|
||
this.callbacks = callbacks;
|
||
this.helpers = helpers;
|
||
|
||
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||
|
||
logTable = new Table(MessagePanel.this);
|
||
logTable.setDefaultRenderer(Object.class, new ColorRenderer(filteredLog, logTable));
|
||
logTable.setAutoCreateRowSorter(true);
|
||
|
||
// Length字段根据大小进行排序
|
||
TableRowSorter<DefaultTableModel> sorter = (TableRowSorter<DefaultTableModel>) logTable.getRowSorter();
|
||
sorter.setComparator(4, new Comparator<String>() {
|
||
@Override
|
||
public int compare(String s1, String s2) {
|
||
Integer age1 = Integer.parseInt(s1);
|
||
Integer age2 = Integer.parseInt(s2);
|
||
return age1.compareTo(age2);
|
||
}
|
||
});
|
||
// Color字段根据颜色顺序进行排序
|
||
sorter.setComparator(5, new Comparator<String>() {
|
||
@Override
|
||
public int compare(String s1, String s2) {
|
||
int index1 = getIndex(s1);
|
||
int index2 = getIndex(s2);
|
||
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)) {
|
||
return i;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
});
|
||
|
||
logTable.setRowSorter(sorter);
|
||
logTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||
|
||
JScrollPane scrollPane = new JScrollPane(logTable);
|
||
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);
|
||
}
|
||
|
||
public JSplitPane getPanel() {
|
||
return splitPane;
|
||
}
|
||
|
||
public Table getTable() {
|
||
return logTable;
|
||
}
|
||
|
||
public List<LogEntry> getLogs() {
|
||
return log;
|
||
}
|
||
|
||
@Override
|
||
public int getRowCount()
|
||
{
|
||
return filteredLog.size();
|
||
}
|
||
|
||
@Override
|
||
public int getColumnCount()
|
||
{
|
||
return 6;
|
||
}
|
||
|
||
@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 "";
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public Class<?> getColumnClass(int columnIndex)
|
||
{
|
||
return String.class;
|
||
}
|
||
|
||
@Override
|
||
public Object getValueAt(int rowIndex, int columnIndex)
|
||
{
|
||
if (filteredLog.isEmpty()) {
|
||
return "";
|
||
}
|
||
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 "";
|
||
}
|
||
}
|
||
|
||
public void applyHostFilter(String filterText) {
|
||
filteredLog.clear();
|
||
fireTableDataChanged();
|
||
for (LogEntry entry : log) {
|
||
String host = entry.getUrl().getHost();
|
||
if (StringHelper.matchFromEnd(host, 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();
|
||
|
||
String requestString = new String(requestResponse.getRequest(), StandardCharsets.UTF_8);
|
||
String responseString = new String(requestResponse.getResponse(), StandardCharsets.UTF_8);
|
||
|
||
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);
|
||
|
||
final boolean[] isMatched = {false}; // 标志变量,表示是否满足过滤条件
|
||
|
||
ConfigEntry.globalRules.keySet().forEach(i -> {
|
||
for (Object[] objects : ConfigEntry.globalRules.get(i)) {
|
||
String name = objects[1].toString();
|
||
String scope = objects[4].toString();
|
||
if (name.contains(tableName)) {
|
||
boolean match = false; // 标志变量,表示当前规则是否匹配
|
||
|
||
switch (scope) {
|
||
case "any":
|
||
match = requestString.contains(filterText) || responseString.contains(filterText);
|
||
break;
|
||
case "request":
|
||
match = requestString.contains(filterText);
|
||
break;
|
||
case "response":
|
||
match = responseString.contains(filterText);
|
||
break;
|
||
case "any header":
|
||
match = requestHeaders.contains(filterText) || responseHeaders.contains(filterText);
|
||
break;
|
||
case "request header":
|
||
match = requestHeaders.contains(filterText);
|
||
break;
|
||
case "response header":
|
||
match = responseHeaders.contains(filterText);
|
||
break;
|
||
case "any body":
|
||
match = requestBody.contains(filterText) || responseBody.contains(filterText);
|
||
break;
|
||
case "request body":
|
||
match = requestBody.contains(filterText);
|
||
break;
|
||
case "response body":
|
||
match = responseBody.contains(filterText);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
if (match) {
|
||
isMatched[0] = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
if (isMatched[0]) {
|
||
filteredLog.add(entry);
|
||
}
|
||
}
|
||
fireTableDataChanged();
|
||
logTable.lastSelectedIndex = -1;
|
||
}
|
||
|
||
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 {
|
||
String hashIndex = HashCalculator.calculateHash(content);
|
||
return GlobalCachePool.getFromCache(hashIndex);
|
||
}
|
||
|
||
private boolean areMapsEqual(Map<String, Map<String, Object>> map1, Map<String, Map<String, Object>> map2) {
|
||
if (map1 == null || map2 == null) {
|
||
return false;
|
||
}
|
||
if (map1.size() != map2.size()) {
|
||
return false;
|
||
}
|
||
|
||
for (String key : map1.keySet()) {
|
||
if (!map2.containsKey(key)) {
|
||
return false;
|
||
}
|
||
if (!areInnerMapsEqual(map1.get(key), map2.get(key))) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
private boolean areInnerMapsEqual(Map<String, Object> innerMap1, Map<String, Object> innerMap2) {
|
||
if (innerMap1.size() != innerMap2.size()) {
|
||
return false;
|
||
}
|
||
|
||
for (String key : innerMap1.keySet()) {
|
||
if (!innerMap2.containsKey(key)) {
|
||
return false;
|
||
}
|
||
Object value1 = innerMap1.get(key);
|
||
Object value2 = innerMap2.get(key);
|
||
|
||
// 如果值是Map,则递归对比
|
||
if (value1 instanceof Map && value2 instanceof Map) {
|
||
if (!areInnerMapsEqual((Map<String, Object>) value1, (Map<String, Object>) value2)) {
|
||
return false;
|
||
}
|
||
} else if (!value1.equals(value2)) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
public class Table extends JTable {
|
||
LogEntry logEntry;
|
||
private SwingWorker<Object, Void> currentWorker;
|
||
// 设置响应报文返回的最大长度为3MB
|
||
private final int MAX_LENGTH = 3145728;
|
||
private int lastSelectedIndex = -1;
|
||
|
||
public Table(TableModel tableModel) {
|
||
super(tableModel);
|
||
}
|
||
|
||
@Override
|
||
public void changeSelection(int row, int col, boolean toggle, boolean extend) {
|
||
super.changeSelection(row, col, toggle, extend);
|
||
int selectedIndex = convertRowIndexToModel(row);
|
||
if (lastSelectedIndex != selectedIndex) {
|
||
lastSelectedIndex = selectedIndex;
|
||
logEntry = filteredLog.get(selectedIndex);
|
||
|
||
requestViewer.setMessage("Loading...".getBytes(), true);
|
||
responseViewer.setMessage("Loading...".getBytes(), false);
|
||
currentlyDisplayedItem = logEntry.getRequestResponse();
|
||
|
||
if (currentWorker != null && !currentWorker.isDone()) {
|
||
currentWorker.cancel(true);
|
||
}
|
||
|
||
currentWorker = new SwingWorker<Object, Void>() {
|
||
@Override
|
||
protected byte[][] doInBackground() throws Exception {
|
||
byte[] requestByte = logEntry.getRequestResponse().getRequest();
|
||
byte[] responseByte = logEntry.getRequestResponse().getResponse();
|
||
|
||
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);
|
||
}
|
||
|
||
return new byte[][] {requestByte, responseByte};
|
||
}
|
||
|
||
@Override
|
||
protected void done() {
|
||
if (!isCancelled()) {
|
||
try {
|
||
byte[][] result = (byte[][]) get();
|
||
requestViewer.setMessage(result[0], true);
|
||
responseViewer.setMessage(result[1], false);
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
}
|
||
}
|
||
}
|
||
};
|
||
currentWorker.execute();
|
||
}
|
||
}
|
||
}
|
||
|
||
} |