diff --git a/src/main/kotlin/burp/BurpExtender.kt b/src/main/kotlin/burp/BurpExtender.kt index 1faaab7..9b1e0b1 100644 --- a/src/main/kotlin/burp/BurpExtender.kt +++ b/src/main/kotlin/burp/BurpExtender.kt @@ -170,6 +170,7 @@ class BurpExtender : IBurpExtender, ITab, ListDataListener { configModel.menuItemsModel.addListDataListener(this) // Menu items are loaded on-demand, thus saving the config is enough configModel.commentatorsModel.addListDataListener(this) // Commentators are menu items as well, see above + configModel.highlightersModel.addListDataListener(this) // Highlighters are menu items as well, see above configModel.messageViewersModel.addListDataListener(MessageViewerManager()) configModel.macrosModel.addListDataListener(MacroManager()) configModel.httpListenersModel.addListDataListener(HttpListenerManager()) @@ -253,6 +254,8 @@ class BurpExtender : IBurpExtender, ITab, ListDataListener { ::CommentatorDialog, Piper.Commentator::getDefaultInstance, ::commentatorFromMap, Piper.Commentator::toMap)) tabs.addTab("Intruder payload processors", MinimalToolListEditor(cfg.intruderPayloadProcessorsModel, parent, ::IntruderPayloadProcessorDialog, Piper.MinimalTool::getDefaultInstance, ::minimalToolFromMap, Piper.MinimalTool::toMap)) + tabs.addTab("Highlighters", MinimalToolListEditor(cfg.highlightersModel, parent, + ::HighlighterDialog, Piper.Highlighter::getDefaultInstance, ::highlighterFromMap, Piper.Highlighter::toMap)) tabs.addTab("Queue", queue) tabs.addTab("Load/Save configuration", createLoadSaveUI(cfg, parent)) tabs.addTab("Developer", createDeveloperUI(cfg)) @@ -306,18 +309,36 @@ class BurpExtender : IBurpExtender, ITab, ListDataListener { } val commentatorCategoryMenus = EnumMap(RequestResponse::class.java) + val highlighterCategoryMenus = EnumMap(RequestResponse::class.java) - if (includeCommentators) configModel.enabledCommentators.forEach { cfgItem -> - messageDetails.forEach { (msrc, md) -> - val item = createMenuItem(cfgItem.common, null, msrc, md, MessageInfoMatchStrategy.ANY) { - performCommentator(cfgItem, md) + if (includeCommentators) { + configModel.enabledCommentators.forEach { cfgItem -> + messageDetails.forEach { (msrc, md) -> + val item = createMenuItem(cfgItem.common, null, msrc, md, MessageInfoMatchStrategy.ANY) { + performCommentator(cfgItem, md) + } + if (item != null) { + val commentatorMenu = commentatorCategoryMenus.getOrPut(msrc.direction) { + categoryMenus[msrc.direction]?.apply { addSeparator() } + ?: createSubMenu(msrc).apply { categoryMenus[msrc.direction] = this } + } + commentatorMenu.add(item) + } } - if (item != null) { - val commentatorMenu = commentatorCategoryMenus.getOrPut(msrc.direction) { - categoryMenus[msrc.direction]?.apply { addSeparator() } - ?: createSubMenu(msrc).apply { categoryMenus[msrc.direction] = this } + } + + configModel.enabledHighlighters.forEach { cfgItem -> + messageDetails.forEach { (msrc, md) -> + val item = createMenuItem(cfgItem.common, null, msrc, md, MessageInfoMatchStrategy.ANY) { + performHighlighter(cfgItem, md) + } + if (item != null) { + val highlighterMenu = highlighterCategoryMenus.getOrPut(msrc.direction) { + categoryMenus[msrc.direction]?.apply { addSeparator() } + ?: createSubMenu(msrc).apply { categoryMenus[msrc.direction] = this } + } + highlighterMenu.add(item) } - commentatorMenu.add(item) } } } @@ -496,6 +517,18 @@ class BurpExtender : IBurpExtender, ITab, ListDataListener { } } + private fun performHighlighter(cfgItem: Piper.Highlighter, messages: List) { + messages.forEach { mi -> + val hrr = mi.hrr ?: return@forEach + if ((hrr.highlight.isNullOrEmpty() || cfgItem.overwrite) && + (!cfgItem.common.hasFilter() || cfgItem.common.filter.matches(mi, helpers, callbacks)) && + cfgItem.common.cmd.matches(mi.content, helpers, callbacks)) { + val h = Highlight.fromString(cfgItem.color) ?: return@forEach + hrr.highlight = h.burpValue + } + } + } + companion object { @JvmStatic fun main (args: Array) { @@ -533,6 +566,7 @@ class ConfigModel(config: Piper.Config = Piper.Config.getDefaultInstance()) { val enabledMessageViewers get() = messageViewersModel.toIterable().filter { it.common.enabled } val enabledMenuItems get() = menuItemsModel.toIterable().filter { it.common.enabled } val enabledCommentators get() = commentatorsModel.toIterable().filter { it.common.enabled } + val enabledHighlighters get() = highlightersModel.toIterable().filter { it.common.enabled } val macrosModel = DefaultListModel() val messageViewersModel = DefaultListModel() @@ -540,6 +574,7 @@ class ConfigModel(config: Piper.Config = Piper.Config.getDefaultInstance()) { val httpListenersModel = DefaultListModel() val commentatorsModel = DefaultListModel() val intruderPayloadProcessorsModel = DefaultListModel() + val highlightersModel = DefaultListModel() private var _developer = config.developer var developer: Boolean @@ -563,6 +598,7 @@ class ConfigModel(config: Piper.Config = Piper.Config.getDefaultInstance()) { fillDefaultModel(config.httpListenerList, httpListenersModel) fillDefaultModel(config.commentatorList, commentatorsModel) fillDefaultModel(config.intruderPayloadProcessorList, intruderPayloadProcessorsModel) + fillDefaultModel(config.highlighterList, highlightersModel) } fun serialize(): Piper.Config = Piper.Config.newBuilder() @@ -572,6 +608,7 @@ class ConfigModel(config: Piper.Config = Piper.Config.getDefaultInstance()) { .addAllHttpListener(httpListenersModel.toIterable()) .addAllCommentator(commentatorsModel.toIterable()) .addAllIntruderPayloadProcessor(intruderPayloadProcessorsModel.toIterable()) + .addAllHighlighter(highlightersModel.toIterable()) .setDeveloper(developer) .build() } diff --git a/src/main/kotlin/burp/ConfigGUI.kt b/src/main/kotlin/burp/ConfigGUI.kt index 594d42b..81af035 100644 --- a/src/main/kotlin/burp/ConfigGUI.kt +++ b/src/main/kotlin/burp/ConfigGUI.kt @@ -471,6 +471,37 @@ class CommentatorDialog(private val commentator: Piper.Commentator, parent: Comp override fun buildEnabled(value: Boolean): Piper.Commentator = commentator.buildEnabled(value) } +class HighlighterDialog(private val highlighter: Piper.Highlighter, parent: Component?) : + MinimalToolDialog(highlighter.common, parent, "highlighter", showScope = true) { + + private val cbOverwrite: JCheckBox = createFullWidthCheckBox("Overwrite highlight on items that already have one", highlighter.overwrite, panel, cs) + private val cbColor = createLabeledWidget("Set highlight to ", JComboBox(Highlight.values()), panel, cs) + + init { + cbColor.renderer = object : DefaultListCellRenderer() { + override fun getListCellRendererComponent(list: JList<*>?, value: Any?, index: Int, isSelected: Boolean, cellHasFocus: Boolean): Component { + val c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus) + val v = value as Highlight + if (v.color != null) { + c.background = v.color + c.foreground = v.textColor + } + return c + } + } + val h = Highlight.fromString(highlighter.color) + if (h != null) cbColor.selectedItem = h + } + + override fun processGUI(mt: Piper.MinimalTool): Piper.Highlighter = Piper.Highlighter.newBuilder().apply { + common = mt + color = cbColor.selectedItem.toString() + if (cbOverwrite.isSelected) overwrite = true + }.build() + + override fun buildEnabled(value: Boolean): Piper.Highlighter = highlighter.buildEnabled(value) +} + private fun createFullWidthCheckBox(caption: String, initialValue: Boolean, panel: Container, cs: GridBagConstraints): JCheckBox { cs.gridwidth = 4 cs.gridx = 0 diff --git a/src/main/kotlin/burp/Enums.kt b/src/main/kotlin/burp/Enums.kt index 009c940..705bb75 100644 --- a/src/main/kotlin/burp/Enums.kt +++ b/src/main/kotlin/burp/Enums.kt @@ -2,6 +2,7 @@ package burp import org.snakeyaml.engine.v1.api.Dump import org.snakeyaml.engine.v1.api.DumpSettingsBuilder +import java.awt.Color import java.util.* import java.util.regex.Pattern @@ -72,6 +73,29 @@ enum class MatchNegation(val negation: Boolean, private val description: String) override fun toString(): String = description } +enum class Highlight(val color: Color?, val textColor: Color = Color.BLACK) { + CLEAR(null), + RED( Color(0xFF, 0x64, 0x64), Color.WHITE), + ORANGE( Color(0xFF, 0xC8, 0x64) ), + YELLOW( Color(0xFF, 0xFF, 0x64) ), + GREEN( Color(0x64, 0xFF, 0x64) ), + CYAN( Color(0x64, 0xFF, 0xFF) ), + BLUE( Color(0x64, 0x64, 0xFF), Color.WHITE), + PINK( Color(0xFF, 0xC8, 0xC8) ), + MAGENTA(Color(0xFF, 0x64, 0xFF) ), + GRAY( Color(0xB4, 0xB4, 0xB4) ); + + override fun toString(): String = super.toString().toLowerCase() + + val burpValue: String? get() = if (color == null) null else toString() + + companion object { + private val lookupTable = Highlight.values().associateBy(Highlight::toString) + + fun fromString(value: String): Highlight? = lookupTable[value] + } +} + enum class ConfigHttpListenerScope(val hls: Piper.HttpListenerScope, val inputList: List) { REQUEST (Piper.HttpListenerScope.REQUEST, Collections.singletonList(RequestResponse.REQUEST)), RESPONSE(Piper.HttpListenerScope.RESPONSE, Collections.singletonList(RequestResponse.RESPONSE)), diff --git a/src/main/kotlin/burp/Extensions.kt b/src/main/kotlin/burp/Extensions.kt index 518189e..f3e5298 100644 --- a/src/main/kotlin/burp/Extensions.kt +++ b/src/main/kotlin/burp/Extensions.kt @@ -119,6 +119,7 @@ fun Piper.UserActionTool.buildEnabled(value: Boolean? = null): Piper.UserActionT fun Piper.HttpListener .buildEnabled(value: Boolean? = null): Piper.HttpListener = toBuilder().setCommon(common.buildEnabled(value)).build() fun Piper.MessageViewer .buildEnabled(value: Boolean? = null): Piper.MessageViewer = toBuilder().setCommon(common.buildEnabled(value)).build() fun Piper.Commentator .buildEnabled(value: Boolean? = null): Piper.Commentator = toBuilder().setCommon(common.buildEnabled(value)).build() +fun Piper.Highlighter .buildEnabled(value: Boolean? = null): Piper.Highlighter = toBuilder().setCommon(common.buildEnabled(value)).build() fun Piper.MessageMatch.matches(message: MessageInfo, helpers: IExtensionHelpers, callbacks: IBurpExtenderCallbacks): Boolean = ( (this.prefix == null || this.prefix.size() == 0 || message.content.startsWith(this.prefix)) && diff --git a/src/main/kotlin/burp/Serialization.kt b/src/main/kotlin/burp/Serialization.kt index 972aee5..c76b9ec 100644 --- a/src/main/kotlin/burp/Serialization.kt +++ b/src/main/kotlin/burp/Serialization.kt @@ -19,10 +19,19 @@ fun configFromYaml(value: String): Piper.Config { copyListOfStructured("httpListeners", b::addHttpListener, ::httpListenerFromMap) copyListOfStructured("commentators", b::addCommentator, ::commentatorFromMap) copyListOfStructured("intruderPayloadProcessors", b::addIntruderPayloadProcessor, ::minimalToolFromMap) + copyListOfStructured("highlighters", b::addHighlighter, ::highlighterFromMap) } return b.build() } +fun highlighterFromMap(source: Map): Piper.Highlighter { + val b = Piper.Highlighter.newBuilder()!! + source.copyBooleanFlag("overwrite", b::setOverwrite) + val c = source["color"] + if (c != null) b.color = c.toString() + return b.setCommon(minimalToolFromMap(source)).build() +} + fun commentatorFromMap(source: Map): Piper.Commentator { val b = Piper.Commentator.newBuilder()!! source.copyBooleanFlag("overwrite", b::setOverwrite) @@ -264,6 +273,13 @@ fun Piper.Commentator.toMap(): Map { return m } +fun Piper.Highlighter.toMap(): Map { + val m = this.common.toMap() + if (this.overwrite) m["overwrite"] = true + if (!this.color.isNullOrEmpty()) m["color"] = this.color + return m +} + fun Piper.MinimalTool.toMap(): MutableMap { val m = this.cmd.toMap() m["name"] = this.name!! diff --git a/src/main/proto/burp/piper.proto b/src/main/proto/burp/piper.proto index 6e80b63..efbc3a6 100644 --- a/src/main/proto/burp/piper.proto +++ b/src/main/proto/burp/piper.proto @@ -102,6 +102,12 @@ message Commentator { bool overwrite = 3; } +message Highlighter { + MinimalTool common = 1; + string color = 2; + bool overwrite = 3; +} + message Config { repeated MinimalTool macro = 1; repeated MessageViewer messageViewer = 2; @@ -110,6 +116,7 @@ message Config { repeated Commentator commentator = 5; bool developer = 6; repeated MinimalTool intruderPayloadProcessor = 7; + repeated Highlighter highlighter = 8; } message MimeTypes {