diff --git a/.gitignore b/.gitignore index 6ed61a6..73dc2ad 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ bin/ lib/burpsuite_pro*.jar lib/burpsuite_free*.jar lib/burpsuite_community*.jar + +# Avoid committing generated files +src/main/resources/static/*.jar \ No newline at end of file diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000..108a70a --- /dev/null +++ b/FAQ.md @@ -0,0 +1,51 @@ +FAQ +=== + +Is Burp Suite Free/Community edition supported? +----------------------------------------------- + +No, it is not. Burp Rest API exposes functionalities that are best suited for the Professional +version of Burp Suite. Even if it was possible to start _burp-rest-api_ using the Free version of Burp, this is no longer possible and the support won't be included in future releases. + +Whenever I run the gradle command I receive an error. What can be the the cause? +---------------------------------------------------------------------------- + +Often times, Gradle introduces incompatibility between major versions, therefore +the recommended way of executing any Gradle build is by using the Gradle +Wrapper (in short just “Wrapper”). The Wrapper is a script that invokes a +declared version of Gradle, downloading it beforehand if necessary. + +See [Issue 37](https://github.com/vmware/burp-rest-api/issues/37). + +Is it possible to run burp-rest-api graphically in remote servers? +------------------------------------------------------------------ + +Yes, it is possible to run Burp in graphical environments in multiple +configurations (X Forwarding, Full VNC, RDP, XPRA). + +For running a non persistent X Forwarding session on your OS you can follow this +[guide](https://uisapp2.iu.edu/confluence-prd/pages/viewpage.action?pageId=280461906). + +See [Issue 60](https://github.com/vmware/burp-rest-api/issues/60). + +Is it possible to customize the binding address:port for Burp Proxy and/or burp-rest-api APIs? +---------------------------------------------------------------------------------------------- + +There are two binding ports in a standard burp-rest-api setup: +- **burp-rest-api RPC mechanism**. Both IP address and port can be customized at runtime using command line arguments (namely _--server.address_ and _--server.port_) +- **Burp Proxy Listener**. This is a Burp Suite configuration, and can be customized using a custom project option file. + +``` + "request_listeners":[ + { + "certificate_mode":"per_host", + "listen_mode":"192.168.1.1", + "listener_port":8080, + "running":true + } +``` + +Is Burp Suite v2 supported? +---------------------------------------------------------------------------------------------- + +Next generation Burp Suite v2 is a beta release at the time of writing this FAQ. While we will *try* to mantain support for both Burp Suite stable and beta, we cannot ensure full compability. For production, please stay on Burp Suite Professional stable branch. diff --git a/LICENSE b/LICENSE index 2d8638a..36c7e18 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ Copyright (c) 2016 VMware, Inc. All Rights Reserved. +Copyright (c) 2018 Doyensec LLC. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of diff --git a/README.md b/README.md index dba72fa..ec07444 100644 --- a/README.md +++ b/README.md @@ -4,105 +4,55 @@ A REST/JSON API to the Burp Suite security tool. -Upon successfully building the project, an executable JAR file is created with the Burp Suite Professional JAR bundled - in it. When the JAR is launched, it provides a REST/JSON endpoint to access the Scanner, Spider, Proxy and other - features of the Burp Suite Professional security tool. - -## Try it out - -### Prerequisites - -* Java 8 -* Gradle -* Licensed Burp Suite Professional version 1.7.x or later from: - - -### Build & Run - -1. [Download](https://portswigger.net/burp/download.html) the Professional edition of Burp Suite. -2. Create a `lib` folder under the project directory and place the Burp Suite JAR file into it and rename it to "burpsuite_pro.jar". -3. The project can be run either by running the Gradle Spring `bootRun` command or by directly launching the JAR - created from building the project: - -``` - gradlew bootRun -``` - -or - -``` - # build the jar - gradlew clean build - # and run it - java -jar build/libs/burp-rest-api-1.0.2.jar -``` -The version number of the JAR should match the version number from `build.gradle` while generating the JAR. +Since version 2.0.0 it is possible to run the burp-rest-api release jar, +downloading it directly from the +[release channel](https://github.com/vmware/burp-rest-api/releases). ## Documentation ### Configuration -By default, Burp is launched in headless mode with the Proxy running on port 8080/tcp (localhost only) and the REST endpoint running on 8090/tcp (localhost only). +By default, Burp is launched in headless mode with the Proxy running on port 8080/tcp (**localhost only**) and the REST endpoint running on 8090/tcp (**localhost only**). To __run Burp in UI mode__ from the command line, use one of the following commands: -With the `bootRun` command: -``` - gradlew bootRun -Djava.awt.headless=false ``` -or -``` - gradlew bootRun -Dheadless.mode=false -``` -or with the `bootRun` command using the `-PappArgs` to pass args directly to burp suite : -``` - gradlew bootRun -PappArgs="['-Djava.awt.headless=false','--project-file=./test.burp']" -``` -With the executable JAR: -``` - java -jar burp-rest-api-1.0.2.jar -Djava.awt.headless=false + java -jar burp-rest-api-2.0.0.jar -Djava.awt.headless=false --burp.jar=./lib/burpsuite_pro.jar ``` or ``` - java -jar burp-rest-api-1.0.2.jar --headless.mode=false + java -jar burp-rest-api-2.0.0.jar --headless.mode=false --burp.jar=./lib/burpsuite_pro.jar ``` To __modify the server port__ on which the API is accessible, use one of the following commands: -With the `bootRun` command: ``` - gradlew bootRun -Dserver.port=8081 + java -jar burp-rest-api-2.0.0.jar --server.port=8081 --burp.jar=./lib/burpsuite_pro.jar ``` or ``` - gradlew bootRun -Dport=8081 -``` - -With the executable JAR: -``` - java -jar burp-rest-api-1.0.2.jar --server.port=8081 -``` -or -``` - java -jar burp-rest-api-1.0.2.jar --port=8081 + java -jar burp-rest-api-2.0.0.jar --port=8081 --burp.jar=./lib/burpsuite_pro.jar ``` You can also __modify the server address__, used for network address binding: -With the `bootRun` command: ``` - gradlew bootRun -Dserver.address=192.168.1.2 + java -jar burp-rest-api-2.0.0.jar --server.address=192.168.1.2 --burp.jar=./lib/burpsuite_pro.jar ``` or ``` - gradlew bootRun -Daddress=192.168.1.2 + java -jar burp-rest-api-2.0.0.jar --address=192.168.1.2 --burp.jar=./lib/burpsuite_pro.jar ``` ### Command Line Arguments The following command line arguments are used only by the extension to configure the run mode and port number. +`--burp.jar=` : Loads the Burp jar dinamically, and expose it through REST APIs. This flag is required. + +`--burp.ext=` : The REST API endpoint is available at the given port number. `--port=` works as short hand argument. @@ -122,7 +72,7 @@ Command line arguments passed to the executable burp-rest-api JAR are forwarded `--config-file=` : Opens the project using the options contained in the selected project configuration file. To load multiple project configurations, this argument can be passed more than once with different values. - `--user-config-file=` : Opens the project using the options contained in the selected user configuration file. To +`--user-config-file=` : Opens the project using the options contained in the selected user configuration file. To load multiple user configurations, this argument can be passed more than once with different values. For more information on Projects, refer to the Burp Suite documentation @@ -155,18 +105,55 @@ This project also comes with a client (_BurpClient.java_) written in Java for us ## Credits This project is originally inspired from [Resty-Burp](https://github.com/continuumsecurity/resty-burp - "continuumsecurity/resty-burp: REST/JSON interface to Burp Suite") and is developed in partnership with [Doyensec](https://www.doyensec.com). + "continuumsecurity/resty-burp: REST/JSON interface to Burp Suite"), and is developed in partnership with [Doyensec LLC](https://doyensec.com/). ## Contributing The burp-rest-api project team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For - any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). For more detailed - information, refer to [CONTRIBUTING.md](CONTRIBUTING.md). + any questions about the CLA process, please refer to our [CLA FAQ](https://cla.vmware.com/faq). For more detailed + information, refer to [CONTRIBUTING.md](CONTRIBUTING.md) and [FAQ.md](FAQ.md). + +### Develop + +Upon successfully building the project, an executable JAR file is created. +The Burp suite JAR can be loaded dinamically through the `--burp.jar=` argument. +When the JAR is launched, it provides a REST/JSON endpoint to access the Scanner, Spider, Proxy and other + features of the Burp Suite Professional security tool. + +#### Prerequisites + +* Java 8 +* Gradle +* Licensed Burp Suite Professional version 1.7.x or later from: + + +#### Build & Run + +1. [Download](https://portswigger.net/burp/download.html) the Professional edition of Burp Suite. +2. The project can be run either by running the Gradle Spring `bootRun` command or by directly launching the JAR + created from building the project: +3. OPTIONAL: Create a `lib` folder under the project directory and place the Burp Suite JAR file into it and rename it to "burpsuite_pro.jar" in order to run the integration tests. + +``` + ./gradlew bootRun --burp.jar=./lib/burpsuite_pro.jar +``` + +or + +``` + # build the jar + ./gradlew clean build + # and run it + java -jar build/libs/burp-rest-api-2.0.0.jar --burp.jar=./lib/burpsuite_pro.jar +``` +The version number of the JAR should match the version number from `build.gradle` while generating the JAR. + ## License Copyright (c) 2016 VMware, Inc. All Rights Reserved. +Copyright (c) 2018 Doyensec LLC. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of diff --git a/build.gradle b/build.gradle index a2f4394..e78ec80 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ apply plugin: 'eclipse' apply plugin: 'spring-boot' final def extensionName = 'burp-rest-api' -version = '1.0.4' +version = '2.0.0' def updateVersion() { def configFile = new File('src/main/resources/application.yml') @@ -25,6 +25,17 @@ def updateVersion() { configFile.write(configContent, 'UTF-8') } +sourceSets { + entrypoint { + java { + compileClasspath += main.output + runtimeClasspath += main.output + } + } +} + +test.onlyIf { file('./lib/burpsuite_pro.jar').exists() } + allprojects { //Display warning println " !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" @@ -55,19 +66,22 @@ targetCompatibility = 1.8 File schemaTargetDir = new File('build/generated-schema') configurations { - jaxb + jaxb compile.exclude module: "spring-boot-starter-tomcat" + testCompile.extendsFrom entrypointCompile + testRuntime.extendsFrom entrypointRuntime } dependencies { compile('org.springframework.boot:spring-boot-starter-web') compile("org.springframework.boot:spring-boot-starter-jetty") - compile fileTree(dir: 'lib', include: '**/*.jar') compile "io.springfox:springfox-swagger2:2.+" compile "io.springfox:springfox-swagger-ui:2.+" - - compile name: 'burpsuite_pro' + compileOnly "net.portswigger.burp.extender:burp-extender-api:1.7.22" + + entrypointCompileOnly "net.portswigger.burp.extender:burp-extender-api:1.7.22" + testCompile fileTree(dir: 'lib', include: '**/*.jar') testCompile('org.springframework.boot:spring-boot-starter-test') testCompile('org.apache.httpcomponents:httpclient:4.5.2') @@ -97,6 +111,20 @@ bootRun { } } + +task entrypointJar(type: Jar) { + destinationDir = file("src/main/resources/static/") + from(sourceSets.entrypoint.output) { + include '**/*.class' + } + archiveName = 'rest-api.jar' +} +jar.dependsOn entrypointJar +task deleteEntrypointOriginalJar(type: Delete) { + delete entrypointJar.archivePath.toString() + '.original' +} +assemble.dependsOn deleteEntrypointOriginalJar + task extractApi(type: Copy) { from(zipTree('build/libs/' + extensionName + '-' + version + '.jar')) into 'build/libs' diff --git a/src/entrypoint/java/burp/BurpExtender.java b/src/entrypoint/java/burp/BurpExtender.java new file mode 100644 index 0000000..bab7f08 --- /dev/null +++ b/src/entrypoint/java/burp/BurpExtender.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Doyensec LLC. + */ + +package burp; + +import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * burp.BurpExtender is the burp-rest-api 2nd-gen entrypoint. + * + * This class search for the burp.LegacyBurpExtender 1st-gen entrypoint in the default classpath in order to execute it + * through reflection. This is needed in order to made Burp able to load more than one extension at a time. + */ +public class BurpExtender implements IBurpExtender { + /** + * This method is invoked when the extension is loaded. It registers an + * instance of the + * IBurpExtenderCallbacks interface, providing methods that may + * be invoked by the extension to perform various actions. + * + * @param callbacks An + * IBurpExtenderCallbacks object. + */ + @Override + public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) { + try { + legacyRegisterExtenderCallbacks(callbacks); + } catch (Exception e) { + PrintWriter stderr = new PrintWriter(callbacks.getStderr(), true); + stderr.format("Exception: %s %s %s", e.getClass().getCanonicalName(), e.getCause(), e.getMessage()); + } + } + + private static void legacyRegisterExtenderCallbacks(IBurpExtenderCallbacks callbacks) + throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { + + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Class clazz = classLoader.loadClass("burp.LegacyBurpExtender"); + Object obj = clazz.newInstance(); + Method method = clazz.getMethod("registerExtenderCallbacks", IBurpExtenderCallbacks.class); + method.invoke(obj, callbacks); + } +} \ No newline at end of file diff --git a/src/main/java/burp/BurpExtender.java b/src/main/java/burp/LegacyBurpExtender.java similarity index 82% rename from src/main/java/burp/BurpExtender.java rename to src/main/java/burp/LegacyBurpExtender.java index ee3ce56..33252a6 100644 --- a/src/main/java/burp/BurpExtender.java +++ b/src/main/java/burp/LegacyBurpExtender.java @@ -6,15 +6,15 @@ import java.io.PrintWriter; /** - * Copyright VMware, Inc. All rights reserved. -- VMware Confidential + * Copyright VMware, Inc. All rights reserved. */ -public class BurpExtender implements IBurpExtender { - private static final Logger log = LoggerFactory.getLogger(BurpExtender.class); - private static BurpExtender instance; +public class LegacyBurpExtender implements IBurpExtender { + private static final Logger log = LoggerFactory.getLogger(LegacyBurpExtender.class); + private static LegacyBurpExtender instance; private IBurpExtenderCallbacks callbacks; private IExtensionHelpers helpers; - public static BurpExtender getInstance() { + public static LegacyBurpExtender getInstance() { return instance; } diff --git a/src/main/java/com/vmware/burp/extension/domain/HttpMessage.java b/src/main/java/com/vmware/burp/extension/domain/HttpMessage.java index 51ac007..d35b848 100644 --- a/src/main/java/com/vmware/burp/extension/domain/HttpMessage.java +++ b/src/main/java/com/vmware/burp/extension/domain/HttpMessage.java @@ -6,7 +6,7 @@ package com.vmware.burp.extension.domain; -import burp.BurpExtender; +import burp.LegacyBurpExtender; import burp.ICookie; import burp.IExtensionHelpers; import burp.IHttpRequestResponse; @@ -88,7 +88,7 @@ public HttpMessage(IHttpRequestResponse iHttpRequestResponse) { this.comment = iHttpRequestResponse.getComment(); this.highlight = iHttpRequestResponse.getHighlight(); - IExtensionHelpers helpers = BurpExtender.getInstance().getHelpers(); + IExtensionHelpers helpers = LegacyBurpExtender.getInstance().getHelpers(); IRequestInfo requestInfo = helpers.analyzeRequest(iHttpRequestResponse); this.url = requestInfo.getUrl(); this.method = requestInfo.getMethod(); diff --git a/src/main/java/com/vmware/burp/extension/domain/internal/SpiderQueueMap.java b/src/main/java/com/vmware/burp/extension/domain/internal/SpiderQueueMap.java index 251ee54..ce1877c 100644 --- a/src/main/java/com/vmware/burp/extension/domain/internal/SpiderQueueMap.java +++ b/src/main/java/com/vmware/burp/extension/domain/internal/SpiderQueueMap.java @@ -6,7 +6,7 @@ package com.vmware.burp.extension.domain.internal; -import burp.BurpExtender; +import burp.LegacyBurpExtender; import burp.IHttpRequestResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,7 +86,7 @@ public int getPercentageComplete() { int totalPercentCompletion = 0; for (String url : map.keySet()) { IHttpRequestResponse[] httpMessageListOld = map.get(url); - IHttpRequestResponse[] httpMessageListNew = BurpExtender.getInstance().getCallbacks().getSiteMap(url); + IHttpRequestResponse[] httpMessageListNew = LegacyBurpExtender.getInstance().getCallbacks().getSiteMap(url); if(compareSiteMap(httpMessageListNew, httpMessageListOld)){ totalPercentCompletion += 100; @@ -95,7 +95,7 @@ public int getPercentageComplete() { } } - map.replaceAll((url, v) -> BurpExtender.getInstance().getCallbacks().getSiteMap(url)); + map.replaceAll((url, v) -> LegacyBurpExtender.getInstance().getCallbacks().getSiteMap(url)); if(totalPercentCompletion > 0) { int percentComplete = totalPercentCompletion / map.size(); diff --git a/src/main/java/com/vmware/burp/extension/service/BurpService.java b/src/main/java/com/vmware/burp/extension/service/BurpService.java index 2289d9d..ce8324f 100644 --- a/src/main/java/com/vmware/burp/extension/service/BurpService.java +++ b/src/main/java/com/vmware/burp/extension/service/BurpService.java @@ -6,7 +6,7 @@ package com.vmware.burp.extension.service; -import burp.BurpExtender; +import burp.LegacyBurpExtender; import burp.IHttpRequestResponse; import burp.IScanIssue; import burp.IScanQueueItem; @@ -15,6 +15,7 @@ import com.vmware.burp.extension.domain.ScanIssue; import com.vmware.burp.extension.domain.internal.ScanQueueMap; import com.vmware.burp.extension.domain.internal.SpiderQueueMap; +import com.vmware.burp.extension.utils.UserConfigUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -25,15 +26,16 @@ import org.springframework.stereotype.Service; import org.springframework.util.FileCopyUtils; -import java.io.File; -import java.io.IOException; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.net.URL; +import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; import java.util.stream.Stream; @Service @@ -48,6 +50,7 @@ public class BurpService { private static final String TEMPORARY_PROJECT_FILE_NAME = "temp-project.burp"; private ScanQueueMap scans; private SpiderQueueMap spiders; + private String restApiPath; @Value("${java.awt.headless}") private boolean awtHeadLessMode; @@ -56,8 +59,12 @@ public class BurpService { private String version; @Autowired - public BurpService(ApplicationArguments args, @Value("${headless.mode}") boolean headlessMode) - throws IOException { + public BurpService(ApplicationArguments args, + @Value("${headless.mode}") boolean headlessMode, + @Value("${burp.jar:#{null}}") String burpJar, + @Value("${burp.ext:#{null}}") String burpExtension) + throws IOException, ClassNotFoundException, NoSuchMethodException, + InvocationTargetException, IllegalAccessException, URISyntaxException { if (!headlessMode) { log.info("Setting java.awt.headless to false..."); System.setProperty("java.awt.headless", Boolean.toString(false)); @@ -69,6 +76,19 @@ public BurpService(ApplicationArguments args, @Value("${headless.mode}") boolean String[] projectOptions; String[] userOptions; + UserConfigUtils ucu = new UserConfigUtils(); + + //Include the REST API Plugin User Options config + restApiPath = extractPlugin(); + ucu.registerBurpExtension(restApiPath); + + if (burpExtension != null) { + log.info("Loading extensions {}", burpExtension); + for (String extension : burpExtension.split(",")) { + ucu.registerBurpExtension(extension); + } + } + //Project Data File if (!args.containsOption(PROJECT_FILE)) { projectData = new String[]{generateProjectDataTempFile()}; @@ -91,11 +111,11 @@ public BurpService(ApplicationArguments args, @Value("${headless.mode}") boolean //User Options File if (!args.containsOption(USER_CONFIG_FILE)) { - userOptions = new String[]{generateUserOptionsTempFile()}; + userOptions = new String[]{USER_CONFIG_FILE_ARGUMENT + ucu.injectExtensions(generateUserOptionsTempFile())}; } else { userOptions = args.getOptionValues(USER_CONFIG_FILE).stream().toArray(String[]::new); for(int i = 0; i < userOptions.length; i++) { - userOptions[i] = USER_CONFIG_FILE_ARGUMENT + userOptions[i]; + userOptions[i] = USER_CONFIG_FILE_ARGUMENT + ucu.injectExtensions(userOptions[i]); } } @@ -103,12 +123,38 @@ public BurpService(ApplicationArguments args, @Value("${headless.mode}") boolean burpOptions = Stream.concat(Arrays.stream(burpOptions), Arrays.stream(userOptions)).toArray(String[]::new); log.info("Launching the Burp with options: {}", Arrays.toString(burpOptions)); - burp.StartBurp.main(burpOptions); + if (burpJar != null) { + log.info("Injecting ClassLoader with Jar: {}", burpJar); + URL url = new File(burpJar).toURI().toURL(); + injectClassLoader(url); + } + BurpService.class.getClassLoader().loadClass("burp.StartBurp") + .getMethod("main", String[].class) + .invoke(null, (Object)burpOptions); scans = new ScanQueueMap(); spiders = new SpiderQueueMap(3000); } + private static void injectClassLoader(URL url) + throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { + URLClassLoader loader = (URLClassLoader)ClassLoader.getSystemClassLoader(); + Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class}); + method.setAccessible(true); + method.invoke(loader, new Object[]{ url }); + } + + private String extractPlugin() throws IOException { + //Use temporary rest-api.jar plugin + Resource restApiFile = new ClassPathResource("/static/rest-api.jar"); + Path restApiTempFile = Files.createTempFile("rest-api_", ".jar"); + FileCopyUtils + .copy(FileCopyUtils.copyToByteArray(restApiFile.getInputStream()), + restApiTempFile.toFile()); + restApiTempFile.toFile().deleteOnExit(); + return restApiTempFile.toAbsolutePath().toString(); + } + private String generateProjectOptionsTempFile() throws IOException { //Use temporary project configuration file Resource defaultProjectOptionsFile = new ClassPathResource("/static/burp-default-project-options.json"); @@ -128,7 +174,7 @@ private String generateUserOptionsTempFile() throws IOException { .copy(FileCopyUtils.copyToByteArray(defaultUserOptionsFile.getInputStream()), userOptionsTempFile.toFile()); userOptionsTempFile.toFile().deleteOnExit(); - return USER_CONFIG_FILE_ARGUMENT + userOptionsTempFile.toAbsolutePath(); + return userOptionsTempFile.toAbsolutePath().toString(); } private String generateProjectDataTempFile() throws IOException { @@ -146,16 +192,16 @@ private String generateProjectDataTempFile() throws IOException { public String getConfigAsJson(String configPaths) { if (configPaths != null) { log.info("Retrieving the Burp Configuration for configPaths: " + configPaths); - return BurpExtender.getInstance().getCallbacks().saveConfigAsJson(configPaths); + return LegacyBurpExtender.getInstance().getCallbacks().saveConfigAsJson(configPaths); } else { log.info("Retrieving the Burp Configuration with empty configPaths"); - return BurpExtender.getInstance().getCallbacks().saveConfigAsJson(); + return LegacyBurpExtender.getInstance().getCallbacks().saveConfigAsJson(); } } public String getBurpVersion() { log.info("Retrieving the Burp Version..."); - return String.join(".", BurpExtender.getInstance().getCallbacks().getBurpVersion()); + return String.join(".", LegacyBurpExtender.getInstance().getCallbacks().getBurpVersion()); } public String getVersion() { @@ -165,12 +211,12 @@ public String getVersion() { public void updateConfigFromJson(String configJson) { log.info("Updating the Burp Configuration..."); - BurpExtender.getInstance().getCallbacks().loadConfigFromJson(configJson); + LegacyBurpExtender.getInstance().getCallbacks().loadConfigFromJson(configJson); } public List getProxyHistory() { List httpMessageList = new ArrayList<>(); - for (IHttpRequestResponse iHttpRequestResponse : BurpExtender.getInstance().getCallbacks() + for (IHttpRequestResponse iHttpRequestResponse : LegacyBurpExtender.getInstance().getCallbacks() .getProxyHistory()) { httpMessageList.add(new HttpMessage(iHttpRequestResponse)); } @@ -180,13 +226,13 @@ public List getProxyHistory() { public boolean scan(String baseUrl, boolean isActive) throws MalformedURLException { boolean inScope = isInScope(baseUrl); - log.info("Total SiteMap size: {}", BurpExtender.getInstance().getCallbacks().getSiteMap("").length); + log.info("Total SiteMap size: {}", LegacyBurpExtender.getInstance().getCallbacks().getSiteMap("").length); log.info("Is {} in Scope: {}", baseUrl, inScope); if (inScope) { - IHttpRequestResponse[] siteMapInScope = BurpExtender.getInstance().getCallbacks().getSiteMap(baseUrl); + IHttpRequestResponse[] siteMapInScope = LegacyBurpExtender.getInstance().getCallbacks().getSiteMap(baseUrl); log.info("Number of URLs submitting for Active/Passive Scan: {}", siteMapInScope.length); for (IHttpRequestResponse iHttpRequestResponse : siteMapInScope) { - URL url = BurpExtender.getInstance().getHelpers().analyzeRequest(iHttpRequestResponse) + URL url = LegacyBurpExtender.getInstance().getHelpers().analyzeRequest(iHttpRequestResponse) .getUrl(); if(url.getPort() == url.getDefaultPort()) { url = new URL(url.getProtocol(), url.getHost(), url.getFile()); @@ -201,14 +247,14 @@ public boolean scan(String baseUrl, boolean isActive) if(isActive) { //Trigger Burp's Active Scan log.debug("Submitting Active Scan for the URL {}", url.toExternalForm()); - IScanQueueItem iScanQueueItem = BurpExtender.getInstance().getCallbacks() + IScanQueueItem iScanQueueItem = LegacyBurpExtender.getInstance().getCallbacks() .doActiveScan(url.getHost(), url.getPort() != -1 ? url.getPort() : url.getDefaultPort(), useHttps, iHttpRequestResponse.getRequest()); scans.addItem(url.toExternalForm(), iScanQueueItem); }else{ //Trigger Burp's Passive Scan log.debug("Submitting Passive Scan for the URL {}", url.toExternalForm()); - BurpExtender.getInstance().getCallbacks() + LegacyBurpExtender.getInstance().getCallbacks() .doPassiveScan(url.getHost(), url.getPort() != -1 ? url.getPort() : url.getDefaultPort(), useHttps, iHttpRequestResponse.getRequest(), iHttpRequestResponse.getResponse()); } @@ -229,7 +275,7 @@ public void clearScans() { public List getSiteMap(String urlPrefix) { List httpMessageList = new ArrayList<>(); - for (IHttpRequestResponse iHttpRequestResponse : BurpExtender.getInstance().getCallbacks() + for (IHttpRequestResponse iHttpRequestResponse : LegacyBurpExtender.getInstance().getCallbacks() .getSiteMap(urlPrefix)) { httpMessageList.add(new HttpMessage(iHttpRequestResponse)); } @@ -239,25 +285,25 @@ public List getSiteMap(String urlPrefix) { // urlString should be encoded for the correct matching. public boolean isInScope(String urlString) throws MalformedURLException { URL url = new URL(urlString); - return BurpExtender.getInstance().getCallbacks().isInScope(url); + return LegacyBurpExtender.getInstance().getCallbacks().isInScope(url); } // urlString should be encoded for the correct matching. public void includeInScope(String urlString) throws MalformedURLException { URL url = new URL(urlString); - BurpExtender.getInstance().getCallbacks().includeInScope(url); + LegacyBurpExtender.getInstance().getCallbacks().includeInScope(url); } // urlString should be encoded for the correct matching. public void excludeFromScope(String urlString) throws MalformedURLException { URL url = new URL(urlString); - BurpExtender.getInstance().getCallbacks().excludeFromScope(url); + LegacyBurpExtender.getInstance().getCallbacks().excludeFromScope(url); } public List getIssues(String urlPrefix) { List scanIssues = new ArrayList<>(); - IScanIssue[] iScanIssues = BurpExtender.getInstance().getCallbacks() + IScanIssue[] iScanIssues = LegacyBurpExtender.getInstance().getCallbacks() .getScanIssues(urlPrefix); for (IScanIssue iScanIssue : iScanIssues) { scanIssues.add(new ScanIssue(iScanIssue)); @@ -268,9 +314,9 @@ public List getIssues(String urlPrefix) { public byte[] generateScanReport(String urlPrefix, ReportType reportType) throws IOException { Path reportFile = Files.createTempFile("Report", "." + reportType.getReportType()); reportFile.toFile().deleteOnExit(); - BurpExtender.getInstance().getCallbacks() + LegacyBurpExtender.getInstance().getCallbacks() .generateScanReport(reportType.getReportType(), - BurpExtender.getInstance().getCallbacks().getScanIssues(urlPrefix), + LegacyBurpExtender.getInstance().getCallbacks().getScanIssues(urlPrefix), reportFile.toFile()); return Files.readAllBytes(reportFile); } @@ -287,8 +333,8 @@ public int getSpiderPercentageComplete() { public void sendToSpider(String baseUrl) throws MalformedURLException { URL url = new URL(baseUrl); - BurpExtender.getInstance().getCallbacks().sendToSpider(url); - spiders.addItem(url.toString(),BurpExtender.getInstance().getCallbacks().getSiteMap(url.toString())); + LegacyBurpExtender.getInstance().getCallbacks().sendToSpider(url); + spiders.addItem(url.toString(),LegacyBurpExtender.getInstance().getCallbacks().getSiteMap(url.toString())); } public void exitSuite(boolean promptUser) { @@ -297,7 +343,7 @@ public void exitSuite(boolean promptUser) { log.info("Burp suite is running in headless mode. Overriding the promptUser to false."); promptUser = false; } - BurpExtender.getInstance().getCallbacks().exitSuite(promptUser); + LegacyBurpExtender.getInstance().getCallbacks().exitSuite(promptUser); } } diff --git a/src/main/java/com/vmware/burp/extension/utils/UserConfigUtils.java b/src/main/java/com/vmware/burp/extension/utils/UserConfigUtils.java new file mode 100644 index 0000000..df0c2ca --- /dev/null +++ b/src/main/java/com/vmware/burp/extension/utils/UserConfigUtils.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2018 Doyensec LLC. + */ +package com.vmware.burp.extension.utils; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.util.FileCopyUtils; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class helps in injecting custom extensions to user config through the burp-rest-api command line. + */ +public class UserConfigUtils { + /** + * Extension POJO representing the Burp configuration for a given extension. + */ + public static class Extension { + private static Map pluginExtToType = new HashMap() {{ + put("jar", "java"); + put("rb", "ruby"); + put("py", "python"); + }}; + private String errors = "console"; + private String output = "console"; + private boolean loaded = true; + private String extension_file; + private String extension_type; + private String name; + + public Extension() {} + + /** + * Build a new Extension POJO from a path. + * @param path representing the location of a given Burp extension + */ + public Extension(String path) { + extension_type = pluginExtToType.get(getFileExtension(path)); + name = path; + extension_file = path; + } + + private static String getFileExtension(String name) { + int lastIndexOf = name.lastIndexOf("."); + if (lastIndexOf == -1) { + return ""; // empty extension + } + return name.substring(lastIndexOf + 1); + } + } + + private List extensions = new ArrayList<>(); + + /** + * Register a new Burp extension + * @param path representing the location of a given Burp extension + */ + public void registerBurpExtension(String path) { + extensions.add(new Extension(path)); + } + + + /** + * Given a userconfig, copy it in a new temporary file and injects all the registered extensions + * @param path a userconfig path to be injected + * @return a new userconfig path + * @throws IOException when one of the two userconfig is not accessible/writable/creatable + */ + public String injectExtensions(String path) throws IOException { + Path userOptionsTempFile = Files.createTempFile("user-options_", ".json"); + FileCopyUtils.copy(new File(path), userOptionsTempFile.toFile()); + + //addBurpExtensions here to the temporary file and return the handle to the new temporary file + //- read all file in in jackson object + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + JsonNode tree = objectMapper.readTree(userOptionsTempFile.toFile()); + //- inject the burp extensions here inside the user configuration + JsonNode user_options = safeGet(objectMapper, tree, "user_options"); + JsonNode extender = safeGet(objectMapper, user_options, "extender"); + JsonNode extension = extender.get("extensions"); + if (!extension.isArray()) { + ArrayNode array = objectMapper.createArrayNode(); + ((ObjectNode)extender).replace("extensions", array); + extension = array; + } + for (Extension e : extensions) { + ((ArrayNode) extension).addPOJO(e); + } + //- write the jackson configuration inside the temporary user configuration + objectMapper.writer(new DefaultPrettyPrinter()).writeValue(userOptionsTempFile.toFile(), tree); + + userOptionsTempFile.toFile().deleteOnExit(); + return userOptionsTempFile.toAbsolutePath().toString(); + } + + private static JsonNode safeGet(ObjectMapper objectMapper, JsonNode root, String path) { + JsonNode newNode = root.get(path); + if (newNode.isMissingNode()) { + ObjectNode addNode = objectMapper.createObjectNode(); + ((ObjectNode)root).replace(path, addNode); + } + return root.get(path); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 1271a52..d51944a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -9,4 +9,4 @@ server: headless: mode: ${java.awt.headless} -build.version: 1.0.4 +build.version: 2.0.0 \ No newline at end of file