├── .gitignore
├── .travis.yml
├── BappDescription.html
├── BappManifest.bmf
├── README.md
├── build.gradle
├── csp-auditor-burp-plugin
├── build.gradle
└── src
│ └── main
│ └── java
│ └── burp
│ ├── BurpExtender.java
│ ├── BurpPolicyBuilder.java
│ ├── CspTab.java
│ ├── scanner
│ ├── BurpCspIssue.java
│ ├── CspHeaderScanner.java
│ └── MockHttpRequestResponse.java
│ └── tab
│ ├── ConfigurationHelperTab.java
│ ├── RequestResponsePanel.java
│ └── StaticMessageEditor.java
├── csp-auditor-core
├── build.gradle
└── src
│ ├── main
│ ├── java
│ │ └── ca
│ │ │ └── gosecure
│ │ │ └── cspauditor
│ │ │ ├── gui
│ │ │ ├── CspHeadersPanel.java
│ │ │ └── generator
│ │ │ │ ├── CspGeneratorPanel.form
│ │ │ │ ├── CspGeneratorPanel.java
│ │ │ │ ├── CspGeneratorPanelController.java
│ │ │ │ ├── CspGeneratorPanelUiProvider.java
│ │ │ │ ├── RequestResponse.java
│ │ │ │ └── SortedUniqueComboBoxModel.java
│ │ │ ├── model
│ │ │ ├── ContentSecurityPolicy.java
│ │ │ ├── CspIssue.java
│ │ │ ├── Directive.java
│ │ │ ├── HeaderValidation.java
│ │ │ ├── WeakCdnHost.java
│ │ │ └── generator
│ │ │ │ └── DetectInlineJavascript.java
│ │ │ └── util
│ │ │ ├── PolicyBuilder.java
│ │ │ └── SimpleListFile.java
│ └── resources
│ │ └── resources
│ │ ├── Media
│ │ ├── scan_issue_decoration_info_certain.png
│ │ ├── scan_issue_high_certain_rpt.png
│ │ ├── scan_issue_low_certain_rpt.png
│ │ └── scan_issue_medium_certain_rpt.png
│ │ ├── data
│ │ ├── csp_host_user_content.txt
│ │ ├── csp_host_vulnerable_js.txt
│ │ └── js_inline_event.txt
│ │ └── descriptions
│ │ ├── issue_deprecated_header_name.htm
│ │ ├── issue_risky_host_known_vulnerable_js.htm
│ │ ├── issue_risky_host_user_content.htm
│ │ ├── issue_script_unsafe_eval.htm
│ │ ├── issue_script_unsafe_inline.htm
│ │ ├── issue_script_wildcard.htm
│ │ ├── issue_style.htm
│ │ └── issue_wildcard_limited.htm
│ └── test
│ └── java
│ └── ca
│ └── gosecure
│ └── cspauditor
│ ├── BaseCspTest.java
│ ├── gui
│ ├── CspGeneratorPanelTest.java
│ └── CspHeadersPanelTest.java
│ ├── model
│ ├── HeaderValidationTest.java
│ └── WeakCdnHostTest.java
│ └── util
│ └── ContentSecurityPolicyBuilderTest.java
├── csp-auditor-zap-plugin
├── build.gradle
├── libs
│ └── zap-2.5.0.jar
└── src
│ └── main
│ ├── java
│ └── org
│ │ └── zaproxy
│ │ └── zap
│ │ └── extension
│ │ └── cspauditor
│ │ ├── CspAuditorExtension.java
│ │ ├── CspAuditorPassiveScanner.java
│ │ ├── ResponseCspView.java
│ │ ├── ResponseCspViewFactory.java
│ │ ├── ResponseCspViewSelector.java
│ │ ├── ResponseImageMetadataViewSelectorFactory.java
│ │ └── ZapPolicyBuilder.java
│ └── resources
│ └── ZapAddOn.xml
├── demo.gif
├── demo2.gif
├── downloads
├── csp-auditor-burp-1.jar
└── cspauditor-alpha-1.zap
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | #Eclipse
2 | .classpath
3 | .project
4 | test-output
5 | .settings
6 |
7 | #IntelliJ
8 | *.iml
9 | *.ipr
10 | *.iws
11 | .idea/
12 |
13 | #Gradle
14 | .gradle
15 | classes/
16 |
17 |
18 | #Build directories
19 | bin/
20 | build/
21 | target/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - openjdk8
4 | - openjdk9
5 | - openjdk10
6 | - openjdk11
--------------------------------------------------------------------------------
/BappDescription.html:
--------------------------------------------------------------------------------
1 |
This extension provides a readable view of CSP headers for responses. It also
2 | includes passive scan rules to detect weak CSP configurations.
3 |
--------------------------------------------------------------------------------
/BappManifest.bmf:
--------------------------------------------------------------------------------
1 | Uuid: 35237408a06043e9945a11016fcbac18
2 | ExtensionType: 1
3 | Name: CSP Auditor
4 | RepoName: csp-auditor
5 | ScreenVersion: 1.0
6 | SerialVersion: 1
7 | MinPlatformVersion: 0
8 | ProOnly: False
9 | Author: Philippe Arteau of GoSecure
10 | ShortDescription: Displays CSP headers for responses, and passively reports CSP weaknesses.
11 | EntryPoint: csp-auditor-burp-plugin/build/libs/csp-auditor-burp-1.jar
12 | BuildCommand: gradle :csp-auditor-burp-plugin:jar
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CSP Auditor [](https://travis-ci.org/GoSecure/csp-auditor)
2 |
3 | This plugin provides:
4 |
5 | * a readable view of CSP Headers in Response Tab
6 | * passive scan rules to detect weak CSP configuration
7 | * a CSP configuration generator based on the Burp crawler or using manual browsing
8 |
9 | This project is packaged as a ZAP and Burp plugin.
10 |
11 | ## Download
12 |
13 | Last updated : August 3th 2017
14 |
15 | - [Burp plugin](https://github.com/GoSecure/csp-auditor/blob/master/downloads/csp-auditor-burp-1.jar?raw=true)
16 | - [ZAP plugin](https://github.com/GoSecure/csp-auditor/blob/master/downloads/cspauditor-alpha-1.zap?raw=true)
17 |
18 | ## Screenshots
19 |
20 | Passive rules and custom tab:
21 |
22 | 
23 |
24 | Configuration builder:
25 |
26 | 
27 |
28 | ## Building the plugin
29 |
30 | Type the following command:
31 |
32 | ```
33 | ./gradlew build
34 | ```
35 |
36 | or if you have already Gradle installed on your machine:
37 |
38 | ```
39 | gradle build
40 | ```
41 |
42 | ## Read more
43 |
44 | For more context around Content-Security-Policy and how to apply it to your website see our blog posts on the topic:
45 |
46 | * http://gosecure.net/2017/07/20/building-a-content-security-policy-configuration-with-csp-auditor
47 | * https://gosecure.net/2016/06/28/auditing-csp-headers-with-burp-and-zap/
48 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | group 'csp-auditor'
2 | version '2.0-SNAPSHOT'
3 |
4 | subprojects { //Common configuration for subprojects
5 | apply plugin: 'java'
6 |
7 | sourceCompatibility = '1.8'
8 | targetCompatibility = '1.8'
9 |
10 | repositories {
11 | mavenCentral()
12 | }
13 | }
14 |
15 |
16 | subprojects {
17 |
18 | compileJava {
19 | options.encoding = 'UTF-8'
20 | //options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
21 | options.compilerArgs << "-Xlint:none"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/csp-auditor-burp-plugin/build.gradle:
--------------------------------------------------------------------------------
1 | group 'csp-auditor'
2 | version '2.0-SNAPSHOT'
3 |
4 | dependencies {
5 | compile group: 'com.esotericsoftware', name: 'minlog', version: '1.3'
6 | compile group: 'net.portswigger.burp.extender', name: 'burp-extender-api', version: '1.7.22'
7 | testCompile group: 'org.testng', name: 'testng', version: '6.8.8'
8 | compile project(':csp-auditor-core')
9 | }
10 |
11 | jar {
12 | archiveName 'csp-auditor-burp-2.jar'
13 | from {
14 | configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/csp-auditor-burp-plugin/src/main/java/burp/BurpExtender.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import burp.scanner.CspHeaderScanner;
4 | import burp.tab.ConfigurationHelperTab;
5 | import com.esotericsoftware.minlog.Log;
6 |
7 | import java.io.PrintWriter;
8 |
9 | public class BurpExtender implements IBurpExtender, IMessageEditorTabFactory {
10 |
11 | private IBurpExtenderCallbacks callbacks;
12 | private IExtensionHelpers helpers;
13 | private CspHeaderScanner scanner;
14 |
15 | public void registerExtenderCallbacks(final IBurpExtenderCallbacks callbacks) {
16 |
17 | this.callbacks = callbacks;
18 | this.helpers = callbacks.getHelpers();
19 | this.callbacks.setExtensionName("CSP Auditor");
20 |
21 | PrintWriter stdout = new PrintWriter(callbacks.getStdout(), true);
22 | stdout.println("== CSP Auditor plugin ==");
23 | stdout.println("This plugin provided a readable view of CSP headers in Response Tab. ");
24 | stdout.println("It also include Passive scan rules to detect weak CSP configuration.");
25 | stdout.println(" - Github : https://github.com/GoSecure/csp-auditor");
26 | stdout.println("");
27 | stdout.println("== License ==");
28 | stdout.println("CSP Auditor plugin is release under LGPL.");
29 | stdout.println("");
30 |
31 | Log.setLogger(new Log.Logger() {
32 | @Override
33 | protected void print(String message) {
34 | callbacks.printOutput(message);
35 | }
36 | });
37 | Log.DEBUG();
38 |
39 | this.callbacks.registerMessageEditorTabFactory(this);
40 |
41 | scanner = new CspHeaderScanner(helpers);
42 | this.callbacks.registerScannerCheck(scanner);
43 | this.callbacks.addSuiteTab(new ConfigurationHelperTab(this.callbacks));
44 | }
45 |
46 |
47 | @Override
48 | public IMessageEditorTab createNewInstance(IMessageEditorController iMessageEditorController, boolean b) {
49 | return new CspTab(this.callbacks, this.helpers, iMessageEditorController);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/csp-auditor-burp-plugin/src/main/java/burp/BurpPolicyBuilder.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import ca.gosecure.cspauditor.model.ContentSecurityPolicy;
4 | import ca.gosecure.cspauditor.util.PolicyBuilder;
5 |
6 | import java.util.HashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | public class BurpPolicyBuilder extends PolicyBuilder {
11 |
12 | public static Map getCspHeader(IResponseInfo response) {
13 | Map headers = new HashMap<>();
14 | for(String header : response.getHeaders()) {
15 | String headerLower = header.toLowerCase();
16 |
17 | for(String cspHeader : CSP_HEADERS) {
18 | if (headerLower.startsWith(cspHeader)) {
19 | String[] parts = header.split(":",2);
20 | if(parts.length>1) {
21 | headers.put(cspHeader, parts[1]);
22 | }
23 | }
24 | }
25 | }
26 | return headers;
27 | }
28 |
29 | public static List buildFromResponse(IResponseInfo responseInfo) {
30 | Map headers = getCspHeader(responseInfo);
31 | return parseCspHeaders(headers);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/csp-auditor-burp-plugin/src/main/java/burp/CspTab.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import ca.gosecure.cspauditor.gui.CspHeadersPanel;
4 | import ca.gosecure.cspauditor.model.ContentSecurityPolicy;
5 | import com.esotericsoftware.minlog.Log;
6 |
7 | import java.awt.*;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | public class CspTab implements IMessageEditorTab {
12 |
13 |
14 | private byte[] message;
15 |
16 | private CspHeadersPanel cspHeaders;
17 |
18 | private IExtensionHelpers helpers;
19 | private IBurpExtenderCallbacks callbacks;
20 | private IMessageEditorController controller;
21 |
22 | CspTab(IBurpExtenderCallbacks callbacks, IExtensionHelpers helpers, IMessageEditorController controller) {
23 | this.helpers = helpers;
24 | this.callbacks = callbacks;
25 | this.controller = controller;
26 |
27 | this.cspHeaders = new CspHeadersPanel();
28 | }
29 |
30 | @Override
31 | public String getTabCaption() {
32 | return "CSP";
33 | }
34 |
35 | @Override
36 | public Component getUiComponent() {
37 | return cspHeaders.getComponent();
38 | }
39 |
40 | @Override
41 | public boolean isEnabled(byte[] respBytes, boolean isRequest) {
42 | if (isRequest) {
43 | return false;
44 | } else { //The tab will appears if it has at least one CSP header
45 | IResponseInfo responseInfo = helpers.analyzeResponse(respBytes);
46 |
47 | Map cspHeaders = BurpPolicyBuilder.getCspHeader(responseInfo);
48 | return cspHeaders.size() > 0;
49 | }
50 | }
51 |
52 | @Override
53 | public void setMessage(byte[] respBytes, boolean isRequest) {
54 | this.message = respBytes;
55 |
56 | try {
57 | IResponseInfo responseInfo = helpers.analyzeResponse(respBytes);
58 | List p = BurpPolicyBuilder.buildFromResponse(responseInfo);
59 | cspHeaders.displayPolicy(p);
60 | } catch (Exception e) {
61 | Log.error(e.getMessage());
62 | }
63 | }
64 |
65 | @Override
66 | public byte[] getMessage() {
67 | return message;
68 | }
69 |
70 | @Override
71 | public boolean isModified() {
72 | return false;
73 | }
74 |
75 | @Override
76 | public byte[] getSelectedData() {
77 | return message;
78 | }
79 |
80 |
81 |
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/csp-auditor-burp-plugin/src/main/java/burp/scanner/BurpCspIssue.java:
--------------------------------------------------------------------------------
1 | package burp.scanner;
2 |
3 | import burp.IHttpRequestResponse;
4 | import burp.IHttpService;
5 | import burp.IScanIssue;
6 |
7 | import java.net.URL;
8 |
9 | public class BurpCspIssue implements IScanIssue {
10 |
11 | private IHttpService httpService;
12 | private URL url;
13 | private IHttpRequestResponse httpMessage;
14 | private String name;
15 | private String detail;
16 | private String severity;
17 | private String confidence;
18 |
19 |
20 | public BurpCspIssue(IHttpService httpService, URL url, IHttpRequestResponse httpMessage, String name, //
21 | String detail, String severity,String confidence) {
22 | this.url = url;
23 | this.name = name;
24 | this.detail = detail;
25 | this.severity = severity;
26 | this.httpService = httpService;
27 | this.httpMessage = httpMessage;
28 | this.confidence = confidence;
29 | }
30 |
31 | @Override
32 | public URL getUrl() {
33 | return url;
34 | }
35 |
36 | @Override
37 | public String getIssueName() {
38 | return name;
39 | }
40 |
41 | @Override
42 | public int getIssueType() {
43 | return 0;
44 | }
45 |
46 | @Override
47 | public String getSeverity() {
48 | return severity;
49 | }
50 |
51 | @Override
52 | public String getConfidence() {
53 | return confidence;
54 | }
55 |
56 | @Override
57 | public String getIssueBackground() {
58 | return null;
59 | }
60 |
61 | @Override
62 | public String getRemediationBackground() {
63 | return null;
64 | }
65 |
66 | @Override
67 | public String getIssueDetail() {
68 | return detail;
69 | }
70 |
71 | @Override
72 | public String getRemediationDetail() {
73 | return null;
74 | }
75 |
76 | @Override
77 | public IHttpRequestResponse[] getHttpMessages() {
78 | return new IHttpRequestResponse[] {httpMessage};
79 | }
80 |
81 | @Override
82 | public IHttpService getHttpService() {
83 | return httpService;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/csp-auditor-burp-plugin/src/main/java/burp/scanner/CspHeaderScanner.java:
--------------------------------------------------------------------------------
1 | package burp.scanner;
2 |
3 | import burp.BurpPolicyBuilder;
4 | import burp.IExtensionHelpers;
5 | import burp.IHttpRequestResponse;
6 | import burp.IRequestInfo;
7 | import burp.IResponseInfo;
8 | import burp.IScanIssue;
9 | import burp.IScannerCheck;
10 | import burp.IScannerInsertionPoint;
11 | import ca.gosecure.cspauditor.model.ContentSecurityPolicy;
12 | import ca.gosecure.cspauditor.model.CspIssue;
13 | import ca.gosecure.cspauditor.model.HeaderValidation;
14 |
15 | import java.util.ArrayList;
16 | import java.util.HashSet;
17 | import java.util.List;
18 | import java.util.Set;
19 |
20 | public class CspHeaderScanner implements IScannerCheck {
21 |
22 | private IExtensionHelpers helpers;
23 |
24 | public CspHeaderScanner(IExtensionHelpers helpers) {
25 | this.helpers = helpers;
26 | }
27 |
28 | @Override
29 | public List doPassiveScan(IHttpRequestResponse baseRequestResponse) {
30 | // IRequestInfo requestInfo = helpers.analyzeRequest(baseRequestResponse.getRequest());
31 | IResponseInfo responseInfo = helpers.analyzeResponse(baseRequestResponse.getResponse());
32 |
33 | List csp = BurpPolicyBuilder.buildFromResponse(responseInfo);
34 |
35 | List cspIssues = HeaderValidation.validateCspConfig(csp);
36 |
37 | if(cspIssues.size() == 0)
38 | return new ArrayList();
39 |
40 | return convertIssues(cspIssues,baseRequestResponse);
41 | }
42 |
43 | @Override
44 | public List doActiveScan(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint) {
45 | return null; //No active scanning done
46 | }
47 |
48 | @Override
49 | public int consolidateDuplicateIssues(IScanIssue existingIssue, IScanIssue newIssue) {
50 | if(existingIssue.getUrl().equals(newIssue.getUrl())) {
51 | return -1; //Keep the old issue
52 | }
53 | return 0; //Accept both
54 | }
55 |
56 | private List convertIssues(List issues,IHttpRequestResponse baseRequestResponse) {
57 |
58 | IRequestInfo reqInfo = helpers.analyzeRequest(baseRequestResponse.getHttpService(), baseRequestResponse.getRequest());
59 |
60 | List burpIssues = new ArrayList<>();
61 | Set types = new HashSet<>(); //Avoid issuing multiples alert for the same type.
62 | for(CspIssue i : issues) {
63 | if(!types.contains(i.getMessage())) {
64 | types.add(i.getMessage());
65 |
66 | String name = "CSP: "+i.getTitle();
67 | String detail = i.getLocalizedMessage();
68 | String severity;
69 | //See IScanIssue.getSeverity() doc for more info
70 | if(i.getSeverity() == CspIssue.HIGH) {
71 | severity = "High";
72 | }
73 | else if(i.getSeverity() == CspIssue.MED) {
74 | severity = "Medium";
75 | }
76 | else if(i.getSeverity() == CspIssue.LOW) {
77 | severity = "Low";
78 | }
79 | else {
80 | continue;
81 | }
82 | String confidence = "Firm";
83 |
84 |
85 | burpIssues.add(new BurpCspIssue(
86 | baseRequestResponse.getHttpService(),
87 | reqInfo.getUrl(),new MockHttpRequestResponse(baseRequestResponse,i.getHighlightedValue()),
88 | name,detail,severity,confidence));
89 | }
90 | }
91 | return burpIssues;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/csp-auditor-burp-plugin/src/main/java/burp/scanner/MockHttpRequestResponse.java:
--------------------------------------------------------------------------------
1 | package burp.scanner;
2 |
3 | import burp.IHttpRequestResponse;
4 | import burp.IHttpRequestResponseWithMarkers;
5 | import burp.IHttpService;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | public class MockHttpRequestResponse implements IHttpRequestResponseWithMarkers {
11 |
12 | IHttpRequestResponse actual;
13 | List responseMarkers = new ArrayList<>();
14 |
15 | MockHttpRequestResponse(IHttpRequestResponse actual, String... highlightedValues) {
16 | byte[] responseBytes = actual.getResponse();
17 | for(String value : highlightedValues) {
18 | int startIndex = indexOf(responseBytes,value.getBytes());
19 | if(startIndex != -1) {
20 | int endIndex = value.length();
21 | responseMarkers.add(new int[] {startIndex,startIndex+endIndex});
22 | }
23 | }
24 | this.actual = actual;
25 | }
26 |
27 | @Override
28 | public byte[] getRequest() {
29 | return actual.getRequest();
30 | }
31 |
32 | @Override
33 | public void setRequest(byte[] message) {
34 | actual.setRequest(message);
35 | }
36 |
37 | @Override
38 | public byte[] getResponse() {
39 | return actual.getResponse();
40 | }
41 |
42 | @Override
43 | public void setResponse(byte[] message) {
44 | actual.setResponse(message);
45 | }
46 |
47 | @Override
48 | public String getComment() {
49 | return actual.getComment();
50 | }
51 |
52 | @Override
53 | public void setComment(String comment) {
54 | actual.setComment(comment);
55 | }
56 |
57 | @Override
58 | public String getHighlight() {
59 | return actual.getHighlight();
60 | }
61 |
62 | @Override
63 | public void setHighlight(String color) {
64 | actual.setHighlight(color);
65 | }
66 |
67 | @Override
68 | public IHttpService getHttpService() {
69 | return actual.getHttpService();
70 | }
71 |
72 | @Override
73 | public void setHttpService(IHttpService httpService) {
74 | actual.setHttpService(httpService);
75 | }
76 |
77 | @Override
78 | public List getRequestMarkers() {
79 | return null;
80 | }
81 |
82 | @Override
83 | public List getResponseMarkers() {
84 | return responseMarkers;
85 | }
86 |
87 |
88 | public int indexOf(byte[] outerArray, byte[] smallerArray) {
89 | for(int i = 0; i < outerArray.length - smallerArray.length+1; ++i) {
90 | boolean found = true;
91 | for(int j = 0; j < smallerArray.length; ++j) {
92 | if (outerArray[i+j] != smallerArray[j]) {
93 | found = false;
94 | break;
95 | }
96 | }
97 | if (found) return i;
98 | }
99 | return -1;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/csp-auditor-burp-plugin/src/main/java/burp/tab/ConfigurationHelperTab.java:
--------------------------------------------------------------------------------
1 | package burp.tab;
2 |
3 | import burp.IBurpExtenderCallbacks;
4 | import burp.IExtensionHelpers;
5 | import burp.IHttpRequestResponse;
6 | import burp.IHttpService;
7 | import burp.IMessageEditor;
8 | import burp.IMessageEditorController;
9 | import burp.IRequestInfo;
10 | import burp.IResponseInfo;
11 | import burp.ITab;
12 | import ca.gosecure.cspauditor.gui.generator.CspGeneratorPanel;
13 | import ca.gosecure.cspauditor.gui.generator.CspGeneratorPanelController;
14 |
15 | import java.awt.*;
16 | import java.net.MalformedURLException;
17 | import java.net.URL;
18 | import java.util.ArrayList;
19 | import java.util.Arrays;
20 | import java.util.List;
21 | import java.util.SortedSet;
22 | import java.util.TreeSet;
23 |
24 | import ca.gosecure.cspauditor.model.ContentSecurityPolicy;
25 | import ca.gosecure.cspauditor.model.generator.DetectInlineJavascript;
26 | import com.esotericsoftware.minlog.Log;
27 | import org.json.JSONException;
28 | import org.json.JSONObject;
29 |
30 | import javax.swing.*;
31 |
32 | /**
33 | * Tab that contains three parts :
34 | * - Configuration
35 | * - External Resources
36 | * - Inline Scripts
37 | */
38 | public class ConfigurationHelperTab implements ITab, CspGeneratorPanelController {
39 |
40 | private CspGeneratorPanel panel;
41 |
42 | private IBurpExtenderCallbacks callbacks;
43 | private IExtensionHelpers helpers;
44 |
45 | private RequestResponsePanel resourceReqRespTab;
46 | private RequestResponsePanel inlineReqRespTab;
47 | private RequestResponsePanel reportReqRespTab;
48 |
49 | public ConfigurationHelperTab(final IBurpExtenderCallbacks callbacks) {
50 |
51 | this.callbacks = callbacks;
52 | this.helpers = callbacks.getHelpers();
53 |
54 | panel = new CspGeneratorPanel(this);
55 | panel.init();
56 | resourceReqRespTab = new RequestResponsePanel(callbacks);
57 | panel.setResourceItem(resourceReqRespTab);
58 | inlineReqRespTab = new RequestResponsePanel(callbacks);
59 | panel.setInlineItem(inlineReqRespTab);
60 | reportReqRespTab = new RequestResponsePanel(callbacks);
61 | panel.setReportItem(reportReqRespTab);
62 | }
63 |
64 | @Override
65 | public String getTabCaption() {
66 | return "CSP";
67 | }
68 |
69 | @Override
70 | public Component getUiComponent() {
71 | return panel.getRootPanel();
72 | }
73 |
74 | @Override
75 | public void analyzeDomain(String domain) {
76 | Log.debug("Analyzing domain " + domain);
77 | AnalyzeDomainTask task = new AnalyzeDomainTask(domain);
78 | task.execute();
79 | }
80 |
81 | protected class AnalyzeDomainTask extends SwingWorker {
82 |
83 | String domain;
84 |
85 | public AnalyzeDomainTask(String domain) {
86 | this.domain = domain;
87 | }
88 |
89 | @Override
90 | public Void doInBackground() {
91 | IHttpRequestResponse[] reqResponses = callbacks.getProxyHistory();
92 |
93 | ContentSecurityPolicy csp = new ContentSecurityPolicy("CSP");
94 | csp.addDirectiveValue("default-src","'self'");
95 | try {
96 | URL domainSelected = new URL(domain);
97 | Log.debug("Analysing domain " + domain);
98 | panel.clearResources();
99 | panel.clearInlineScript();
100 | panel.clearReports();
101 | int id = 0;
102 | for (IHttpRequestResponse reqResp : reqResponses) {
103 | id++;
104 | //Log.debug("Request "+id);
105 | IRequestInfo reqInfo = helpers.analyzeRequest(reqResp.getHttpService(), reqResp.getRequest());
106 | if (reqResp.getResponse() == null) continue;
107 | IResponseInfo respInfo = helpers.analyzeResponse(reqResp.getResponse());
108 |
109 | String mimeType = respInfo.getStatedMimeType() != "" ? respInfo.getStatedMimeType().toUpperCase() : respInfo.getInferredMimeType().toUpperCase(); //Uppercase is applied because to make the content-type uniform
110 | URL urlRequested = reqInfo.getUrl();
111 | String urlString = getUrl(reqInfo);
112 | String protoAndHost = urlRequested.getProtocol() + "://" + urlRequested.getHost();
113 |
114 | if("".equals(mimeType) || "TEXT".equals(mimeType)) {
115 | String pathSegment = urlRequested.getPath();
116 | if(pathSegment.contains(".")) {
117 | //No content-type is returned when the server return a Not Modified response
118 | String extension = pathSegment.substring(pathSegment.lastIndexOf(".")).replace(".","").toUpperCase();
119 | mimeType = extension;
120 | }
121 | }
122 | if("TXT".equals(mimeType)) {
123 | mimeType = "TEXT";
124 | }
125 | if("JS".equals(mimeType)) {
126 | mimeType = "SCRIPT";
127 | }
128 | if("JPG".equals(mimeType)) {
129 | mimeType = "JPEG";
130 | }
131 |
132 |
133 | boolean isRequestToDomain = protoAndHost.equals(domain);
134 |
135 | //Finding inline script
136 |
137 | String host = getHeader("host", reqInfo.getHeaders());
138 | if (isRequestToDomain) {//Same-Origin
139 | if (mimeType.equals("HTML")) {
140 | List problemInline = DetectInlineJavascript.getInstance().findInlineJs(new String(reqResp.getResponse()));
141 |
142 | for (String line : problemInline)
143 | panel.addInlineScript(String.valueOf(id), urlString, line);
144 | }
145 | }
146 |
147 |
148 | //Finding external resources
149 |
150 | if (!isRequestToDomain) {//Different-Origin
151 |
152 | String referrer = getHeader("referer", reqInfo.getHeaders());
153 | if (referrer.startsWith("http://") || referrer.startsWith("https://")) { //Just to make sure the URL will be parsable
154 | URL referrerUrl = new URL(referrer);
155 | if (domainSelected.getHost().equals(referrerUrl.getHost())) {
156 | panel.addResource(String.valueOf(id), urlString, mimeType);
157 | mimeTypeToDirective(mimeType, protoAndHost, csp);
158 |
159 | }
160 | }
161 | }
162 |
163 | byte[] completeRequest = reqResp.getRequest();
164 | int startOffset = reqInfo.getBodyOffset();
165 | if(completeRequest.length - startOffset != 0) { //Skip GET request
166 | byte[] part = Arrays.copyOfRange(completeRequest, startOffset, completeRequest.length);
167 | String body = new String(part);
168 | if (body.contains("{\"csp-report\":{")) {
169 | try {
170 | JSONObject rootJson = new JSONObject(body);
171 | String documentUri = rootJson.getJSONObject("csp-report").getString("document-uri");
172 | String originalPolicy = rootJson.getJSONObject("csp-report").getString("original-policy");
173 | // chrome sends just the directive. firefox sends directive + sources. e.g. script-src https://domain.com ...
174 | String violatedDirective = rootJson.getJSONObject("csp-report").getString("violated-directive").split(" ")[0];
175 | String blockedUri;
176 |
177 | if (violatedDirective.equalsIgnoreCase("frame-ancestors")) {
178 | // the report's blocked-uri is the page that got framed, not the one that needs to be added to the policy.
179 | blockedUri = rootJson.getJSONObject("csp-report").getString("referrer").split(" ")[0];
180 | if (blockedUri.isEmpty())
181 | continue; // browsers won't always send referrer, in which case we can't use the report.
182 | }
183 | else{
184 | blockedUri = rootJson.getJSONObject("csp-report").getString("blocked-uri");
185 | }
186 |
187 | String newSrc = blockedUri;
188 | try {
189 | URL url = new URL(blockedUri);
190 | String port = url.getPort() == -1 ? "" : ":" + Integer.toString(url.getPort());
191 | newSrc = url.getProtocol() + "://" + url.getHost() + port;
192 | }
193 | catch (MalformedURLException e) {
194 | if (blockedUri.equalsIgnoreCase("inline")
195 | || blockedUri .equalsIgnoreCase("eval")){
196 | newSrc = "'unsafe-" + blockedUri + "'";
197 | }
198 | else if (blockedUri.equalsIgnoreCase("data") || blockedUri.equalsIgnoreCase("blob")){
199 | newSrc = blockedUri + ":";
200 | }
201 | else {
202 | Log.error("Invalid blocked uri", blockedUri);
203 | }
204 | }
205 |
206 | if (newSrc.equalsIgnoreCase(domain)) {
207 | newSrc = "'self'";
208 | }
209 |
210 | csp.addDirectiveValue(violatedDirective, newSrc);
211 | panel.addReport(String.valueOf(id), blockedUri, documentUri, originalPolicy, violatedDirective);
212 | }
213 | catch (JSONException e){ //Invalid csp-report
214 | Log.error("Invalid CSP report at "+urlString);
215 | }
216 | }
217 | }
218 |
219 | }
220 | } catch (Exception e) {
221 | Log.error(e.getMessage(),e);
222 | }
223 |
224 | csp.addDirectiveValue("report-uri", "/change-this-uri/");
225 | displayConfiguration(csp);
226 | Log.debug("Done analyzing "+domain);
227 |
228 | return null;
229 | }
230 |
231 | }
232 |
233 | private void mimeTypeToDirective(String mimeType,String host,ContentSecurityPolicy csp) {
234 |
235 | String directive = null;
236 | switch (mimeType) {
237 | case "CSS":
238 | directive = "style-src";
239 | break;
240 | case "PNG":
241 | case "JPG":
242 | case "JPEG":
243 | case "GIF":
244 | case "SVG":
245 | case "WEBP":
246 | directive = "img-src";
247 | break;
248 | case "SCRIPT":
249 | directive = "script-src";
250 | break;
251 | case "FONT":
252 | directive = "font-src";
253 | break;
254 | case "WAV":
255 | case "MP3":
256 | case "MPG":
257 | case "MPEG":
258 | case "AVI":
259 | directive = "media-src";
260 | break;
261 | default:
262 | //Log.debug("Unknown MimeType "+mimeType);
263 | }
264 |
265 | if(directive != null) {
266 | csp.addDirectiveValue(directive, host);
267 | if(host.equals("https://fonts.googleapis.com")) {
268 | csp.addDirectiveValue("style-src", "https://fonts.gstatic.com");
269 | csp.addDirectiveValue("font-src", "https://fonts.gstatic.com");
270 | }
271 | }
272 |
273 | }
274 |
275 | @Override
276 | public void refreshDomains() {
277 | Log.debug("Refreshing the domain list");
278 | DomainRefreshTask task = new DomainRefreshTask();
279 | task.execute();
280 | }
281 |
282 | protected class DomainRefreshTask extends SwingWorker, String> {
283 |
284 | SortedSet hosts = new TreeSet<>();
285 |
286 | @Override
287 | public SortedSet doInBackground() {
288 | IHttpRequestResponse[] reqResponses = callbacks.getProxyHistory();
289 | hosts = new TreeSet<>();
290 | for (IHttpRequestResponse reqResp : reqResponses) {
291 | if ( !isCancelled() ) {
292 | IRequestInfo reqInfo = helpers.analyzeRequest(reqResp.getHttpService(), reqResp.getRequest());
293 | String domain = reqInfo.getUrl().getProtocol() + "://" + reqInfo.getUrl().getHost();
294 | hosts.add(domain);
295 | publish(domain);
296 | }
297 | }
298 |
299 | return hosts;
300 | }
301 |
302 | @Override
303 | public void process(List domains) {
304 | for (String domain : domains) {
305 | panel.addDomain(domain);
306 | }
307 | }
308 |
309 | }
310 |
311 | @Override
312 | public void selectResource(String url) {
313 | displayReqResp(url, resourceReqRespTab);
314 | }
315 |
316 | @Override
317 | public void selectInline(String id) {
318 | inlineReqRespTab.selectResponse();
319 | displayReqResp(id, inlineReqRespTab);
320 | }
321 |
322 | @Override
323 | public void selectReport(String id) {
324 | displayReqResp(id, reportReqRespTab);
325 | }
326 |
327 | private void displayReqResp(String id, RequestResponsePanel tabbedPane) {
328 | //IHttpRequestResponse[] reqResponses = callbacks.getSiteMap(url);
329 | try {
330 | Integer requestId = Integer.parseInt(id);
331 |
332 | IHttpRequestResponse reqResp = callbacks.getProxyHistory()[requestId-1];
333 | if (reqResp != null) {
334 |
335 | tabbedPane.editorRequest.setMessage(reqResp.getRequest(), true);
336 | if (reqResp.getResponse() != null) tabbedPane.editorResponse.setMessage(reqResp.getResponse(), false);
337 |
338 | } else {
339 | Log.error("Oups request not found.");
340 | }
341 | }
342 | catch (NumberFormatException | IndexOutOfBoundsException e) {
343 |
344 | }
345 |
346 | }
347 |
348 | private void displayConfiguration(ContentSecurityPolicy policy) {
349 |
350 | StringBuilder str = new StringBuilder();
351 | str.append("HTTP/1.1 200 OK\r\n"); //Only to have a valid response so that the "new" syntax highlight is effective.
352 | str.append("Content-Security-Policy: ");
353 | str.append(policy.toHeaderString());
354 | str.append("\r\n\r\n");
355 |
356 | IMessageEditor msg = callbacks.createMessageEditor(null, true);
357 |
358 | msg.setMessage(str.toString().getBytes(), false);
359 |
360 | panel.setConfiguration(msg.getComponent());
361 | }
362 |
363 | private String getUrl(IRequestInfo reqInfo) {
364 | String url = reqInfo.getUrl().toString();
365 |
366 | try {//BUG: BurpSuite does not support default port being specified to getSiteMap() API
367 | URL urlTest = new URL(url); // Test if for port to remove from url
368 | if (urlTest.getDefaultPort() == urlTest.getPort()) {
369 | url = urlTest.getProtocol() + "://" + urlTest.getHost() + urlTest.getPath();
370 | if (urlTest.getQuery() != null) {
371 | url += "?" + urlTest.getQuery();
372 | }
373 | }
374 | } catch (MalformedURLException e) {
375 | }
376 |
377 | return url;
378 | }
379 |
380 | private String getHeader(String name, List headers) {
381 | for (String h : headers) {
382 | String[] parts = h.split(":", 2);
383 | String headerName = parts[0].trim();
384 | if (headerName.equalsIgnoreCase(name) && parts.length > 1) {
385 | return parts[1].trim();
386 | }
387 | }
388 | return "";
389 | }
390 | }
391 |
--------------------------------------------------------------------------------
/csp-auditor-burp-plugin/src/main/java/burp/tab/RequestResponsePanel.java:
--------------------------------------------------------------------------------
1 | package burp.tab;
2 |
3 | import burp.IBurpExtenderCallbacks;
4 | import burp.IMessageEditor;
5 |
6 | import javax.swing.*;
7 |
8 | public class RequestResponsePanel extends JTabbedPane {
9 |
10 | public IMessageEditor editorRequest;
11 | public IMessageEditor editorResponse;
12 |
13 | public RequestResponsePanel(IBurpExtenderCallbacks callbacks) {
14 |
15 | this.editorRequest = callbacks.createMessageEditor(null, false);
16 | this.editorResponse = callbacks.createMessageEditor(null, false);
17 |
18 | addTab("Request", editorRequest.getComponent());
19 | addTab("Response", editorResponse.getComponent());
20 | }
21 |
22 | public void selectResponse() {
23 | setSelectedIndex(1);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/csp-auditor-burp-plugin/src/main/java/burp/tab/StaticMessageEditor.java:
--------------------------------------------------------------------------------
1 | package burp.tab;
2 |
3 | import burp.IHttpService;
4 | import burp.IMessageEditor;
5 | import burp.IMessageEditorController;
6 |
7 | import java.awt.*;
8 |
9 | public class StaticMessageEditor implements IMessageEditorController {
10 | private IHttpService iHttpService;
11 | private byte[] request;
12 | private byte[] response;
13 |
14 | public StaticMessageEditor(IHttpService iHttpService, byte[] request, byte[] response) {
15 | this.iHttpService = iHttpService;
16 | this.request = request;
17 | this.response = response;
18 | }
19 |
20 | @Override
21 | public IHttpService getHttpService() {
22 | return iHttpService;
23 | }
24 |
25 | @Override
26 | public byte[] getRequest() {
27 | return request;
28 | }
29 |
30 | @Override
31 | public byte[] getResponse() {
32 | return response;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/csp-auditor-core/build.gradle:
--------------------------------------------------------------------------------
1 | group 'csp-auditor'
2 | version '1.0-SNAPSHOT'
3 |
4 |
5 |
6 | dependencies {
7 | compile group: 'com.esotericsoftware', name: 'minlog', version: '1.3'
8 | compile group: 'commons-io', name: 'commons-io', version: '2.4'
9 | compile group: 'org.json', name: 'json', version: '20140107'
10 | testCompile group: 'org.testng', name: 'testng', version: '6.8.8'
11 |
12 | //files('$ROOT_DIR$/lib/forms_rt-12.1.8.jar')
13 | //files('$ROOT_DIR$/lib/javac2-12.1.8.jar')
14 | }
15 |
--------------------------------------------------------------------------------
/csp-auditor-core/src/main/java/ca/gosecure/cspauditor/gui/CspHeadersPanel.java:
--------------------------------------------------------------------------------
1 | package ca.gosecure.cspauditor.gui;
2 |
3 | import ca.gosecure.cspauditor.model.ContentSecurityPolicy;
4 | import ca.gosecure.cspauditor.model.Directive;
5 | import ca.gosecure.cspauditor.model.HeaderValidation;
6 | import com.esotericsoftware.minlog.Log;
7 | import org.apache.commons.io.IOUtils;
8 |
9 | import javax.swing.*;
10 | import java.awt.*;
11 | import java.io.File;
12 | import java.io.FileOutputStream;
13 | import java.io.IOException;
14 | import java.io.InputStream;
15 | import java.net.URL;
16 |
17 | public class CspHeadersPanel {
18 |
19 |
20 | private JPanel mainPanel = new JPanel();
21 |
22 | public CspHeadersPanel() {
23 | mainPanel.setLayout(new BorderLayout());
24 | }
25 |
26 | public JComponent getComponent() {
27 | return mainPanel;
28 | }
29 |
30 | private static final URL ICON_HIGH = getAccessibleResource("/resources/Media/scan_issue_high_certain_rpt.png");
31 | private static final URL ICON_MED = getAccessibleResource("/resources/Media/scan_issue_medium_certain_rpt.png");
32 | private static final URL ICON_LOW = getAccessibleResource("/resources/Media/scan_issue_decoration_info_certain.png");
33 |
34 | public void displayPolicy(java.util.List p) {
35 |
36 | StringBuilder str = new StringBuilder();
37 |
38 | str.append("");
39 | for(ContentSecurityPolicy policyOrig : p) {
40 | ContentSecurityPolicy policy = policyOrig.getComputedPolicy();
41 | str.append("Header : " + policy.getHeaderName() + "
\n");
42 |
43 | for (Directive d : policy.getDirectives().values()) {
44 | str.append("
" + d.getName() + " " + (d.isImplicit() ? "(Implicit taken from the default-src)" : "") + "
\n");
45 |
46 | for (String value : d.getValues()) {
47 | if (HeaderValidation.isAllowingAnyScript(d.getName(),value) ||
48 | HeaderValidation.isAllowingInlineScript(d.getName(),value) ||
49 | HeaderValidation.isAllowingUnsafeEvalScript(d.getName(),value) ||
50 | HeaderValidation.isUserContentHost(d.getName(),value) ||
51 | HeaderValidation.isHostingVulnerableJs(d.getName(),value)) {
52 | str.append(iconify(value,ICON_HIGH,"red"));
53 |
54 | } else if (HeaderValidation.isAllowingAnyStyle(d.getName(),value) ||
55 | HeaderValidation.isAllowingAny(d.getName(),value)) {
56 | str.append(iconify(value,ICON_MED,"orange"));
57 |
58 | } else {
59 | str.append(iconify(value,ICON_LOW,""));
60 | }
61 | }
62 | }
63 | }
64 | str.append("
");
65 |
66 | JLabel lbl = new JLabel();
67 | lbl.setText(str.toString());
68 |
69 |
70 | mainPanel.removeAll();
71 |
72 | mainPanel.add(new JScrollPane(lbl));
73 | }
74 |
75 | private static String iconify(String message,URL icon,String color) {
76 | StringBuilder buffer = new StringBuilder(" ");
77 |
78 | buffer.append("");
79 | if(icon != null) {
80 | buffer.append("
");
81 | }
82 | else {
83 | buffer.append(" - ");
84 | }
85 |
86 | buffer.append(" ");
87 | buffer.append(message);
88 |
89 | // if(icon != null) {
90 | buffer.append("
\n");
91 | // }
92 | // else {
93 | // buffer.append("");
94 | // }
95 | buffer.append("");
96 | return buffer.toString();
97 | }
98 |
99 | private static URL getAccessibleResource(String url) {
100 |
101 | URL urlResource = CspHeadersPanel.class.getResource(url);
102 | if(urlResource == null) {
103 | Log.error("Resource not found "+url);
104 | return null;
105 | }
106 |
107 | if(!urlResource.getFile().contains(".jar!") && !urlResource.getFile().contains(".zap!"))
108 | {
109 | return urlResource;
110 | }
111 |
112 | try {
113 | File tempFile = File.createTempFile("temp-file-name", ".png");
114 |
115 | System.out.println(tempFile.toPath());
116 |
117 | try (InputStream in = urlResource.openStream(); FileOutputStream out = new FileOutputStream(tempFile)) {
118 | IOUtils.copy(in, out);
119 | }
120 | return tempFile.toURI().toURL();
121 | }
122 | catch (IOException e) {
123 | Log.warn(e.getMessage());
124 | return null;
125 | }
126 |
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/csp-auditor-core/src/main/java/ca/gosecure/cspauditor/gui/generator/CspGeneratorPanel.form:
--------------------------------------------------------------------------------
1 |
2 |
216 |
--------------------------------------------------------------------------------
/csp-auditor-core/src/main/java/ca/gosecure/cspauditor/gui/generator/CspGeneratorPanel.java:
--------------------------------------------------------------------------------
1 | package ca.gosecure.cspauditor.gui.generator;
2 |
3 | import com.esotericsoftware.minlog.Log;
4 | import main.java.ca.gosecure.cspauditor.gui.generator.SortedUniqueComboBoxModel;
5 |
6 | import javax.swing.*;
7 | import javax.swing.event.HyperlinkEvent;
8 | import javax.swing.event.ListSelectionEvent;
9 | import javax.swing.table.DefaultTableModel;
10 | import java.awt.*;
11 | import java.awt.event.ActionEvent;
12 | import java.awt.event.ActionListener;
13 | import java.util.Collection;
14 | import java.util.Vector;
15 |
16 | public class CspGeneratorPanel {
17 | private JButton analyseButton;
18 | private JComboBox comboBox1;
19 | private JPanel rootPanel;
20 | private JButton refreshButton;
21 | private JTabbedPane resultTabbedPane;
22 | private JPanel configurationPanel;
23 | private JPanel inlineScriptPanel;
24 | private JTable resourcesTable;
25 | private JPanel resourcePanel;
26 | private JTable inlinesTable;
27 | private JPanel inlinePanel;
28 | private JPanel configTabPanel;
29 | private JTable reportsTable;
30 | private JPanel reportPanel;
31 | private JTextPane warningConfigurationTextPane;
32 |
33 | DefaultTableModel tableResourcesModel = new DefaultTableModel() {
34 | @Override
35 | public boolean isCellEditable(int row, int column) {
36 | return false;
37 | }
38 | };
39 | DefaultTableModel tableInlinesModel = new DefaultTableModel() {
40 | @Override
41 | public boolean isCellEditable(int row, int column) {
42 | return false;
43 | }
44 | };
45 | DefaultTableModel reportsModel = new DefaultTableModel() {
46 | @Override
47 | public boolean isCellEditable(int row, int column) {
48 | return false;
49 | }
50 | };
51 |
52 |
53 | private CspGeneratorPanelController controller;
54 | //private CspGeneratorPanelUiProvider uiProvider;
55 |
56 | public CspGeneratorPanel(CspGeneratorPanelController controller) {
57 | this.controller = controller;
58 | //this.uiProvider = uiProvider;
59 |
60 | }
61 |
62 | public void init() {
63 |
64 |
65 | //Resources table
66 | tableResourcesModel.addColumn("id");
67 | tableResourcesModel.addColumn("Request");
68 | tableResourcesModel.addColumn("Type");
69 | resourcesTable.setModel(tableResourcesModel);
70 | resourcesTable.getColumnModel().getColumn(0).setMaxWidth(45);
71 |
72 | resourcesTable.getSelectionModel().addListSelectionListener((ListSelectionEvent event) -> {
73 | int viewRow = resourcesTable.getSelectedRow();
74 | if(viewRow == -1) return;
75 |
76 | Vector values = (Vector) tableResourcesModel.getDataVector().get(viewRow);
77 | selectResourceItem((String) values.get(0));
78 | });
79 |
80 | //Inlines table
81 | tableInlinesModel.addColumn("id");
82 | tableInlinesModel.addColumn("Request");
83 | tableInlinesModel.addColumn("Code");
84 | inlinesTable.setModel(tableInlinesModel);
85 | inlinesTable.getColumnModel().getColumn(0).setMaxWidth(45);
86 |
87 | inlinesTable.getSelectionModel().addListSelectionListener((ListSelectionEvent event) -> {
88 | int viewRow = inlinesTable.getSelectedRow();
89 | if(viewRow == -1) return;
90 | Vector values = (Vector) tableInlinesModel.getDataVector().get(viewRow);
91 | selectInlineItem((String) values.get(0));
92 | });
93 |
94 |
95 | //Report table
96 | reportsModel.addColumn("id");
97 | reportsModel.addColumn("blocked-uri");
98 | reportsModel.addColumn("document-uri");
99 | reportsModel.addColumn("original-policy");
100 | reportsModel.addColumn("violated-directive");
101 | reportsTable.setModel(reportsModel);
102 | reportsTable.getColumnModel().getColumn(0).setMaxWidth(45);
103 |
104 | reportsTable.getSelectionModel().addListSelectionListener((ListSelectionEvent event) -> {
105 | int viewRow = reportsTable.getSelectedRow();
106 | if(viewRow == -1) return;
107 |
108 | Vector values = (Vector) reportsModel.getDataVector().get(viewRow);
109 | selectReportItem((String) values.get(0));
110 | });
111 |
112 | //Analyze
113 | analyseButton.addActionListener((ActionEvent e) -> {
114 | String value = (String) comboBox1.getSelectedItem();
115 | if (value != null)
116 | controller.analyzeDomain(value);
117 | });
118 |
119 |
120 | //Refresh button
121 | this.refreshButton.setText("\u21BB");
122 | refreshButton.addActionListener(new ActionListener() {
123 | @Override
124 | public void actionPerformed(ActionEvent e) {
125 | controller.refreshDomains();
126 | }
127 | });
128 |
129 | this.warningConfigurationTextPane.addHyperlinkListener(e -> {
130 | if(e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
131 | if (Desktop.isDesktopSupported()) {
132 | try {
133 | Desktop.getDesktop().browse(e.getURL().toURI());
134 | } catch (Exception ex) {
135 | ex.printStackTrace();
136 | }
137 | } else {
138 | System.out.println(e.getURL().toString()+" clicked");
139 | }
140 | }
141 | });
142 | }
143 |
144 | private void selectReportItem(String id) {
145 | controller.selectReport(id);
146 | }
147 |
148 | public JPanel getRootPanel() {
149 | return rootPanel;
150 | }
151 |
152 | ////Domains
153 |
154 | public void clearDomains() {
155 | comboBox1.removeAll();
156 | }
157 |
158 | public void addDomains(Collection domains) {
159 | for (String domain : domains) {
160 | comboBox1.addItem(domain);
161 | }
162 | }
163 |
164 | public void addDomain(String domain) {
165 | comboBox1.addItem(domain);
166 | }
167 |
168 | ////Configurations
169 |
170 | public void setConfiguration(Component configuration) {
171 | configurationPanel.removeAll();
172 | configurationPanel.add(configuration);
173 | }
174 |
175 | ////Resources
176 |
177 | public void clearResources() {
178 | tableResourcesModel.setRowCount(0);
179 | }
180 |
181 | public void addResource(String id, String url, String type) {
182 | Log.debug("Adding resource " + url);
183 | tableResourcesModel.addRow(new String[]{id, url, type});
184 | }
185 |
186 | public void selectResourceItem(String path) {
187 | controller.selectResource(path);
188 | }
189 |
190 | public void setResourceItem(Component resource) {
191 | resourcePanel.removeAll();
192 | resourcePanel.add(resource);
193 | }
194 |
195 | ////Inlines scripts
196 |
197 | public void clearInlineScript() {
198 | tableInlinesModel.setRowCount(0);
199 | }
200 |
201 | public void addInlineScript(String id, String urlString, String line) {
202 | Log.debug("Adding inline script from " + urlString);
203 | tableInlinesModel.addRow(new String[]{id, urlString, line});
204 | }
205 |
206 | public void selectInlineItem(String path) {
207 | controller.selectInline(path);
208 | }
209 |
210 | public void setInlineItem(Component resource) {
211 | inlinePanel.removeAll();
212 | inlinePanel.add(resource);
213 | }
214 |
215 | ////Reports
216 |
217 | public void setReportItem(Component resource) {
218 | reportPanel.removeAll();
219 | reportPanel.add(resource);
220 | }
221 |
222 |
223 | public void addReport(String id, String blockedUri, String documentUri, String originalPolicy, String violatedDirective) {
224 | reportsModel.addRow(new String[]{id, blockedUri, documentUri,originalPolicy,violatedDirective});
225 | }
226 |
227 | public void clearReports() {
228 | reportsModel.setRowCount(0);
229 | }
230 |
231 | {
232 | // GUI initializer generated by IntelliJ IDEA GUI Designer
233 | // >>> IMPORTANT!! <<<
234 | // DO NOT EDIT OR ADD ANY CODE HERE!
235 | $$$setupUI$$$();
236 | }
237 |
238 | /**
239 | * Method generated by IntelliJ IDEA GUI Designer
240 | * >>> IMPORTANT!! <<<
241 | * DO NOT edit this method OR call it in your code!
242 | *
243 | * @noinspection ALL
244 | */
245 | private void $$$setupUI$$$() {
246 | rootPanel = new JPanel();
247 | rootPanel.setLayout(new BorderLayout(0, 0));
248 | final JPanel panel1 = new JPanel();
249 | panel1.setLayout(new BorderLayout(0, 0));
250 | rootPanel.add(panel1, BorderLayout.CENTER);
251 | final JPanel panel2 = new JPanel();
252 | panel2.setLayout(new BorderLayout(0, 0));
253 | panel2.setEnabled(true);
254 | panel1.add(panel2, BorderLayout.NORTH);
255 | comboBox1 = new JComboBox();
256 | comboBox1.setModel(new SortedUniqueComboBoxModel());
257 | panel2.add(comboBox1, BorderLayout.CENTER);
258 | final JPanel panel3 = new JPanel();
259 | panel3.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
260 | panel2.add(panel3, BorderLayout.EAST);
261 | refreshButton = new JButton();
262 | refreshButton.setText("Refresh");
263 | panel3.add(refreshButton);
264 | analyseButton = new JButton();
265 | analyseButton.setHideActionText(true);
266 | analyseButton.setText("Analyze");
267 | panel3.add(analyseButton);
268 | final JPanel panel4 = new JPanel();
269 | panel4.setLayout(new BorderLayout(0, 0));
270 | panel1.add(panel4, BorderLayout.CENTER);
271 | resultTabbedPane = new JTabbedPane();
272 | resultTabbedPane.setTabLayoutPolicy(0);
273 | panel4.add(resultTabbedPane, BorderLayout.CENTER);
274 | configTabPanel = new JPanel();
275 | configTabPanel.setLayout(new BorderLayout(0, 0));
276 | resultTabbedPane.addTab("Configuration", configTabPanel);
277 | configurationPanel = new JPanel();
278 | configurationPanel.setLayout(new BorderLayout(0, 0));
279 | configTabPanel.add(configurationPanel, BorderLayout.CENTER);
280 | final JPanel panel5 = new JPanel();
281 | panel5.setLayout(new BorderLayout(0, 0));
282 | configTabPanel.add(panel5, BorderLayout.SOUTH);
283 | warningConfigurationTextPane = new JTextPane();
284 | warningConfigurationTextPane.setEditable(false);
285 | warningConfigurationTextPane.setContentType("text/html");
286 | warningConfigurationTextPane.setText(" Warning: Refer to "Inline Scripts" to see scripts that are not compatible with CSP strict mode (Directive "script-src 'unsafe-inline'" is not included.).
More information on CSP pitfalls ");
287 | panel5.add(warningConfigurationTextPane, BorderLayout.CENTER);
288 | final JPanel panel6 = new JPanel();
289 | panel6.setLayout(new BorderLayout(0, 0));
290 | resultTabbedPane.addTab("External Resources", panel6);
291 | final JSplitPane splitPane1 = new JSplitPane();
292 | splitPane1.setOrientation(0);
293 | panel6.add(splitPane1, BorderLayout.CENTER);
294 | resourcePanel = new JPanel();
295 | resourcePanel.setLayout(new BorderLayout(0, 0));
296 | splitPane1.setRightComponent(resourcePanel);
297 | final JScrollPane scrollPane1 = new JScrollPane();
298 | splitPane1.setLeftComponent(scrollPane1);
299 | resourcesTable = new JTable();
300 | scrollPane1.setViewportView(resourcesTable);
301 | final JPanel panel7 = new JPanel();
302 | panel7.setLayout(new BorderLayout(0, 0));
303 | resultTabbedPane.addTab("Inline Scripts", panel7);
304 | final JSplitPane splitPane2 = new JSplitPane();
305 | splitPane2.setOrientation(0);
306 | panel7.add(splitPane2, BorderLayout.CENTER);
307 | final JScrollPane scrollPane2 = new JScrollPane();
308 | splitPane2.setLeftComponent(scrollPane2);
309 | inlinesTable = new JTable();
310 | scrollPane2.setViewportView(inlinesTable);
311 | inlinePanel = new JPanel();
312 | inlinePanel.setLayout(new BorderLayout(0, 0));
313 | splitPane2.setRightComponent(inlinePanel);
314 | final JPanel panel8 = new JPanel();
315 | panel8.setLayout(new BorderLayout(0, 0));
316 | resultTabbedPane.addTab("Reports", panel8);
317 | final JSplitPane splitPane3 = new JSplitPane();
318 | splitPane3.setOrientation(0);
319 | panel8.add(splitPane3, BorderLayout.CENTER);
320 | final JScrollPane scrollPane3 = new JScrollPane();
321 | splitPane3.setLeftComponent(scrollPane3);
322 | reportsTable = new JTable();
323 | scrollPane3.setViewportView(reportsTable);
324 | reportPanel = new JPanel();
325 | reportPanel.setLayout(new BorderLayout(0, 0));
326 | splitPane3.setRightComponent(reportPanel);
327 | }
328 |
329 | /**
330 | * @noinspection ALL
331 | */
332 | public JComponent $$$getRootComponent$$$() {
333 | return rootPanel;
334 | }
335 | }
336 |
--------------------------------------------------------------------------------
/csp-auditor-core/src/main/java/ca/gosecure/cspauditor/gui/generator/CspGeneratorPanelController.java:
--------------------------------------------------------------------------------
1 | package ca.gosecure.cspauditor.gui.generator;
2 |
3 | public interface CspGeneratorPanelController {
4 |
5 | void analyzeDomain(String domain);
6 |
7 | void refreshDomains();
8 |
9 | void selectResource(String id);
10 |
11 | void selectInline(String id);
12 |
13 | void selectReport(String id);
14 | }
15 |
--------------------------------------------------------------------------------
/csp-auditor-core/src/main/java/ca/gosecure/cspauditor/gui/generator/CspGeneratorPanelUiProvider.java:
--------------------------------------------------------------------------------
1 | package ca.gosecure.cspauditor.gui.generator;
2 |
3 | import java.awt.*;
4 |
5 | public interface CspGeneratorPanelUiProvider {
6 |
7 | Component getTextEditor(byte[] content);
8 |
9 | // Component getRequestEditor(byte[] request,byte[] response);
10 | }
11 |
--------------------------------------------------------------------------------
/csp-auditor-core/src/main/java/ca/gosecure/cspauditor/gui/generator/RequestResponse.java:
--------------------------------------------------------------------------------
1 | package ca.gosecure.cspauditor.gui.generator;
2 |
3 | public interface RequestResponse {
4 | }
5 |
--------------------------------------------------------------------------------
/csp-auditor-core/src/main/java/ca/gosecure/cspauditor/gui/generator/SortedUniqueComboBoxModel.java:
--------------------------------------------------------------------------------
1 | package main.java.ca.gosecure.cspauditor.gui.generator;
2 |
3 | public class SortedUniqueComboBoxModel extends javax.swing.DefaultComboBoxModel {
4 |
5 | public SortedUniqueComboBoxModel() {
6 | super();
7 | }
8 |
9 | @Override
10 | public void addElement(Object element) {
11 | insertElementAt(element, 0);
12 | }
13 |
14 | @Override
15 | public void insertElementAt(Object element, int index) {
16 | int size = getSize();
17 | for (index = 0; index < size; index++) {
18 | Comparable c = (Comparable) getElementAt(index);
19 | int comparison = c.compareTo(element);
20 | if (comparison == 0) return;
21 | if (comparison > 0) {
22 | break;
23 | }
24 | }
25 | super.insertElementAt(element, index);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/csp-auditor-core/src/main/java/ca/gosecure/cspauditor/model/ContentSecurityPolicy.java:
--------------------------------------------------------------------------------
1 | package ca.gosecure.cspauditor.model;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.HashMap;
6 | import java.util.HashSet;
7 | import java.util.LinkedHashMap;
8 | import java.util.Map;
9 | import java.util.Set;
10 |
11 | public class ContentSecurityPolicy {
12 |
13 | private String headerName;
14 | private Map directives = new LinkedHashMap<>();
15 |
16 | private Set importantDirectives = new HashSet<>(Arrays.asList("script-src","object-src","style-src","img-src","media-src","frame-src","font-src","connect-src"));
17 |
18 |
19 | public ContentSecurityPolicy(String headerName) {
20 | this.headerName = headerName;
21 | }
22 |
23 | public void addDirective(Directive d) {
24 | directives.put(d.getName(),d);
25 | }
26 |
27 |
28 | public String getHeaderName() {
29 | return headerName;
30 | }
31 |
32 | public Map getDirectives() {
33 | return directives;
34 | }
35 |
36 | public ContentSecurityPolicy getComputedPolicy() {
37 | ContentSecurityPolicy pol = new ContentSecurityPolicy(headerName);
38 |
39 | Directive defaultSrc = directives.get("default-src");
40 |
41 | if(defaultSrc == null) {
42 | defaultSrc = new Directive("default-src", Arrays.asList("'self'"), true);
43 | }
44 | else {
45 | pol.addDirective(defaultSrc);
46 | }
47 |
48 | //Display the important first
49 | for(String directive : importantDirectives) {
50 | pol.addDirective(getDirectiveOrDefault(directive ,defaultSrc));
51 | }
52 |
53 | //Other directive found..
54 | for(String otherDirName : directives.keySet()) {
55 | if(!importantDirectives.contains(otherDirName)) {
56 | pol.addDirective(directives.get(otherDirName));
57 | }
58 | }
59 |
60 | return pol;
61 | }
62 |
63 | private Directive getDirectiveOrDefault(String name,Directive defaultSrc) {
64 | Directive targetDir = directives.get(name);
65 | return targetDir != null ? targetDir.clone(name) : defaultSrc.cloneImplicit(name);
66 | }
67 |
68 | public String toHeaderString() {
69 | StringBuilder str = new StringBuilder();
70 | for(String key : directives.keySet()) {
71 | str.append(key);
72 | Directive d = directives.get(key);
73 | for(String val : d.getValues()) {
74 | str.append(' ').append(val);
75 | }
76 | str.append("; ");
77 | }
78 | return str.toString();
79 | }
80 |
81 | public String toString() {
82 | StringBuilder str = new StringBuilder();
83 |
84 | ContentSecurityPolicy policy = getComputedPolicy();
85 | str.append("Header : "+policy.headerName+"\n");
86 |
87 | for(Directive d : policy.directives.values()) {
88 | str.append(d.getName()+""+(d.isImplicit()? " (Implicit)" : "")+"\n");
89 | for(String value : d.getValues()) {
90 | str.append("\t- "+value+"\n");
91 | }
92 | }
93 | return str.toString();
94 | }
95 |
96 | public void addDirectiveValue(String key, String domain) {
97 | Directive d = directives.get(key);
98 | if(d == null) {
99 | Directive newD = new Directive(key, new ArrayList<>());
100 | directives.put(key, newD);
101 | d = newD;
102 | }
103 | if(!d.getValues().contains(domain)) {
104 | d.getValues().add(domain);
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/csp-auditor-core/src/main/java/ca/gosecure/cspauditor/model/CspIssue.java:
--------------------------------------------------------------------------------
1 | package ca.gosecure.cspauditor.model;
2 |
3 | import java.io.InputStream;
4 |
5 | public class CspIssue {
6 | public static final int HIGH = 2;
7 | public static final int MED = 1;
8 | public static final int LOW = 0;
9 | public static final int INFO = -1;
10 |
11 | private final int severity;
12 | private final String title;
13 | private final String message;
14 | private final Directive directive;
15 | private final String highlightedValue;
16 |
17 | public CspIssue(int severity, String title, String message, Directive directive,String highlightValue) {
18 | this.severity = severity;
19 | this.title = title;
20 | this.message = message;
21 | this.directive = directive;
22 | this.highlightedValue = highlightValue;
23 | }
24 |
25 | public int getSeverity() {
26 | return severity;
27 | }
28 |
29 | public String getTitle() {
30 | return title;
31 | }
32 |
33 | public String getMessage() {
34 | return message;
35 | }
36 |
37 | public Directive getDirective() {
38 | return directive;
39 | }
40 |
41 | public String getHighlightedValue() {
42 | return highlightedValue;
43 | }
44 |
45 | public String getLocalizedMessage() {
46 | InputStream in = getClass().getResourceAsStream("/resources/descriptions/"+message+".htm");
47 | if(in == null) {
48 | return "Localized message not found :(";
49 | }
50 |
51 | String description = convertStreamToString(in);
52 |
53 | if(directive != null) {
54 | return description + "\nWeak configuration
\n" +
55 | ""+directive.getName()+": "+highlightedValue+"
\n" +
56 | "
";
57 | }
58 | else {
59 | return description + "\nWeak configuration
\n" +
60 | ""+highlightedValue+"
\n" +
61 | "
";
62 | }
63 | }
64 |
65 | private static String convertStreamToString(InputStream is) {
66 | java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
67 | return s.hasNext() ? s.next() : "";
68 | }
69 |
70 | @Override
71 | public String toString() {
72 | String sev = severity == HIGH ? "High" :
73 | severity == MED? "Med" : "Low";
74 | return "[" + sev +"]\t" + message + "\t("+directive+")";
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/csp-auditor-core/src/main/java/ca/gosecure/cspauditor/model/Directive.java:
--------------------------------------------------------------------------------
1 | package ca.gosecure.cspauditor.model;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.List;
6 |
7 | public class Directive {
8 | private final String name;
9 | private final List values;
10 | private final boolean implicit;
11 |
12 | public Directive(String name,List values) {
13 | this.name = name;
14 | this.values = new ArrayList<>(values);
15 | this.implicit = false;
16 | }
17 |
18 | public Directive(String name,List values,boolean implicit) {
19 | this.name = name;
20 | this.values = new ArrayList<>(values);
21 | this.implicit = implicit;
22 | }
23 |
24 | //Getters
25 |
26 | public String getName() {
27 | return name;
28 | }
29 |
30 | public List getValues() {
31 | return values;
32 | }
33 |
34 | public boolean isImplicit() {
35 | return implicit;
36 | }
37 |
38 | ////Clones
39 |
40 | protected Directive clone(String name) {
41 | return new Directive(name, cloneArrayList(values), false);
42 | }
43 | protected Directive cloneImplicit(String name) {
44 | return new Directive(name, cloneArrayList(values), true);
45 | }
46 |
47 | private List cloneArrayList(List values) {
48 | List newList = new ArrayList();
49 | newList.addAll(values);
50 | return newList;
51 | }
52 |
53 | @Override
54 | public String toString() {
55 | return getName()+": "+ join("",getValues());
56 | }
57 |
58 | /**
59 | * In replacement of String.join() from Java 8.
60 | * @param delimiter the delimiter that separates each element
61 | * @param elements the elements to join together.
62 | * @return a new String that is composed of the elements separated by the delimiter
63 | */
64 | public static String join(String delimiter, List elements)
65 | {
66 | StringBuilder sb = new StringBuilder();
67 | for(int i = 0; i < elements.size(); i++)
68 | {
69 | sb.append(elements.get(i));
70 | if(i < elements.size() - 1)
71 | sb.append(delimiter);
72 | }
73 | return sb.toString();
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/csp-auditor-core/src/main/java/ca/gosecure/cspauditor/model/HeaderValidation.java:
--------------------------------------------------------------------------------
1 | package ca.gosecure.cspauditor.model;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public class HeaderValidation {
7 |
8 | private static final String[] deprecatedHeaders = {"X-Content-Security-Policy"};
9 |
10 | public static boolean isAllowingAnyScript(String name, String value) {
11 | return (name.equals("script-src") || name.equals("object-src"))
12 | && (value.equals("*"));
13 | }
14 |
15 | public static boolean isAllowingInlineScript(String name, String value) {
16 | return (name.equals("script-src") || name.equals("object-src"))
17 | && (value.equals("'unsafe-inline'"));
18 | }
19 | public static boolean isAllowingUnsafeEvalScript(String name, String value) {
20 | return (name.equals("script-src") || name.equals("object-src"))
21 | && (value.equals("'unsafe-eval'"));
22 | }
23 |
24 | public static boolean isAllowingAny(String name, String value) {
25 | return value.equals("'unsafe-inline'") || value.equals("'unsafe-eval'") || value.equals("*");
26 | }
27 |
28 | public static boolean isAllowingAnyStyle(String name, String value) {
29 | return (name.equals("style-src") && value.equals("*"));
30 | }
31 |
32 | public static boolean isUserContentHost(String name, String value) {
33 | if(!(name.equals("script-src") || name.equals("object-src"))) {
34 | return false;
35 | }
36 | return WeakCdnHost.getInstance().isUserContentHost(value);
37 | }
38 |
39 | public static boolean isHostingVulnerableJs(String name, String value) {
40 | if(!(name.equals("script-src") || name.equals("object-src"))) {
41 | return false;
42 | }
43 | return WeakCdnHost.getInstance().isHostingVulnerableJs(value);
44 | }
45 |
46 | public static boolean isHeaderDeprecated(String headerName) {
47 | return !"content-security-policy".equals(headerName.toLowerCase());
48 | }
49 |
50 | public static List validateCspConfig(List csp) {
51 | List issues = new ArrayList<>();
52 |
53 | for(ContentSecurityPolicy policyOrig : csp) {
54 | ContentSecurityPolicy policy = policyOrig.getComputedPolicy();
55 |
56 | if(isHeaderDeprecated(policy.getHeaderName())){
57 | issues.add(new CspIssue(CspIssue.MED, "Deprecated header name", //
58 | "issue_deprecated_header_name",null,policy.getHeaderName()));
59 | }
60 |
61 | for (Directive d : policy.getDirectives().values()) {
62 | for (String value : d.getValues()) {
63 | if (isAllowingAnyScript(d.getName(),value)) {
64 | issues.add(new CspIssue(CspIssue.MED, "External scripts allowed", "issue_script_wildcard", d, value));
65 | } else if (isAllowingInlineScript(d.getName(),value)) {
66 | issues.add(new CspIssue(CspIssue.MED, "Inline scripts can be inserted", "issue_script_unsafe_inline",d, value));
67 | } else if (isAllowingUnsafeEvalScript(d.getName(),value)) {
68 | issues.add(new CspIssue(CspIssue.MED, "Libraries using eval or setTimeout are allow", "issue_script_unsafe_eval",d, value));
69 | } else if (isAllowingAnyStyle(d.getName(),value)) {
70 | issues.add(new CspIssue(CspIssue.LOW, "External stylesheets allowed", "issue_style", d, value));
71 | } else if (isUserContentHost(d.getName(), value)) {
72 | issues.add(new CspIssue(CspIssue.MED, "The domain is hosting user content", "issue_risky_host_user_content", d, value));
73 | } else if (isHostingVulnerableJs(d.getName(), value)) {
74 | issues.add(new CspIssue(CspIssue.MED, "The domain is hosting vulnerable JavaScript", "issue_risky_host_known_vulnerable_js", d, value));
75 | } else if (isAllowingAny(d.getName(),value)) {
76 | issues.add(new CspIssue(CspIssue.INFO, "Use of wildcard", "issue_wildcard_limited", d, value));
77 | }
78 | }
79 | }
80 | }
81 |
82 | return issues;
83 | }
84 |
85 |
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/csp-auditor-core/src/main/java/ca/gosecure/cspauditor/model/WeakCdnHost.java:
--------------------------------------------------------------------------------
1 | package ca.gosecure.cspauditor.model;
2 |
3 | import ca.gosecure.cspauditor.util.SimpleListFile;
4 | import com.esotericsoftware.minlog.Log;
5 |
6 | import java.io.BufferedReader;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.io.InputStreamReader;
10 | import java.util.HashSet;
11 | import java.util.Iterator;
12 | import java.util.List;
13 | import java.util.Set;
14 |
15 | public class WeakCdnHost {
16 |
17 | private Boolean blacklistLoaded = false;
18 |
19 | private static final String USER_CONTENT_HOSTS_PATH = "/resources/data/csp_host_user_content.txt";
20 | private static final String VULNERABLE_JS_HOSTS_PATH = "/resources/data/csp_host_vulnerable_js.txt";
21 |
22 | private Set blacklistUserContentHosts = new HashSet<>();
23 | private Set blacklistVulnerableJsHosts = new HashSet<>();
24 |
25 | private static WeakCdnHost instance = new WeakCdnHost();
26 |
27 | //Singleton pattern
28 | private WeakCdnHost() {}
29 | public static WeakCdnHost getInstance() { return instance; }
30 |
31 |
32 | private void preloadLists() {
33 | if(!blacklistLoaded) { //Race-conditions will at worst load two times the list.
34 | blacklistLoaded = true;
35 |
36 | try {
37 | loadFileToSet(USER_CONTENT_HOSTS_PATH, blacklistUserContentHosts);
38 | loadFileToSet(VULNERABLE_JS_HOSTS_PATH, blacklistVulnerableJsHosts);
39 | } catch (IOException e) {
40 | Log.error("Unable to load the blacklist hosts :"+ e.getMessage(),e);
41 | }
42 | }
43 | }
44 |
45 | private void loadFileToSet(String file,Set set) throws IOException {
46 | Log.debug("Loading file : "+file);
47 |
48 | SimpleListFile.openFile(file, (String line) -> {
49 |
50 | //Adding precise main
51 | set.add(line);
52 |
53 | //Adding subpath
54 | String subPath = line;
55 | int lastIndex = -1;
56 | while((lastIndex = subPath.lastIndexOf("/")) != -1) {
57 | subPath = subPath.substring(0, lastIndex+1);
58 | set.add(subPath);
59 | // System.out.println(subPath);
60 | subPath = subPath.substring(0, subPath.length()-1); //Remove the trailing slash
61 | }
62 |
63 | });
64 |
65 | //Add wildcard on subdomain
66 |
67 | Iterator it = set.iterator();
68 | Set wildcardsVariations = new HashSet<>();
69 | while(it.hasNext()) {
70 | String url = it.next();
71 |
72 | String subDomain = url;
73 |
74 | int firstIndex = -1;
75 | while((firstIndex = subDomain.indexOf(".")) != -1) {
76 | subDomain = subDomain.substring(firstIndex+1,subDomain.length());
77 | wildcardsVariations.add("*."+subDomain);
78 | //System.out.println(subDomain);
79 | }
80 | }
81 |
82 | set.addAll(wildcardsVariations);
83 |
84 | }
85 |
86 | public boolean isUserContentHost(String value) {
87 | value = value.replaceFirst("http://","").replaceFirst("https://","");
88 | preloadLists();
89 | return blacklistUserContentHosts.contains(value) || blacklistUserContentHosts.contains(value+"/");
90 | }
91 |
92 |
93 | public boolean isHostingVulnerableJs(String value) {
94 | value = value.replaceFirst("http://","").replaceFirst("https://","");
95 | preloadLists();
96 | return blacklistVulnerableJsHosts.contains(value) || blacklistVulnerableJsHosts.contains(value+"/");
97 | }
98 |
99 | public Set getBlacklistVulnerableJsHosts() {
100 | preloadLists();
101 | return blacklistVulnerableJsHosts;
102 | }
103 | public Set getBlacklistUserContentHosts() {
104 | preloadLists();
105 | return blacklistUserContentHosts;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/csp-auditor-core/src/main/java/ca/gosecure/cspauditor/model/generator/DetectInlineJavascript.java:
--------------------------------------------------------------------------------
1 | package ca.gosecure.cspauditor.model.generator;
2 |
3 | import ca.gosecure.cspauditor.util.SimpleListFile;
4 | import com.esotericsoftware.minlog.Log;
5 |
6 | import java.io.IOException;
7 | import java.util.ArrayList;
8 | import java.util.HashSet;
9 | import java.util.List;
10 | import java.util.Set;
11 | import java.util.StringTokenizer;
12 | import java.util.regex.Matcher;
13 | import java.util.regex.Pattern;
14 |
15 | public class DetectInlineJavascript {
16 |
17 | private Set jsInlineEvents = new HashSet<>();
18 |
19 | private static DetectInlineJavascript instance = new DetectInlineJavascript();
20 |
21 | //Singleton pattern
22 | private DetectInlineJavascript() {}
23 | public static DetectInlineJavascript getInstance() { return instance; }
24 |
25 | private void preloadEvents() {
26 | if(jsInlineEvents.size() < 1) {
27 | try {
28 | SimpleListFile.openFile("/resources/data/js_inline_event.txt", (String line) -> {
29 | jsInlineEvents.add(line);
30 | });
31 | } catch (IOException e) {
32 | Log.error("Unable to load the inline JS events" + e.getMessage(), e);
33 | }
34 | }
35 | }
36 |
37 | public List findInlineJs(String source) {
38 | preloadEvents();
39 | String[] tokens = source.split("");
40 |
41 | List problematicLines = new ArrayList<>();
42 |
43 | for(String line : tokens) {
44 | if(line.contains(" on")) { //Faster than regex
45 | for(String event : jsInlineEvents) {
46 | if(line.contains(" "+event)) {
47 |
48 | Pattern p = Pattern.compile(".{0,10} " + event + ".{0,50}");
49 | Matcher m = p.matcher(line);
50 |
51 | if (m.find()) {
52 | problematicLines.add(m.group(0));
53 | }
54 | }
55 | }
56 | }
57 | if(line.contains("