├── README.md ├── java ├── .gitignore ├── BurpExtender.java ├── build.gradle └── settings.gradle ├── python └── IntruderPayloads.py ├── ruby └── IntruderPayloads.rb └── server ├── Demo.aspx └── server.js /README.md: -------------------------------------------------------------------------------- 1 | # Sample Burp Suite extension: Intruder payloads 2 | 3 | This example shows how you can use an extension to: 4 | - Generate custom Intruder payloads 5 | - Apply custom processing to Intruder payloads (including built-in ones) 6 | 7 | When an extension registers itself as an Intruder payload provider, this will 8 | be available within the Intruder UI for the user to select as the payload 9 | source for an attack. When an extension registers itself as a payload 10 | processor, the user can create a payload processing rule and select the 11 | extension's processor as the rule's action. 12 | 13 | When Burp calls out to a payload provider to generate a payload, it passes the 14 | base value of the payload position as a parameter. This allows you to create 15 | attacks in which a whole block of serialized data is marked as the payload 16 | position, and your extension places payloads into suitable locations within 17 | that data, and re-serializes the data to create a valid request. Hence, you can 18 | use Intruder's powerful attack engine to automatically manipulate input deep 19 | within complex data structures. 20 | 21 | This example is artificially simple, and generates two payloads: one to identify 22 | basic XSS, and one to trigger the ficititious vulnerability that was used in the 23 | [custom scanner checks 24 | example](//github.com/PortSwigger/example-scanner-checks). It then uses a custom 25 | payload processor to reconstruct the serialized data structure around the custom 26 | payload. 27 | 28 | This repository includes source code for Java, Python and Ruby. It also includes 29 | a server (for ASP.NET and NodeJS) that extends the [serialization 30 | example](//github.com/PortSwigger/example-custom-editor-tab) to add some 31 | fictitious bugs so that you can test the custom payloads, and see that the two 32 | vulnerabilities are triggered. 33 | 34 | After loading the extension, you'll need to: 35 | - Select "Extension-generated" payloads as your Intruder payloads type. 36 | - Add a payload processing rule choosing the "Invoke Burp extension" processor. 37 | - Start an attack against a POST sent to the included webserver. 38 | 39 | Note: the sample server uses the JavaScript btoa() function to perform 40 | Base64-encoding on the client side. This function is not supported by Internet 41 | Explorer, but works on most other browsers. 42 | -------------------------------------------------------------------------------- /java/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | build/ 3 | -------------------------------------------------------------------------------- /java/BurpExtender.java: -------------------------------------------------------------------------------- 1 | package burp; 2 | 3 | public class BurpExtender implements IBurpExtender, IIntruderPayloadGeneratorFactory, IIntruderPayloadProcessor 4 | { 5 | private IExtensionHelpers helpers; 6 | 7 | // hard-coded payloads 8 | // [in reality, you would use an extension for something cleverer than this] 9 | private static final byte[][] PAYLOADS = 10 | { 11 | "|".getBytes(), 12 | "".getBytes(), 13 | }; 14 | 15 | // 16 | // implement IBurpExtender 17 | // 18 | 19 | @Override 20 | public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) 21 | { 22 | // obtain an extension helpers object 23 | helpers = callbacks.getHelpers(); 24 | 25 | // set our extension name 26 | callbacks.setExtensionName("Custom intruder payloads"); 27 | 28 | // register ourselves as an Intruder payload generator 29 | callbacks.registerIntruderPayloadGeneratorFactory(this); 30 | 31 | // register ourselves as an Intruder payload processor 32 | callbacks.registerIntruderPayloadProcessor(this); 33 | } 34 | 35 | // 36 | // implement IIntruderPayloadGeneratorFactory 37 | // 38 | 39 | @Override 40 | public String getGeneratorName() 41 | { 42 | return "My custom payloads"; 43 | } 44 | 45 | @Override 46 | public IIntruderPayloadGenerator createNewInstance(IIntruderAttack attack) 47 | { 48 | // return a new IIntruderPayloadGenerator to generate payloads for this attack 49 | return new IntruderPayloadGenerator(); 50 | } 51 | 52 | // 53 | // implement IIntruderPayloadProcessor 54 | // 55 | 56 | @Override 57 | public String getProcessorName() 58 | { 59 | return "Serialized input wrapper"; 60 | } 61 | 62 | @Override 63 | public byte[] processPayload(byte[] currentPayload, byte[] originalPayload, byte[] baseValue) 64 | { 65 | // decode the base value 66 | String dataParameter = helpers.bytesToString(helpers.base64Decode(helpers.urlDecode(baseValue))); 67 | 68 | // parse the location of the input string in the decoded data 69 | int start = dataParameter.indexOf("input=") + 6; 70 | if (start == -1) 71 | return currentPayload; 72 | String prefix = dataParameter.substring(0, start); 73 | int end = dataParameter.indexOf("&", start); 74 | if (end == -1) 75 | end = dataParameter.length(); 76 | String suffix = dataParameter.substring(end, dataParameter.length()); 77 | 78 | // rebuild the serialized data with the new payload 79 | dataParameter = prefix + helpers.bytesToString(currentPayload) + suffix; 80 | return helpers.stringToBytes(helpers.urlEncode(helpers.base64Encode(dataParameter))); 81 | } 82 | 83 | // 84 | // class to generate payloads from a simple list 85 | // 86 | 87 | class IntruderPayloadGenerator implements IIntruderPayloadGenerator 88 | { 89 | int payloadIndex; 90 | 91 | @Override 92 | public boolean hasMorePayloads() 93 | { 94 | return payloadIndex < PAYLOADS.length; 95 | } 96 | 97 | @Override 98 | public byte[] getNextPayload(byte[] baseValue) 99 | { 100 | byte[] payload = PAYLOADS[payloadIndex]; 101 | payloadIndex++; 102 | return payload; 103 | } 104 | 105 | @Override 106 | public void reset() 107 | { 108 | payloadIndex = 0; 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /java/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | repositories { 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | compile 'net.portswigger.burp.extender:burp-extender-api:1.7.13' 9 | } 10 | 11 | sourceSets { 12 | main { 13 | java { 14 | srcDir '.' 15 | } 16 | } 17 | } 18 | 19 | task fatJar(type: Jar) { 20 | baseName = project.name + '-all' 21 | from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } 22 | with jar 23 | } 24 | -------------------------------------------------------------------------------- /java/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'IntruderPayloads' 2 | -------------------------------------------------------------------------------- /python/IntruderPayloads.py: -------------------------------------------------------------------------------- 1 | from burp import IBurpExtender 2 | from burp import IIntruderPayloadGeneratorFactory 3 | from burp import IIntruderPayloadProcessor 4 | from burp import IIntruderPayloadGenerator 5 | 6 | # hard-coded payloads 7 | # [in reality, you would use an extension for something cleverer than this] 8 | 9 | PAYLOADS = [ 10 | bytearray("|"), 11 | bytearray("") 12 | ] 13 | 14 | class BurpExtender(IBurpExtender, IIntruderPayloadGeneratorFactory, IIntruderPayloadProcessor): 15 | 16 | # 17 | # implement IBurpExtender 18 | # 19 | 20 | def registerExtenderCallbacks(self, callbacks): 21 | # obtain an extension helpers object 22 | self._helpers = callbacks.getHelpers() 23 | 24 | # set our extension name 25 | callbacks.setExtensionName("Custom intruder payloads") 26 | 27 | # register ourselves as an Intruder payload generator 28 | callbacks.registerIntruderPayloadGeneratorFactory(self) 29 | 30 | # register ourselves as an Intruder payload processor 31 | callbacks.registerIntruderPayloadProcessor(self) 32 | 33 | # 34 | # implement IIntruderPayloadGeneratorFactory 35 | # 36 | 37 | def getGeneratorName(self): 38 | return "My custom payloads" 39 | 40 | def createNewInstance(self, attack): 41 | # return a new IIntruderPayloadGenerator to generate payloads for this attack 42 | return IntruderPayloadGenerator() 43 | 44 | # 45 | # implement IIntruderPayloadProcessor 46 | # 47 | 48 | def getProcessorName(self): 49 | return "Serialized input wrapper" 50 | 51 | def processPayload(self, currentPayload, originalPayload, baseValue): 52 | # decode the base value 53 | dataParameter = self._helpers.bytesToString( 54 | self._helpers.base64Decode(self._helpers.urlDecode(baseValue))) 55 | 56 | # parse the location of the input string in the decoded data 57 | start = dataParameter.index("input=") + 6 58 | if start == -1: 59 | return currentPayload 60 | 61 | prefix = dataParameter[0:start] 62 | end = dataParameter.index("&", start) 63 | if end == -1: 64 | end = len(dataParameter) 65 | 66 | suffix = dataParameter[end:len(dataParameter)] 67 | 68 | # rebuild the serialized data with the new payload 69 | dataParameter = prefix + self._helpers.bytesToString(currentPayload) + suffix 70 | return self._helpers.stringToBytes( 71 | self._helpers.urlEncode(self._helpers.base64Encode(dataParameter))) 72 | 73 | # 74 | # class to generate payloads from a simple list 75 | # 76 | 77 | class IntruderPayloadGenerator(IIntruderPayloadGenerator): 78 | def __init__(self): 79 | self._payloadIndex = 0 80 | 81 | def hasMorePayloads(self): 82 | return self._payloadIndex < len(PAYLOADS) 83 | 84 | def getNextPayload(self, baseValue): 85 | payload = PAYLOADS[self._payloadIndex] 86 | self._payloadIndex = self._payloadIndex + 1 87 | 88 | return payload 89 | 90 | def reset(self): 91 | self._payloadIndex = 0 92 | -------------------------------------------------------------------------------- /ruby/IntruderPayloads.rb: -------------------------------------------------------------------------------- 1 | java_import 'burp.IBurpExtender' 2 | java_import 'burp.IIntruderPayloadGeneratorFactory' 3 | java_import 'burp.IIntruderPayloadProcessor' 4 | java_import 'burp.IIntruderPayloadGenerator' 5 | 6 | # hard-coded payloads 7 | # [in reality, you would use an extension for something cleverer than this] 8 | 9 | PAYLOADS = [ 10 | "|".bytes.to_a, 11 | "".bytes.to_a 12 | ] 13 | 14 | class BurpExtender 15 | include IBurpExtender, IIntruderPayloadGeneratorFactory, IIntruderPayloadProcessor 16 | 17 | # 18 | # implement IBurpExtender 19 | # 20 | 21 | def registerExtenderCallbacks(callbacks) 22 | # obtain an extension helpers object 23 | @helpers = callbacks.getHelpers 24 | 25 | # set our extension name 26 | callbacks.setExtensionName "Custom intruder payloads" 27 | 28 | # register ourselves as an Intruder payload generator 29 | callbacks.registerIntruderPayloadGeneratorFactory self 30 | 31 | # register ourselves as an Intruder payload processor 32 | callbacks.registerIntruderPayloadProcessor self 33 | end 34 | 35 | # 36 | # implement IIntruderPayloadGeneratorFactory 37 | # 38 | 39 | def getGeneratorName() 40 | "My custom payloads" 41 | end 42 | 43 | def createNewInstance(attack) 44 | # return a new IIntruderPayloadGenerator to generate payloads for this attack 45 | IntruderPayloadGenerator.new 46 | end 47 | 48 | # 49 | # implement IIntruderPayloadProcessor 50 | # 51 | 52 | def getProcessorName() 53 | "Serialized input wrapper" 54 | end 55 | 56 | def processPayload(currentPayload, originalPayload, baseValue) 57 | # decode the base value 58 | dataParameter = @helpers.bytesToString( 59 | @helpers.base64Decode(@helpers.urlDecode baseValue)) 60 | 61 | # parse the location of the input string in the decoded data 62 | start = dataParameter.index("input=") + 6 63 | return currentPayload if start == -1 64 | 65 | prefix = dataParameter[0...start] 66 | end_ = dataParameter.index("&", start) 67 | end_ = dataParameter.length if end_ == -1 68 | 69 | suffix = dataParameter[end_..-1] 70 | 71 | # rebuild the serialized data with the new payload 72 | dataParameter = prefix + @helpers.bytesToString(currentPayload) + suffix 73 | return @helpers.stringToBytes( 74 | @helpers.urlEncode(@helpers.base64Encode dataParameter)) 75 | end 76 | end 77 | 78 | # 79 | # class to generate payloads from a simple list 80 | # 81 | 82 | class IntruderPayloadGenerator 83 | include IIntruderPayloadGenerator 84 | 85 | def initialize() 86 | @payloadIndex = 0 87 | end 88 | 89 | def hasMorePayloads() 90 | @payloadIndex < PAYLOADS.length 91 | end 92 | 93 | def getNextPayload(baseValue) 94 | payload = PAYLOADS[@payloadIndex] 95 | @payloadIndex = @payloadIndex + 1 96 | 97 | return payload 98 | end 99 | 100 | def reset() 101 | @payloadIndex = 0 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /server/Demo.aspx: -------------------------------------------------------------------------------- 1 | <%@ Page Language="C#" %> 2 | 3 | <% 4 | string data = Request.Form["data"]; 5 | if (data != null) 6 | { 7 | try 8 | { 9 | data = Encoding.ASCII.GetString(Convert.FromBase64String(data)); 10 | data = HttpUtility.ParseQueryString(data)["input"]; 11 | 12 | // add a fictitious input vulnerability 13 | if (data.Contains("|")) 14 | throw new Exception("Unexpected pipe"); 15 | 16 | output.InnerHtml = "Input received: " + data; 17 | } 18 | catch (Exception ex) 19 | { 20 | output.InnerHtml = ex.ToString(); 21 | } 22 | } 23 | %> 24 | 25 | 26 | 27 | 28 | Demo 29 | 38 | 39 | 40 | 45 |
46 | Input: 47 |
48 |
49 |
50 | 51 | 52 |
53 |
54 |
55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | 3 | const PORT = 8000; 4 | 5 | const parseQueryString = str => str 6 | .split('&') 7 | .map(pair => { 8 | const idx = pair.indexOf('='); 9 | if (idx === -1) return null; 10 | return [pair.substr(0, idx), pair.substr(idx+1)]; 11 | }) 12 | .reduce((acc, kvp) => { 13 | if (kvp !== null) acc[unescape(kvp[0])] = unescape(kvp[1]); 14 | return acc; 15 | }, {}); 16 | 17 | console.log(`Serving on http://localhost:${PORT}, press ctrl+c to stop`); 18 | http.createServer((req, res) => { 19 | if (req.method === 'POST') { 20 | const body = []; 21 | req.on('data', chunk => { 22 | body.push(chunk); 23 | }).on('end', () => { 24 | var data = parseQueryString(Buffer.concat(body).toString()).data; 25 | data = new Buffer(data, 'base64').toString('ascii'); 26 | data = parseQueryString(data).input; 27 | 28 | // add a fictitious input vulnerability 29 | if (data !== undefined && data.indexOf('|') !== -1) { 30 | res.writeHead(500, {'Content-Type': 'text/html'}); 31 | res.end("Error: Unexpected pipe"); 32 | } else { 33 | res.writeHead(200, {'Content-Type': 'text/html'}); 34 | res.end(`Input received: ${data}`); 35 | } 36 | }); 37 | } else { 38 | res.writeHead(200, {'Content-Type': 'text/html'}); 39 | res.end(` 40 | 41 | 42 | 43 | Demo 44 | 53 | 54 | 55 | 60 |
61 | Input: 62 |
63 |
64 |
65 | 66 | 67 |
68 |
69 |
70 | 71 | 72 | `); 73 | } 74 | }).listen(PORT, 'localhost'); 75 | --------------------------------------------------------------------------------