├── lib
├── burp-api.jar
├── junit-4.12.jar
├── junit
│ └── runner
│ │ ├── logo.gif
│ │ └── smalllogo.gif
├── sqlite-jdbc-3.8.10.1.jar
├── META-INF
│ └── MANIFEST.MF
└── LICENSE-junit.txt
├── src
└── burp
│ ├── EncodingType.java
│ ├── ItemType.java
│ ├── Parameter.java
│ ├── SearchType.java
│ ├── ParameterWithHash.java
│ ├── Utilities.java
│ ├── HashEngine.java
│ ├── HashAlgorithm.java
│ ├── HashAlgorithmName.java
│ ├── HashMatchesIssueText.java
│ ├── HashRecord.java
│ ├── CcUnitTests.java
│ ├── HashDiscoveredIssueText.java
│ ├── Issue.java
│ ├── Item.java
│ ├── Config.java
│ ├── BurpHashEmailTests.java
│ ├── Database.java
│ ├── GuiTab.java
│ └── BurpExtender.java
├── .gitignore
├── BappManifest.bmf
├── BappDescription.html
├── LICENSE
└── README.md
/lib/burp-api.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PortSwigger/burp-hash/master/lib/burp-api.jar
--------------------------------------------------------------------------------
/lib/junit-4.12.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PortSwigger/burp-hash/master/lib/junit-4.12.jar
--------------------------------------------------------------------------------
/lib/junit/runner/logo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PortSwigger/burp-hash/master/lib/junit/runner/logo.gif
--------------------------------------------------------------------------------
/src/burp/EncodingType.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | enum EncodingType
4 | {
5 | Hex, Base64, StringBase64
6 | };
--------------------------------------------------------------------------------
/src/burp/ItemType.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | enum ItemType
4 | {
5 | COOKIE, PARAMETER, VALUE_ONLY;
6 | }
7 |
--------------------------------------------------------------------------------
/lib/sqlite-jdbc-3.8.10.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PortSwigger/burp-hash/master/lib/sqlite-jdbc-3.8.10.1.jar
--------------------------------------------------------------------------------
/lib/junit/runner/smalllogo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PortSwigger/burp-hash/master/lib/junit/runner/smalllogo.gif
--------------------------------------------------------------------------------
/src/burp/Parameter.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | /**
4 | * Stores parameters and a list of their {@link ParameterHash}es.
5 | */
6 | class Parameter
7 | {
8 | String name = "", value = "";
9 | }
10 |
--------------------------------------------------------------------------------
/src/burp/SearchType.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | /**
4 | * Used in {@link HashRecord} to flag the source of the hash. Handling differs based on this distinction.
5 | */
6 | enum SearchType
7 | {
8 | REQUEST, RESPONSE
9 | };
10 |
--------------------------------------------------------------------------------
/src/burp/ParameterWithHash.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | /**
4 | * Stores the hash of a {@link Parameter} along with its {@HashAlgorithmName}.
5 | */
6 | class ParameterWithHash
7 | {
8 | Parameter parameter;
9 | HashAlgorithmName algorithm;
10 | String hashedValue = "";
11 | }
--------------------------------------------------------------------------------
/src/burp/Utilities.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | class Utilities
4 | {
5 | static String byteArrayToHex(byte[] bytes)
6 | {
7 | StringBuilder sb = new StringBuilder(bytes.length * 2);
8 | for(byte b: bytes)
9 | sb.append(String.format("%02x", b & 0xff));
10 | return sb.toString();
11 | }
12 | }
--------------------------------------------------------------------------------
/lib/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 | Implementation-Vendor: JUnit
3 | Implementation-Title: JUnit
4 | Implementation-Version: 4.12
5 | Implementation-Vendor-Id: junit
6 | Built-By: jenkins
7 | Build-Jdk: 1.6.0_45
8 | Created-By: Apache Maven 3.0.4
9 | Archiver-Version: Plexus Archiver
10 |
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # vim swap
2 | *.swp
3 |
4 | # burp
5 | *.burp
6 |
7 | # Distribution / packaging
8 | build/
9 | dist/
10 |
11 | # java
12 | *.class
13 |
14 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
15 | hs_err_pid*
16 |
17 | # eclipse
18 | .project
19 | .settings
20 | /bin/
21 | .classpath
22 | .externalToolBuilders
23 | *.jardesc
24 |
--------------------------------------------------------------------------------
/BappManifest.bmf:
--------------------------------------------------------------------------------
1 | Uuid: 34db77290bca46f1b2321bbe5e116ff2
2 | ExtensionType: 1
3 | Name: Burp-hash
4 | RepoName: burp-hash
5 | ScreenVersion: 1.0
6 | SerialVersion: 1
7 | MinPlatformVersion: 0
8 | ProOnly: True
9 | Author: Scott Johnson, Tim MalcomVetter, Matt South
10 | ShortDescription: Identifies previously submitted inputs appearing in hashed form.
11 | EntryPoint: dist/burp-hash.jar
12 | BuildCommand: ant
13 |
--------------------------------------------------------------------------------
/BappDescription.html:
--------------------------------------------------------------------------------
1 |
Warning: some users have reported high memory and CPU usage when running the extension on large audits.
2 |
3 | This extension passively scans requests looking for values containing hashes
4 | using common algorithms (SHA, MD5, etc.). Observed hashes are reported to the
5 | user.
6 | Additionally, the extension keeps track of submitted parameter values, such
7 | as usernames, email addresses, and ID numbers. When a hash of a previously
8 | submitted value is identified, this is also reported to the user.
9 | Here is a brief video that explains the concept: https://youtu.be/KdgeipzmESE.
10 |
11 | Requires Java version 8
12 |
--------------------------------------------------------------------------------
/src/burp/HashEngine.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import java.security.MessageDigest;
5 | import java.security.NoSuchAlgorithmException;
6 |
7 | /**
8 | * Generates hashes in one place.
9 | */
10 |
11 | class HashEngine
12 | {
13 | static String Hash(String value, HashAlgorithmName algorithm) throws NoSuchAlgorithmException
14 | {
15 | if (value == null) throw new IllegalArgumentException ("Parameter 'value' cannot be null.");
16 | if (algorithm == null) throw new IllegalArgumentException ("Parameter 'algorithm' cannot be null");
17 | MessageDigest md = MessageDigest.getInstance(algorithm.getValue());
18 | byte[] digest = md.digest(value.getBytes(StandardCharsets.UTF_8));
19 | return Utilities.byteArrayToHex(digest);
20 | }
21 | }
--------------------------------------------------------------------------------
/src/burp/HashAlgorithm.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.util.regex.Pattern;
4 | import java.io.Serializable;
5 |
6 | /**
7 | * Each instance of this object represent one hash algorithm and contains everything needed to search
8 | * for hashes generated by the algorithm.
9 | */
10 | class HashAlgorithm implements Serializable
11 | {
12 | private static final long serialVersionUID = -110268321075344518L;
13 | int id = 0;
14 | int charWidth;
15 | HashAlgorithmName name;
16 | Pattern pattern;
17 | final String hexRegex = "([a-fA-F0-9]+)";
18 |
19 | boolean enabled;
20 |
21 | HashAlgorithm(int charWidth, HashAlgorithmName name, int id, boolean enabled)
22 | {
23 | this.charWidth = charWidth;
24 | this.name = name;
25 | pattern = Pattern.compile(String.format(hexRegex, charWidth));
26 | this.id = id;
27 | this.enabled = enabled;
28 | }
29 | }
--------------------------------------------------------------------------------
/src/burp/HashAlgorithmName.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | /**
4 | * A list of supported hash algorithms.
5 | *
6 | * Note the different output from the following methods:
7 | * HashAlgorithmName.SHA_256.getValue() == SHA-256
8 | * HashAlgorithmName.SHA_256.toString() == SHA_256
9 | *
10 | * Example Usage:
11 | * HashAlgorithmName han = HashAlgorithmName.SHA_1;
12 | * MessageDigest md = MessageDigest.getInstance(han.getValue());
13 | */
14 | enum HashAlgorithmName {
15 | MD5("MD5"), SHA_1("SHA-1"), SHA_224("SHA-224"), SHA_256("SHA-256"), SHA_384("SHA-384"), SHA_512("SHA-512");
16 |
17 | static HashAlgorithmName getName(String text) {
18 | return valueOf(text.replaceAll("-", "_").toUpperCase());
19 | }
20 |
21 | final String text;
22 |
23 | private HashAlgorithmName(String text) {
24 | this.text = text;
25 | }
26 |
27 | String getValue() {
28 | return text;
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 burp-hash
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/burp/HashMatchesIssueText.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | /**
4 | * Generates text used in creating Burp Scanner issues.
5 | */
6 | class HashMatchesIssueText
7 | {
8 | String Name, Details, Severity, Confidence, RemediationDetails, Background, RemediationBackground;
9 |
10 | HashMatchesIssueText(HashRecord hash, String plainTextValue)
11 | {
12 | Severity = "High";
13 | Name = hash.algorithm.name.text + " Hash Match";
14 | String source = SearchType.RESPONSE.toString();
15 | if (hash.searchType.equals(SearchType.REQUEST))
16 | {
17 | source = SearchType.REQUEST.toString();
18 | }
19 | Details = "The " + source + " contains a " + hash.algorithm.name.text + " hashed value that matches an observed parameter.
\n"
20 | + "Observed hash: " + hash.getNormalizedRecord() + "
"
21 | + "Source parameter: " + plainTextValue + "
";
22 | Confidence = "Firm";
23 | RemediationDetails = "Only use salted or keyed hashes for high security operations.";
24 | RemediationBackground = "This was found by the " + BurpExtender.extensionName +
25 | " extension: " + BurpExtender.extensionUrl + "";
26 | }
27 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # burp-hash
2 |
3 | Burp-hash is a Burp Suite plugin.
4 |
5 | Many applications will hash parameters such as ID numbers and email addresses for use in secure tokens, like session cookies. The plugin will passively scan requests looking for hashed values. Once a hashed value is found, it is compared to a table of parameters already observed in the application to find a match. The plugin keeps a lookout for parameters such as usernames, email addresses, and ID numbers. It also keeps a lookout for hashes (SHA, MD5, etc). It hashes new data and compares to observed hashes. The user receives a notification if any hashes match. This automates the process of trying to guess common parameters used in the generation of hashes observed in an application.
6 |
7 | Here is a brief video that explains the concept: https://youtu.be/KdgeipzmESE
8 |
9 | ### Release
10 |
11 | We are pleased to announce burp-hash has been accepted for [Black Hat USA Arsenal 2015](https://www.blackhat.com/us-15/arsenal.html#burp-hash). Following the presentation at Black Hat, the software will be released to the public here on GitHub.
12 |
13 |
14 | ### Created by
15 |
16 | * [Scott Johnson](https://twitter.com/scottj)
17 | * [Tim MalcomVetter](https://twitter.com/TeeEmmVee)
18 | * [Matt South](https://twitter.com/themattymcfatty)
19 |
20 | ### [Download](https://github.com/burp-hash/burp-hash/releases/)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/burp/HashRecord.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.Base64;
6 |
7 | /**
8 | * Stores found strings that appear to be hashes.
9 | */
10 | class HashRecord
11 | {
12 | List markers = new ArrayList<>();
13 | String record = "";
14 | HashAlgorithm algorithm;
15 | EncodingType encodingType;
16 | SearchType searchType;
17 | boolean reported = false;
18 |
19 | String getNormalizedRecord() //TODO: normalize h:e:x, 0xFF
20 | {
21 | if (encodingType.equals(EncodingType.Base64))
22 | {
23 | return Utilities.byteArrayToHex(Base64.getDecoder().decode(record)).toLowerCase();
24 | }
25 | if (encodingType.equals(EncodingType.StringBase64))
26 | {
27 | return new String(Base64.getDecoder().decode(record)).toLowerCase();
28 | }
29 | return record.toLowerCase();
30 | }
31 |
32 | @Override
33 | public String toString()
34 | {
35 | if (!encodingType.equals(EncodingType.Hex))
36 | {
37 | return algorithm + " Hash " + record + " (" + getNormalizedRecord() + ")";
38 | }
39 | return algorithm + " Hash " + record;
40 | }
41 |
42 | void sortMarkers()
43 | {
44 | List sorted = new ArrayList<>();
45 | int[] previous = { -1, -1 };
46 | for (int[] marker : markers)
47 | {
48 | boolean unique = true;
49 | for(int[] m : sorted)
50 | {
51 | if (m[0] == marker[0] && m[1] == marker[1])
52 | {
53 | unique = false;
54 | break;
55 | }
56 | }
57 | if(unique)
58 | {
59 | sorted.add(marker);
60 | }
61 | }
62 | markers = sorted;
63 | }
64 | }
--------------------------------------------------------------------------------
/src/burp/CcUnitTests.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import java.util.regex.Matcher;
6 |
7 | import org.junit.After;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 |
11 | public class CcUnitTests {
12 |
13 | @Before
14 | public void setUp() throws Exception {
15 | }
16 |
17 | @After
18 | public void tearDown() throws Exception {
19 | }
20 |
21 | @Test
22 | public void test() {
23 | fail("Not yet implemented");
24 | }
25 |
26 | BurpExtender burpExtender = new BurpExtender();
27 |
28 | @Test
29 | public void emailregex0()
30 | {
31 | String req = "&CC=4929001695946994&cvv=123";
32 | Matcher matcher = burpExtender.ccRegex.matcher(req);
33 | assertTrue(matcher.find() && matcher.group().equals("4929001695946994"));
34 | }
35 |
36 | @Test
37 | public void emailregex1()
38 | {
39 | String req = "{\"cc\":\"4929001695946994\"}";
40 | Matcher matcher = burpExtender.ccRegex.matcher(req);
41 | assertTrue(matcher.find() && matcher.group().equals("4929001695946994"));
42 | }
43 |
44 | @Test
45 | public void emailregex2()
46 | {
47 | String req = "{cc:4929001695946994}";
48 | Matcher matcher = burpExtender.ccRegex.matcher(req);
49 | assertTrue(matcher.find() && matcher.group().equals("4929001695946994"));
50 | }
51 |
52 | @Test
53 | public void emailregex3()
54 | {
55 | String req = "&CC=5493144048785645&cvv=123";
56 | Matcher matcher = burpExtender.ccRegex.matcher(req);
57 | assertTrue(matcher.find() && matcher.group().equals("5493144048785645"));
58 | }
59 |
60 | @Test
61 | public void emailregex4()
62 | {
63 | String req = "&CC=5493-1440-4878-5645&cvv=123";
64 | Matcher matcher = burpExtender.ccRegex.matcher(req);
65 | assertTrue(matcher.find() && matcher.group().equals("5493-1440-4878-5645"));
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/burp/HashDiscoveredIssueText.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | /**
4 | * Generates text used in creating Burp Scanner issues.
5 | */
6 | class HashDiscoveredIssueText
7 | {
8 | String Name, Details, Severity, Confidence, RemediationDetails, Background, RemediationBackground;
9 |
10 | HashDiscoveredIssueText(HashRecord hash)
11 | {
12 | Name = hash.algorithm.name.text + " Hash Discovered";
13 | String source = SearchType.RESPONSE.toString();
14 | if (hash.searchType.equals(SearchType.REQUEST))
15 | {
16 | source = "request";
17 | }
18 | Details = "The " + source + " contains what appears to be a " + hash.algorithm.name.text + " hashed value:\n- " + hash.getNormalizedRecord() + "
";
19 | if (!hash.encodingType.equals(EncodingType.Hex))
20 | {
21 | Details += "
The hash was discovered encoded as:\n";
22 | }
23 | Confidence = "Tentative";
24 | RemediationBackground = "This was found by the " + BurpExtender.extensionName +
25 | " extension: " + BurpExtender.extensionUrl + "";
26 | if (hash.algorithm.equals(HashAlgorithmName.MD5) || hash.algorithm.equals(HashAlgorithmName.SHA_1))
27 | {
28 | Severity = "Medium";
29 | if (hash.algorithm.equals(HashAlgorithmName.MD5))
30 | {
31 | Severity = "Medium";
32 | }
33 | RemediationDetails = "Consider upgrading to a stronger cryptographic hash algorithm, such as SHA-256.";
34 | Background = "This cryptographic algorithm is considered to be weak and should be phased out.\n\n" +
35 | "The presence of a cryptographic hash may be of interest to a penetration tester. " +
36 | "This may assist the tester in locating vectors to bypass access controls.";
37 | }
38 | else
39 | {
40 | Severity = "Information";
41 | RemediationDetails = "No remediation may be necessary. This is purely informational.";
42 | Background = "The presence of a cryptographic hash may be of interest to a penetration tester. " +
43 | "This may assist the tester in locating vectors to bypass access controls.";
44 | }
45 |
46 | }
47 | }
--------------------------------------------------------------------------------
/src/burp/Issue.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.net.URL;
4 |
5 | /**
6 | * Implementation of the {@link IScanIssue} interface.
7 | */
8 | class Issue implements IScanIssue {
9 | private IHttpService httpService;
10 | private URL url;
11 | private IHttpRequestResponse[] httpMessages;
12 | private String issueName;
13 | private String issueDetail;
14 | private String severity;
15 | private String confidence;
16 | private String remediationDetail;
17 | private String issueBackground;
18 | private String remediationBackground;
19 |
20 | Issue(IHttpService httpService, URL url, IHttpRequestResponse[] httpMessages, String issueName,
21 | String issueDetail, String severity, String confidence, String remediationDetail, String issueBackground,
22 | String remediationBackground) {
23 | this.httpService = httpService;
24 | this.url = url;
25 | this.httpMessages = httpMessages;
26 | this.issueName = issueName;
27 | this.issueDetail = issueDetail;
28 | this.severity = severity;
29 | this.confidence = confidence;
30 | this.remediationDetail = remediationDetail;
31 | this.issueBackground = issueBackground;
32 | this.remediationBackground = remediationBackground;
33 | }
34 |
35 | @Override
36 | public URL getUrl() {
37 | return this.url;
38 | }
39 |
40 | @Override
41 | public String getIssueName() {
42 | return this.issueName;
43 | }
44 |
45 | @Override
46 | public int getIssueType() {
47 | return 134217728; // type is always "extension generated"
48 | }
49 |
50 | @Override
51 | public String getSeverity() {
52 | return this.severity;
53 | }
54 |
55 | @Override
56 | public String getConfidence() {
57 | return this.confidence;
58 | }
59 |
60 | @Override
61 | public String getIssueBackground() {
62 | return this.issueBackground;
63 | }
64 |
65 | @Override
66 | public String getRemediationBackground() {
67 | return this.remediationBackground;
68 | }
69 |
70 | @Override
71 | public String getIssueDetail() {
72 | return this.issueDetail;
73 | }
74 |
75 | @Override
76 | public String getRemediationDetail() {
77 | return this.remediationDetail;
78 | }
79 |
80 | @Override
81 | public IHttpRequestResponse[] getHttpMessages() {
82 | return this.httpMessages;
83 | }
84 |
85 | @Override
86 | public IHttpService getHttpService() {
87 | return this.httpService;
88 | }
89 |
90 | @Override
91 | public String toString() {
92 | return "Name: " + this.issueName + " URL: " + this.url + " Severity: " + this.severity + " Confidence: "
93 | + this.confidence + " Detail: " + this.issueDetail + " Remediation: " + this.remediationDetail
94 | + " Background: " + this.issueBackground + " Remediation Background: " + this.remediationBackground;
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/burp/Item.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.util.Date;
4 |
5 | /**
6 | * This implementation of {@link ICookie}, {@link IParameter}, and {@link IBurpHashParameter} is used to homogenize the
7 | * object types during processing.
8 | */
9 | class Item implements ICookie, IParameter
10 | {
11 | private ItemType type;
12 | private Object item;
13 | private String value = null;
14 |
15 | Item(IParameter p)
16 | {
17 | this.type = ItemType.PARAMETER;
18 | this.item = p;
19 | }
20 |
21 | Item(ICookie c)
22 | {
23 | this.type = ItemType.COOKIE;
24 | this.item = c;
25 | }
26 |
27 | Item(String s)
28 | {
29 | this.value = s;
30 | this.type = ItemType.VALUE_ONLY;
31 | this.item = s;
32 | }
33 |
34 | Object getItem()
35 | {
36 | return item;
37 | }
38 |
39 | ItemType getItemType() {
40 | return this.type;
41 | }
42 |
43 | // Methods common to both interfaces
44 | @Override
45 | public String getName()
46 | {
47 | switch (this.type)
48 | {
49 | case COOKIE:
50 | return ((ICookie) item).getName();
51 | case PARAMETER:
52 | return ((IParameter) item).getName();
53 | case VALUE_ONLY:
54 | return ((String) "");
55 | }
56 | return null;
57 | }
58 |
59 | @Override
60 | public String getValue()
61 | {
62 | switch (this.type)
63 | {
64 | case COOKIE:
65 | if (this.value == null) return ((ICookie) item).getValue();
66 | return this.value;
67 | case PARAMETER:
68 | if (this.value == null) return ((IParameter) item).getValue();
69 | return this.value;
70 | case VALUE_ONLY:
71 | return this.value;
72 | }
73 | return null;
74 | }
75 |
76 | public void setValue(String s)
77 | {
78 | this.value = s;
79 | }
80 |
81 | // ICookie methods
82 | @Override
83 | public String getDomain() {
84 | if (this.getItemType() == ItemType.COOKIE) {
85 | return ((ICookie) item).getDomain();
86 | } else {
87 | return null;
88 | }
89 | }
90 |
91 | @Override
92 | public Date getExpiration() {
93 | if (this.getItemType() == ItemType.COOKIE) {
94 | return ((ICookie) item).getExpiration();
95 | } else {
96 | return null;
97 | }
98 | }
99 |
100 | // IParameter methods
101 | @Override
102 | public byte getType() {
103 | if (this.getItemType() == ItemType.PARAMETER) {
104 | return ((IParameter) item).getType();
105 | } else {
106 | return -1;
107 | }
108 | }
109 |
110 | @Override
111 | public int getNameStart() {
112 | if (this.getItemType() == ItemType.PARAMETER) {
113 | return ((IParameter) item).getNameStart();
114 | } else {
115 | return -1;
116 | }
117 | }
118 |
119 | @Override
120 | public int getNameEnd() {
121 | if (this.getItemType() == ItemType.PARAMETER) {
122 | return ((IParameter) item).getNameEnd();
123 | } else {
124 | return -1;
125 | }
126 | }
127 |
128 | @Override
129 | public int getValueStart() {
130 | if (this.getItemType() == ItemType.PARAMETER) {
131 | return ((IParameter) item).getValueStart();
132 | } else {
133 | return -1;
134 | }
135 | }
136 |
137 | @Override
138 | public int getValueEnd() {
139 | if (this.getItemType() == ItemType.PARAMETER) {
140 | return ((IParameter) item).getValueEnd();
141 | } else {
142 | return -1;
143 | }
144 | }
145 | }
--------------------------------------------------------------------------------
/src/burp/Config.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.ByteArrayOutputStream;
5 | import java.io.IOException;
6 | import java.io.ObjectInputStream;
7 | import java.io.ObjectOutputStream;
8 | import java.io.PrintWriter;
9 | import java.io.Serializable;
10 | import java.util.ArrayList;
11 | import java.util.Base64;
12 | import java.util.List;
13 |
14 | /**
15 | * Manages settings for the extension. It's crude, but it works. This object is serialized
16 | * and stored as a string using Burp's extension settings functionality.
17 | */
18 | class Config implements Serializable {
19 | private static final long serialVersionUID = 1L;
20 | private static final String moduleName = "Config";
21 |
22 | /**
23 | * load saved config if it exists
24 | * otherwise return default config
25 | */
26 | static Config load(BurpExtender b) throws Exception {
27 | IBurpExtenderCallbacks c = b.getCallbacks();
28 | String encodedConfig = c.loadExtensionSetting(BurpExtender.extensionName);
29 | if (encodedConfig == null) {
30 | return new Config(b);
31 | }
32 | byte[] decodedConfig = Base64.getDecoder().decode(encodedConfig);
33 | ByteArrayInputStream bytes = new ByteArrayInputStream(decodedConfig);
34 | ObjectInputStream in = new ObjectInputStream(bytes);
35 | Config cfg = (Config) in.readObject();
36 | cfg.callbacks = c;
37 | cfg.stdErr = b.getStdErr();
38 | cfg.stdOut = b.getStdOut();
39 | cfg.loadHashAlgorithms(); //get a fresh copy of the algorithms so old regexes are not captured here.
40 | if (cfg.hashAlgorithms == null || cfg.hashAlgorithms.isEmpty())
41 | {
42 | cfg.stdOut.println(moduleName + ": Hash algorithm configuration is missing ... rebuilding defaults.");
43 | cfg.hashAlgorithms = new ArrayList();
44 | cfg.loadHashAlgorithms();
45 | }
46 | cfg.stdOut.println(moduleName + ": Successfully loaded settings.");
47 | return cfg;
48 | }
49 |
50 | private transient IBurpExtenderCallbacks callbacks;
51 | private transient PrintWriter stdErr;
52 | private transient PrintWriter stdOut;
53 |
54 | // variables below are the extension settings
55 | String databaseFilename;
56 | boolean reportHashesOnly;
57 | boolean debug = true;
58 | public List hashAlgorithms = new ArrayList();
59 |
60 | /**
61 | * constructor used only when saved config is not found
62 | */
63 | private Config(BurpExtender b)
64 | {
65 | callbacks = b.getCallbacks();
66 | stdErr = b.getStdErr();
67 | stdOut = b.getStdOut();
68 | setDefaults();
69 | stdOut.println(moduleName + ": No saved settings found - using defaults.");
70 | }
71 |
72 | /**
73 | * reset to default config
74 | */
75 | void reset()
76 | {
77 | callbacks.saveExtensionSetting(BurpExtender.extensionName, null);
78 | setDefaults();
79 | }
80 |
81 | /**
82 | * save serialized Config object into Burp extension settings
83 | */
84 | void save()
85 | {
86 | ByteArrayOutputStream bytes = new ByteArrayOutputStream();
87 | try
88 | {
89 | ObjectOutputStream out = new ObjectOutputStream(bytes);
90 | out.writeObject(this);
91 | }
92 | catch (IOException e)
93 | {
94 | stdErr.println(moduleName + ": Error saving configuration: " + e);
95 | return;
96 | }
97 | String encoded = Base64.getEncoder().encodeToString(bytes.toByteArray());
98 | callbacks.saveExtensionSetting(BurpExtender.extensionName, encoded);
99 | }
100 |
101 | /**
102 | * set default values in Config properties
103 | */
104 | private void setDefaults()
105 | {
106 | databaseFilename = BurpExtender.extensionName + ".sqlite";
107 | loadHashAlgorithms();
108 | }
109 |
110 | void loadHashAlgorithms()
111 | {
112 | hashAlgorithms = new ArrayList<>();
113 | //As of now, we're always enabling all algorithms:
114 | hashAlgorithms.add(new HashAlgorithm(128, HashAlgorithmName.SHA_512, 6, true));
115 | hashAlgorithms.add(new HashAlgorithm(96, HashAlgorithmName.SHA_384, 5, true));
116 | hashAlgorithms.add(new HashAlgorithm(64, HashAlgorithmName.SHA_256, 4, true));
117 | hashAlgorithms.add(new HashAlgorithm(56, HashAlgorithmName.SHA_224, 3, true));
118 | hashAlgorithms.add(new HashAlgorithm(40, HashAlgorithmName.SHA_1, 2, true));
119 | hashAlgorithms.add(new HashAlgorithm(32, HashAlgorithmName.MD5, 1, true));
120 | }
121 |
122 | void toggleHashAlgorithm(HashAlgorithmName name, boolean enabled)
123 | {
124 | for (HashAlgorithm algo : hashAlgorithms)
125 | {
126 | if (algo.name.equals(name))
127 | {
128 | algo.enabled = enabled;
129 | }
130 | }
131 | }
132 |
133 | boolean isHashEnabled(HashAlgorithmName name)
134 | {
135 | if (hashAlgorithms == null || hashAlgorithms.size() < 1) {
136 | stdErr.println(moduleName + ": Hash algorithm configuration is missing or empty. Cannot check if " + name.toString() + " is enabled.");
137 | return false;
138 | }
139 | for (HashAlgorithm algo: hashAlgorithms)
140 | {
141 | if (algo.name.equals(name))
142 | {
143 | return algo.enabled;
144 | }
145 | }
146 | return false;
147 | }
148 |
149 | int getHashId(HashAlgorithmName name)
150 | {
151 | for (HashAlgorithm algo : hashAlgorithms)
152 | {
153 | if (algo.name.equals(name))
154 | {
155 | return algo.id;
156 | }
157 | }
158 | return 0;
159 | }
160 | }
--------------------------------------------------------------------------------
/src/burp/BurpHashEmailTests.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import java.util.List;
6 | import java.util.regex.Matcher;
7 | import java.util.regex.Pattern;
8 | import java.net.URLDecoder;
9 | import java.util.ArrayList;
10 |
11 | import org.junit.After;
12 | import org.junit.Assert;
13 | import org.junit.Before;
14 | import org.junit.Test;
15 |
16 | @SuppressWarnings("deprecation")
17 | public class BurpHashEmailTests
18 | {
19 | @Before
20 | public void setUp() throws Exception {
21 | }
22 |
23 | @After
24 | public void tearDown() throws Exception {
25 | }
26 |
27 | @Test
28 | public void testJunitIsWorking() {
29 | String str= "Junit is working fine";
30 | assertEquals("Junit is working fine",str);
31 | }
32 |
33 | @Test
34 | public void emailregex0()
35 | {
36 | String email = "joe@example.com";
37 | Matcher matcher = burpExtender.emailRegex.matcher(email);
38 | assertTrue(matcher.find() && matcher.group().equals("joe@example.com"));
39 | }
40 |
41 |
42 | @Test
43 | public void emailregex1()
44 | {
45 | String email = "&email=joe@example.com&";
46 | Matcher matcher = burpExtender.emailRegex.matcher(email);
47 | assertTrue(matcher.find() && matcher.group().equals("joe@example.com"));
48 | }
49 |
50 | @Test
51 | public void emailregex2()
52 | {
53 | String email = "{\"email\":\"foo@bar.com\"}";
54 | Matcher matcher = burpExtender.emailRegex.matcher(email);
55 | assertTrue(matcher.find() && matcher.group().equals("foo@bar.com"));
56 | }
57 |
58 | @Test
59 | public void emailregex3()
60 | {
61 | String email = "{\"email\":\"foo-foo@bar-bar.com\"}";
62 | Matcher matcher = burpExtender.emailRegex.matcher(email);
63 | assertTrue(matcher.find() && matcher.group().equals("foo-foo@bar-bar.com"));
64 | }
65 |
66 | @Test
67 | public void emailregex4()
68 | {
69 | String email = "{\"email\":\"foo.foo@bar.bar.com\"}";
70 | Matcher matcher = burpExtender.emailRegex.matcher(email);
71 | assertTrue(matcher.find() && matcher.group().equals("foo.foo@bar.bar.com"));
72 | }
73 |
74 | @Test
75 | public void emailregex5()
76 | {
77 | String email = "{email:foo-bar@yo-domain.com}";
78 | Matcher matcher = burpExtender.emailRegex.matcher(email);
79 | assertTrue(matcher.find() && matcher.group().equals("foo-bar@yo-domain.com"));
80 | }
81 |
82 | @Test
83 | public void emailregex6()
84 | {
85 | String email = "foo=joe.smith@somewhere.cc&this=not_yours;";
86 | Matcher matcher = burpExtender.emailRegex.matcher(email);
87 | assertTrue(matcher.find() && matcher.group().equals("joe.smith@somewhere.cc"));
88 | }
89 |
90 | @Test
91 | public void emailregex7()
92 | {
93 | String email = "foo=;asdf-asdf@gmail.com;";
94 | Matcher matcher = burpExtender.emailRegex.matcher(email);
95 | assertTrue(matcher.find() && matcher.group().equals("asdf-asdf@gmail.com"));
96 | }
97 |
98 | @Test
99 | public void emailregex8()
100 | {
101 | String email = "foo=;asdf-asdf%40gmail.com;";
102 | Matcher matcher = burpExtender.emailRegex.matcher(URLDecoder.decode(email));
103 | assertTrue(matcher.find() && matcher.group().equals("asdf-asdf@gmail.com"));
104 | }
105 |
106 | @Test
107 | public void emailregex9()
108 | {
109 | String email = "&email=joe@joe.com&acceptTerms=true";
110 | Matcher matcher = burpExtender.emailRegex.matcher(URLDecoder.decode(email));
111 | matcher.find();
112 | String result = matcher.group();
113 | assertTrue(result.equals("joe@joe.com&acceptterms"));
114 | result = result.split("&")[0];
115 | //assertTrue(result.equals("joe@joe.com"));
116 | }
117 |
118 |
119 | BurpExtender burpExtender = new BurpExtender();
120 |
121 | String requestWithUrlEncodedEmail = "POST /blog/forgot2 HTTP/1.1\r\n" +
122 | "Host: 192.168.13.216:999\r\n" +
123 | "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:39.0) Gecko/20100101 Firefox/39.0\r\n" +
124 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
125 | "Accept-Language: en-US,en;q=0.5\r\n" +
126 | "Accept-Encoding: gzip, deflate\r\n" +
127 | "Referer: http://192.168.13.216:999/blog/reset\r\n" +
128 | "Cookie: csrftoken=5865246ded13f3543adef0a39ed494b3\r\n" +
129 | "Connection: keep-alive\r\n" +
130 | "Content-Type: application/x-www-form-urlencoded\r\n" +
131 | "Content-Length: 87\r\n" +
132 | "\r\n" +
133 | "csrfmiddlewaretoken=5865246ded13f3543adef0a39ed494b3&email=joe%40joe.com&acceptTerms=on\r\n";
134 |
135 | @Test
136 | public void FindEmail_urlEncoded_returnsEmptyCollection()
137 | {
138 | List- items = new ArrayList<>();
139 | items.addAll(burpExtender.findEmailRegex(requestWithUrlEncodedEmail));
140 | assertTrue(items.isEmpty());
141 | }
142 |
143 | @Test
144 | public void FindEmail_urlDecoded_returnsNonEmptyCollection()
145 | {
146 | List
- items = new ArrayList<>();
147 | String urlDecoded = URLDecoder.decode(requestWithUrlEncodedEmail);
148 | items.addAll(burpExtender.findEmailRegex(urlDecoded));
149 | assertFalse(items.isEmpty());
150 | }
151 |
152 |
153 | @Test
154 | public void FindEmail_returnsExpectedEmail()
155 | {
156 | List
- items = new ArrayList<>();
157 | String urlDecoded = URLDecoder.decode(requestWithUrlEncodedEmail);
158 | items.addAll(burpExtender.findEmailRegex(urlDecoded));
159 | String expected = "joe@joe.com";
160 | String actual = items.get(0).getValue();
161 | assertTrue(expected.equals(actual));
162 | }
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/lib/LICENSE-junit.txt:
--------------------------------------------------------------------------------
1 | JUnit
2 |
3 | Eclipse Public License - v 1.0
4 |
5 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
6 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
7 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
8 |
9 | 1. DEFINITIONS
10 |
11 | "Contribution" means:
12 |
13 | a) in the case of the initial Contributor, the initial code and
14 | documentation distributed under this Agreement, and
15 | b) in the case of each subsequent Contributor:
16 |
17 | i) changes to the Program, and
18 |
19 | ii) additions to the Program;
20 |
21 | where such changes and/or additions to the Program originate from and are
22 | distributed by that particular Contributor. A Contribution 'originates' from a
23 | Contributor if it was added to the Program by such Contributor itself or anyone
24 | acting on such Contributor's behalf. Contributions do not include additions to
25 | the Program which: (i) are separate modules of software distributed in
26 | conjunction with the Program under their own license agreement, and (ii) are
27 | not derivative works of the Program.
28 |
29 | "Contributor" means any person or entity that distributes the Program.
30 |
31 | "Licensed Patents " mean patent claims licensable by a Contributor which are
32 | necessarily infringed by the use or sale of its Contribution alone or when
33 | combined with the Program.
34 |
35 | "Program" means the Contributions distributed in accordance with this Agreement.
36 |
37 | "Recipient" means anyone who receives the Program under this Agreement,
38 | including all Contributors.
39 |
40 | 2. GRANT OF RIGHTS
41 |
42 | a) Subject to the terms of this Agreement, each Contributor hereby grants
43 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
44 | reproduce, prepare derivative works of, publicly display, publicly perform,
45 | distribute and sublicense the Contribution of such Contributor, if any, and
46 | such derivative works, in source code and object code form.
47 |
48 | b) Subject to the terms of this Agreement, each Contributor hereby grants
49 | Recipient a non-exclusive, worldwide, royalty-free patent license under
50 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
51 | transfer the Contribution of such Contributor, if any, in source code and
52 | object code form. This patent license shall apply to the combination of the
53 | Contribution and the Program if, at the time the Contribution is added by the
54 | Contributor, such addition of the Contribution causes such combination to be
55 | covered by the Licensed Patents. The patent license shall not apply to any
56 | other combinations which include the Contribution. No hardware per se is
57 | licensed hereunder.
58 |
59 | c) Recipient understands that although each Contributor grants the
60 | licenses to its Contributions set forth herein, no assurances are provided by
61 | any Contributor that the Program does not infringe the patent or other
62 | intellectual property rights of any other entity. Each Contributor disclaims
63 | any liability to Recipient for claims brought by any other entity based on
64 | infringement of intellectual property rights or otherwise. As a condition to
65 | exercising the rights and licenses granted hereunder, each Recipient hereby
66 | assumes sole responsibility to secure any other intellectual property rights
67 | needed, if any. For example, if a third party patent license is required to
68 | allow Recipient to distribute the Program, it is Recipient's responsibility to
69 | acquire that license before distributing the Program.
70 |
71 | d) Each Contributor represents that to its knowledge it has sufficient
72 | copyright rights in its Contribution, if any, to grant the copyright license
73 | set forth in this Agreement.
74 |
75 | 3. REQUIREMENTS
76 |
77 | A Contributor may choose to distribute the Program in object code form under
78 | its own license agreement, provided that:
79 |
80 | a) it complies with the terms and conditions of this Agreement; and
81 |
82 | b) its license agreement:
83 |
84 | i) effectively disclaims on behalf of all Contributors all warranties and
85 | conditions, express and implied, including warranties or conditions of title
86 | and non-infringement, and implied warranties or conditions of merchantability
87 | and fitness for a particular purpose;
88 |
89 | ii) effectively excludes on behalf of all Contributors all liability for
90 | damages, including direct, indirect, special, incidental and consequential
91 | damages, such as lost profits;
92 |
93 | iii) states that any provisions which differ from this Agreement are
94 | offered by that Contributor alone and not by any other party; and
95 |
96 | iv) states that source code for the Program is available from such
97 | Contributor, and informs licensees how to obtain it in a reasonable manner on
98 | or through a medium customarily used for software exchange.
99 |
100 | When the Program is made available in source code form:
101 |
102 | a) it must be made available under this Agreement; and
103 |
104 | b) a copy of this Agreement must be included with each copy of the
105 | Program.
106 |
107 | Contributors may not remove or alter any copyright notices contained within the
108 | Program.
109 |
110 | Each Contributor must identify itself as the originator of its Contribution, if
111 | any, in a manner that reasonably allows subsequent Recipients to identify the
112 | originator of the Contribution.
113 |
114 | 4. COMMERCIAL DISTRIBUTION
115 |
116 | Commercial distributors of software may accept certain responsibilities with
117 | respect to end users, business partners and the like. While this license is
118 | intended to facilitate the commercial use of the Program, the Contributor who
119 | includes the Program in a commercial product offering should do so in a manner
120 | which does not create potential liability for other Contributors. Therefore, if
121 | a Contributor includes the Program in a commercial product offering, such
122 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
123 | every other Contributor ("Indemnified Contributor") against any losses, damages
124 | and costs (collectively "Losses") arising from claims, lawsuits and other legal
125 | actions brought by a third party against the Indemnified Contributor to the
126 | extent caused by the acts or omissions of such Commercial Contributor in
127 | connection with its distribution of the Program in a commercial product
128 | offering. The obligations in this section do not apply to any claims or Losses
129 | relating to any actual or alleged intellectual property infringement. In order
130 | to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
131 | Contributor in writing of such claim, and b) allow the Commercial Contributor
132 | to control, and cooperate with the Commercial Contributor in, the defense and
133 | any related settlement negotiations. The Indemnified Contributor may
134 | participate in any such claim at its own expense.
135 |
136 | For example, a Contributor might include the Program in a commercial product
137 | offering, Product X. That Contributor is then a Commercial Contributor. If that
138 | Commercial Contributor then makes performance claims, or offers warranties
139 | related to Product X, those performance claims and warranties are such
140 | Commercial Contributor's responsibility alone. Under this section, the
141 | Commercial Contributor would have to defend claims against the other
142 | Contributors related to those performance claims and warranties, and if a court
143 | requires any other Contributor to pay any damages as a result, the Commercial
144 | Contributor must pay those damages.
145 |
146 | 5. NO WARRANTY
147 |
148 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
149 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
150 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
151 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
152 | Recipient is solely responsible for determining the appropriateness of using
153 | and distributing the Program and assumes all risks associated with its exercise
154 | of rights under this Agreement, including but not limited to the risks and
155 | costs of program errors, compliance with applicable laws, damage to or loss of
156 | data, programs or equipment, and unavailability or interruption of operations.
157 |
158 | 6. DISCLAIMER OF LIABILITY
159 |
160 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
161 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
162 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
163 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
164 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
165 | WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS
166 | GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
167 |
168 | 7. GENERAL
169 |
170 | If any provision of this Agreement is invalid or unenforceable under applicable
171 | law, it shall not affect the validity or enforceability of the remainder of the
172 | terms of this Agreement, and without further action by the parties hereto, such
173 | provision shall be reformed to the minimum extent necessary to make such
174 | provision valid and enforceable.
175 |
176 | If Recipient institutes patent litigation against any
177 | entity (including a cross-claim or counterclaim in a lawsuit) alleging that the
178 | Program itself (excluding combinations of the Program with other software or
179 | hardware) infringes such Recipient's patent(s), then such Recipient's rights
180 | granted under Section 2(b) shall terminate as of the date such litigation is
181 | filed.
182 |
183 | All Recipient's rights under this Agreement shall terminate if it fails to
184 | comply with any of the material terms or conditions of this Agreement and does
185 | not cure such failure in a reasonable period of time after becoming aware of
186 | such noncompliance. If all Recipient's rights under this Agreement terminate,
187 | Recipient agrees to cease use and distribution of the Program as soon as
188 | reasonably practicable. However, Recipient's obligations under this Agreement
189 | and any licenses granted by Recipient relating to the Program shall continue
190 | and survive.
191 |
192 | Everyone is permitted to copy and distribute copies of this Agreement, but in
193 | order to avoid inconsistency the Agreement is copyrighted and may only be
194 | modified in the following manner. The Agreement Steward reserves the right to
195 | publish new versions (including revisions) of this Agreement from time to time.
196 | No one other than the Agreement Steward has the right to modify this Agreement.
197 | The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to
198 | serve as the Agreement Steward to a suitable separate entity. Each new version
199 | of the Agreement will be given a distinguishing version number. The Program
200 | (including Contributions) may always be distributed subject to the version of
201 | the Agreement under which it was received. In addition, after a new version of
202 | the Agreement is published, Contributor may elect to distribute the Program
203 | (including its Contributions) under the new version. Except as expressly stated
204 | in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to
205 | the intellectual property of any Contributor under this Agreement, whether
206 | expressly, by implication, estoppel or otherwise. All rights in the Program not
207 | expressly granted under this Agreement are reserved.
208 |
209 | This Agreement is governed by the laws of the State of New York and the
210 | intellectual property laws of the United States of America. No party to this
211 | Agreement will bring a legal action under this Agreement more than one year
212 | after the cause of action arose. Each party waives its rights to a jury trial
213 | in any resulting litigation.
214 |
215 |
--------------------------------------------------------------------------------
/src/burp/Database.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.io.PrintWriter;
4 | import java.sql.Connection;
5 | import java.sql.DriverManager;
6 | import java.sql.PreparedStatement;
7 | import java.sql.ResultSet;
8 | import java.sql.SQLException;
9 | import java.sql.Statement;
10 | import java.util.ArrayList;
11 | import java.util.Collections;
12 | import java.util.List;
13 |
14 | import org.sqlite.SQLiteConfig;
15 |
16 | import com.sun.webkit.ThemeClient;
17 |
18 | /**
19 | * Handles SQLite database access
20 | */
21 | class Database {
22 | private Config config;
23 | private Connection conn = null;
24 | private PreparedStatement pstmt = null;
25 | private PrintWriter stdErr;
26 | private PrintWriter stdOut;
27 | private final String moduleName = "DB";
28 | private final String connPrefix = "jdbc:sqlite:";
29 |
30 | Database(BurpExtender b)
31 | {
32 | config = b.getConfig();
33 | stdErr = b.getStdErr();
34 | stdOut = b.getStdOut();
35 | try
36 | {
37 | Class.forName("org.sqlite.JDBC"); // load the JDBC Driver
38 | }
39 | catch (ClassNotFoundException e)
40 | {
41 | stdErr.println(e.getMessage());
42 | }
43 | }
44 |
45 | /**
46 | * open a different database file after a config change
47 | */
48 | void changeFile()
49 | {
50 | close();
51 | conn = getConnection();
52 | }
53 |
54 | /**
55 | * close the database connection
56 | */
57 | boolean close()
58 | {
59 | try
60 | {
61 | if (conn != null)
62 | {
63 | conn.close();
64 | }
65 | return true;
66 | }
67 | catch (SQLException e)
68 | {
69 | stdErr.println(e.getMessage());
70 | return false;
71 | }
72 | }
73 |
74 | /**
75 | * TODO: this might need some tweaking
76 | */
77 | protected void finalize() throws Throwable
78 | {
79 | try
80 | {
81 | if (conn != null)
82 | {
83 | conn.close();
84 | }
85 | }
86 | finally
87 | {
88 | super.finalize();
89 | }
90 | }
91 |
92 | /**
93 | * open and return database connections
94 | */
95 | private Connection getConnection()
96 | {
97 | Connection connection;
98 | SQLiteConfig sc = new SQLiteConfig();
99 | sc.setEncoding(SQLiteConfig.Encoding.UTF_8);
100 | try
101 | {
102 | connection = DriverManager.getConnection(connPrefix
103 | + config.databaseFilename, sc.toProperties());
104 | stdOut.println(moduleName + ": Opened database file: " + config.databaseFilename);
105 | }
106 | catch (SQLException e)
107 | {
108 | stdErr.println(e.getMessage());
109 | return null;
110 | }
111 | return connection;
112 | }
113 |
114 | /**
115 | * initialize the database
116 | */
117 | boolean init()
118 | {
119 | Statement stmt = null;
120 | try
121 | {
122 | if (conn == null)
123 | {
124 | conn = getConnection();
125 | }
126 | stmt = conn.createStatement();
127 | stmt.setQueryTimeout(30);
128 | stdOut.println(" + Rebuilding all DB tables.");
129 | String sql_dropTables = "DROP TABLE IF EXISTS params; DROP TABLE IF EXISTS hashes; DROP TABLE IF EXISTS algorithms;";
130 | stmt.executeUpdate(sql_dropTables);
131 | String sql_createAlgoTable = "CREATE TABLE algorithms (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, Name TEXT NOT NULL)";
132 | stmt.executeUpdate(sql_createAlgoTable);
133 | String sql_createParamTable = "CREATE TABLE params (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, value TEXT NOT NULL)";
134 | stmt.executeUpdate(sql_createParamTable);
135 | String sql_createHashTable = "CREATE TABLE hashes (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, algorithmID INTEGER NOT NULL, paramID INTEGER, value TEXT NOT NULL)";
136 | stmt.executeUpdate(sql_createHashTable);
137 | stdOut.println(" + Adding " + config.hashAlgorithms.size() + " hash algorithms to DB.");
138 | Collections.reverse(config.hashAlgorithms); //so the db has ascending order
139 | String sql_insertAlgo = "INSERT OR REPLACE INTO algorithms(name, ID) VALUES (?, ?)";
140 | for (HashAlgorithm algo : config.hashAlgorithms)
141 | {
142 | pstmt = conn.prepareStatement(sql_insertAlgo);
143 | pstmt.setString(1, algo.name.text);
144 | pstmt.setString(2, Integer.toString(algo.id));
145 | pstmt.executeUpdate();
146 | stdOut.println(" + Adding Hash Algorithm to DB: " + algo.name.text + ", " + algo.id);
147 | }
148 | Collections.reverse(config.hashAlgorithms); //back to descending order for hash searching
149 | stdOut.println(moduleName + ": Database initialized.");
150 | return true;
151 | }
152 | catch (SQLException e)
153 | {
154 | stdErr.println(e.getMessage());
155 | return false;
156 | }
157 | catch (Exception ex)
158 | {
159 | stdErr.println(ex);
160 | return false;
161 | }
162 | }
163 |
164 | boolean saveParam(String paramValue)
165 | {
166 | int paramId = getParamId(paramValue);
167 | if (paramId > 0)
168 | {
169 | //if (config.debug) stdOut.println(moduleName + ": Not saving parameter (" + paramValue +") since it's already in the db at index = " + paramId);
170 | return false;
171 | }
172 | try
173 | {
174 | if (conn == null)
175 | {
176 | conn = getConnection();
177 | }
178 | String sql_insertParam = "INSERT OR REPLACE INTO params(value) VALUES (?)";
179 | pstmt = conn.prepareStatement(sql_insertParam);
180 | pstmt.setString(1, paramValue);
181 | pstmt.executeUpdate();
182 | stdOut.println(moduleName + ": Saving Discovered Parameter Value: " + paramValue);
183 | return true;
184 | }
185 | catch (SQLException e)
186 | {
187 | stdErr.println(e.getMessage());
188 | return false;
189 | }
190 | }
191 |
192 | int getParamId(String paramValue)
193 | {
194 | try
195 | {
196 | if (conn == null)
197 | {
198 | conn = getConnection();
199 | }
200 | String sql_paramExists = "SELECT * from params where value = ?";
201 | pstmt = conn.prepareStatement(sql_paramExists);
202 | pstmt.setString(1, paramValue);
203 | ResultSet rs = pstmt.executeQuery();
204 | if (!rs.next())
205 | {
206 | return 0;
207 | }
208 | int id = rs.getInt("id");
209 | if (config.debug) stdOut.println(moduleName + ": Found '" + paramValue + "' in the db at index=" + id);
210 | return id;
211 | }
212 | catch (SQLException e)
213 | {
214 | stdErr.println(moduleName + ": SQLException: " + e);
215 | return -1;
216 | }
217 | }
218 |
219 | String getParamByHash(HashRecord hash)
220 | {
221 | try
222 | {
223 | if (conn == null)
224 | {
225 | conn = getConnection();
226 | }
227 | String sql_paramExists = "select params.value from hashes inner join params on hashes.paramID=params.ID where hashes.algorithmid = ? and hashes.value = ?";
228 | pstmt = conn.prepareStatement(sql_paramExists);
229 | pstmt.setString(1, Integer.toString(hash.algorithm.id));
230 | pstmt.setString(2, hash.getNormalizedRecord());
231 | ResultSet rs = pstmt.executeQuery();
232 | if (!rs.next())
233 | {
234 | return null;
235 | }
236 | String paramValue = rs.getString("value");
237 | if (config.debug) stdOut.println(moduleName + ": Match '" + paramValue + "' for '" + hash.getNormalizedRecord() +"'");
238 | return paramValue;
239 | }
240 | catch (SQLException e)
241 | {
242 | stdErr.println(moduleName + ": SQLException: " + e);
243 | return null;
244 | }
245 | }
246 |
247 | boolean saveParamWithHash(ParameterWithHash parmWithHash)
248 | {
249 | int paramId = getParamId(parmWithHash.parameter.value);
250 | if (paramId <= 0)
251 | {
252 | if (config.debug) stdOut.println(moduleName + ": Cannot save hash " + parmWithHash.hashedValue + " until the following parameter is saved " + parmWithHash.parameter.value);
253 | saveParam(parmWithHash.parameter.value);
254 | paramId = getParamId(parmWithHash.parameter.value);
255 | }
256 | try
257 | {
258 | if (conn == null)
259 | {
260 | conn = getConnection();
261 | }
262 | int algorithmId = config.getHashId(parmWithHash.algorithm);
263 | if (algorithmId <= 0)
264 | {
265 | stdErr.println(moduleName + ": Could not locate Algorithm ID for " + parmWithHash.algorithm);
266 | return false;
267 | }
268 | String sql_insertHash = "INSERT OR REPLACE INTO hashes(algorithmID, paramID, value) VALUES (?, ?, ?)";
269 | pstmt = conn.prepareStatement(sql_insertHash);
270 | pstmt.setString(1, Integer.toString(algorithmId));
271 | pstmt.setString(2, Integer.toString(paramId));
272 | pstmt.setString(3, parmWithHash.hashedValue.toLowerCase());
273 | pstmt.executeUpdate();
274 | if (config.debug) stdOut.println(moduleName + ": Saved " + parmWithHash.algorithm.text + " hash in db: " + parmWithHash.parameter.value + ":" + parmWithHash.hashedValue);
275 | return true;
276 | }
277 | catch (SQLException e)
278 | {
279 | stdErr.println(moduleName + ": SQLException: " + e);
280 | return false;
281 | }
282 | }
283 |
284 | boolean saveHash(HashRecord hash)
285 | {
286 | if (getHashIdByValue(hash.getNormalizedRecord()) > 0)
287 | {
288 | //stdOut.println(moduleName + ": Not saving hash (" + hash.getNormalizedRecord() + ") since it's already in the db.");
289 | return false;
290 | }
291 | try
292 | {
293 | if (conn == null)
294 | {
295 | conn = getConnection();
296 | }
297 | String sql_insertHash = "INSERT OR REPLACE INTO hashes(algorithmID, value) VALUES (?, ?)";
298 | pstmt = conn.prepareStatement(sql_insertHash);
299 | pstmt.setString(1, Integer.toString(hash.algorithm.id));
300 | pstmt.setString(2, hash.getNormalizedRecord());
301 | pstmt.executeUpdate();
302 | stdOut.println(moduleName + ": Saving " + hash.algorithm.name.text + " hash of unknown source value in db: " + hash.getNormalizedRecord());
303 | return true;
304 | }
305 | catch (SQLException e)
306 | {
307 | stdErr.println(moduleName + ": SQLException: " + e);
308 | return false;
309 | }
310 | }
311 |
312 | int getHashIdByValue(String hashedValue)
313 | {
314 | try
315 | {
316 | if (conn == null)
317 | {
318 | conn = getConnection();
319 | }
320 | String sql_hashExists = "SELECT * from hashes where value = ?";
321 | pstmt = conn.prepareStatement(sql_hashExists);
322 | pstmt.setString(1, hashedValue);
323 | ResultSet rs = pstmt.executeQuery();
324 | if (!rs.next())
325 | {
326 | return 0;
327 | }
328 | int id = rs.getInt("id");
329 | if (config.debug) stdOut.println(moduleName + ": Found '" + hashedValue + "' in the db at index=" + id);
330 | return id;
331 | }
332 | catch (SQLException e)
333 | {
334 | stdErr.println(moduleName + ": SQLException: " + e);
335 | return -1;
336 | }
337 | }
338 |
339 | /**
340 | * TODO: verify presence of all tables? (params, hashes, etc.) < Yes please, but !MVP [TM]
341 | */
342 | boolean verify()
343 | {
344 | Statement stmt = null;
345 | ResultSet rs = null;
346 |
347 | try
348 | {
349 | if (conn == null)
350 | {
351 | conn = getConnection();
352 | }
353 | stmt = conn.createStatement();
354 | stmt.setQueryTimeout(30);
355 | String sql_tableCheck = "SELECT name FROM sqlite_master WHERE type='table' AND name='params';";
356 | rs = stmt.executeQuery(sql_tableCheck);
357 | boolean x = false;
358 | while (rs.next())
359 | {
360 | x = true;
361 | }
362 | return x;
363 | }
364 | catch (SQLException e)
365 | {
366 | stdErr.println(moduleName + ": SQLException: " + e);
367 | return false;
368 | }
369 | }
370 |
371 | /**
372 | * TODO: This is to return the list of parameters that were saved without HashAlgorithm.Name hashes.
373 | * In other words, pump this list out, hash them against 'algorithm' and save them back to the DB for
374 | * future comparisons.
375 | * @param algorithm
376 | * @return
377 | */
378 | public List getParamsWithoutHashType(HashAlgorithm algorithm)
379 | {
380 | List params = new ArrayList<>();
381 | //TODO: Need to fix this query - want to find all the params that don't have a hash table entry with algorithm ID matching the algorithm passed to this method:
382 | String sql_selectMissing = "select ID, VALUE from params where ID not in (select paramID from hashes where hashes.algorithmID = ?)";
383 | try
384 | {
385 | pstmt = conn.prepareStatement(sql_selectMissing);
386 | pstmt.setString(1, Integer.toString(algorithm.id));
387 | ResultSet rs = pstmt.executeQuery();
388 | while (rs.next())
389 | {
390 | //int paramId = rs.getInt("id");
391 | String value = rs.getString("value");
392 | params.add(value);
393 | }
394 | }
395 | catch (SQLException e)
396 | {
397 | stdErr.println(moduleName + ": SQL Exception: " + e);
398 | }
399 | return params;
400 | }
401 | }
--------------------------------------------------------------------------------
/src/burp/GuiTab.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.awt.Color;
4 | import java.awt.Component;
5 | import java.awt.Font;
6 | import java.awt.Point;
7 | import java.awt.event.ActionEvent;
8 | import java.io.File;
9 | import java.io.PrintWriter;
10 |
11 | import javax.swing.BorderFactory;
12 | import javax.swing.ButtonGroup;
13 | import javax.swing.GroupLayout;
14 | import javax.swing.GroupLayout.Alignment;
15 | import javax.swing.JButton;
16 | import javax.swing.JCheckBox;
17 | import javax.swing.JFileChooser;
18 | import javax.swing.JLabel;
19 | import javax.swing.JPanel;
20 | import javax.swing.JRadioButton;
21 | import javax.swing.JSeparator;
22 | import javax.swing.JTextField;
23 | import javax.swing.LayoutStyle.ComponentPlacement;
24 |
25 | /**
26 | * Creates a tab in the Burp GUI for changing settings.
27 | *
28 | * @author sjohnson
29 | */
30 | class GuiTab implements ITab {
31 | private BurpExtender burpHashScanner;
32 | private JButton btnReinitDatabase;
33 | private JButton btnResetDefaults;
34 | private JButton btnSelectFile;
35 | private final Color burpGrey = new Color(146, 151, 161);
36 | private final Color burpOrange = new Color(229, 137, 0);
37 | private ButtonGroup buttonGroup1;
38 | private IBurpExtenderCallbacks callbacks;
39 | private JCheckBox chkMd5;
40 | private JCheckBox chkSha1;
41 | private JCheckBox chkSha224;
42 | private JCheckBox chkSha256;
43 | private JCheckBox chkSha384;
44 | private JCheckBox chkSha512;
45 | private Config config;
46 | private Database db;
47 | private JSeparator jSeparator1;
48 | private JSeparator jSeparator2;
49 | private JLabel lblBehavior;
50 | private JLabel lblExtensionName;
51 | private JLabel lblSelectAlgorithm;
52 | private JLabel lblSelectFile;
53 | private JPanel pnlAlgorithm;
54 | private JPanel pnlBehavior;
55 | private JPanel pnlBorder;
56 | private JPanel pnlBottomButtons;
57 | private JPanel pnlMain;
58 | private JPanel pnlSelectFile;
59 | private JRadioButton rbMatch;
60 | private JRadioButton rbReport;
61 | private PrintWriter stdOut;
62 | private JTextField txtFileName;
63 |
64 | GuiTab(BurpExtender b) {
65 | burpHashScanner = b;
66 | callbacks = b.getCallbacks();
67 | config = b.getConfig();
68 | db = b.getDatabase();
69 | stdOut = b.getStdOut();
70 |
71 | initComponents();
72 | callbacks.customizeUiComponent(pnlMain);
73 | }
74 |
75 | private void btnReinitDatabaseActionPerformed(ActionEvent evt) {
76 | db.init();
77 | }
78 |
79 | private void btnResetDefaultsActionPerformed(ActionEvent evt) {
80 | config.reset();
81 | loadConfig();
82 | stdOut.println("Configuration reset to defaults.");
83 | }
84 |
85 | private void btnSelectFileActionPerformed(ActionEvent evt) {
86 | File dbFile = selectDatabaseFile();
87 | if (dbFile != null && !dbFile.getAbsolutePath().equals(config.databaseFilename)) {
88 | config.databaseFilename = dbFile.getAbsolutePath();
89 | config.save();
90 | txtFileName.setText(config.databaseFilename);
91 | db.changeFile();
92 | }
93 | }
94 |
95 | private void chkMd5ActionPerformed(ActionEvent evt) {
96 | config.toggleHashAlgorithm(HashAlgorithmName.MD5, !config.isHashEnabled(HashAlgorithmName.MD5));
97 | config.save();
98 | // burpExtender.loadHashAlgorithms();
99 | }
100 |
101 | private void chkSha1ActionPerformed(ActionEvent evt) {
102 | config.toggleHashAlgorithm(HashAlgorithmName.SHA_1, !config.isHashEnabled(HashAlgorithmName.SHA_1));
103 | config.save();
104 | // burpExtender.loadHashAlgorithms();
105 | }
106 |
107 | private void chkSha224ActionPerformed(ActionEvent evt) {
108 | config.toggleHashAlgorithm(HashAlgorithmName.SHA_224, !config.isHashEnabled(HashAlgorithmName.SHA_224));
109 | config.save();
110 | // burpExtender.loadHashAlgorithms();
111 | }
112 |
113 | private void chkSha256ActionPerformed(ActionEvent evt) {
114 | config.toggleHashAlgorithm(HashAlgorithmName.SHA_256, !config.isHashEnabled(HashAlgorithmName.SHA_256));
115 | config.save();
116 | // burpExtender.loadHashAlgorithms();
117 | }
118 |
119 | private void chkSha384ActionPerformed(ActionEvent evt) {
120 | config.toggleHashAlgorithm(HashAlgorithmName.SHA_384, !config.isHashEnabled(HashAlgorithmName.SHA_384));
121 | config.save();
122 | // burpExtender.loadHashAlgorithms();
123 | }
124 |
125 | private void chkSha512ActionPerformed(ActionEvent evt) {
126 | config.toggleHashAlgorithm(HashAlgorithmName.SHA_512, !config.isHashEnabled(HashAlgorithmName.SHA_512));
127 | config.save();
128 | // burpExtender.loadHashAlgorithms();
129 | }
130 |
131 | @Override
132 | public String getTabCaption() {
133 | return BurpExtender.extensionName;
134 | }
135 |
136 | @Override
137 | public Component getUiComponent() {
138 | return pnlMain;
139 | }
140 |
141 | private void initComponents() {
142 | buttonGroup1 = new ButtonGroup();
143 | pnlMain = new JPanel();
144 | pnlBorder = new JPanel();
145 | lblExtensionName = new JLabel();
146 | pnlAlgorithm = new JPanel();
147 | lblSelectAlgorithm = new JLabel();
148 | chkMd5 = new JCheckBox();
149 | chkSha1 = new JCheckBox();
150 | chkSha256 = new JCheckBox();
151 | chkSha512 = new JCheckBox();
152 | chkSha384 = new JCheckBox();
153 | chkSha224 = new JCheckBox();
154 | jSeparator1 = new JSeparator();
155 | pnlBehavior = new JPanel();
156 | rbReport = new JRadioButton();
157 | rbMatch = new JRadioButton();
158 | lblBehavior = new JLabel();
159 | jSeparator2 = new JSeparator();
160 | pnlSelectFile = new JPanel();
161 | btnSelectFile = new JButton();
162 | txtFileName = new JTextField();
163 | lblSelectFile = new JLabel();
164 | pnlBottomButtons = new JPanel();
165 | btnReinitDatabase = new JButton();
166 | btnResetDefaults = new JButton();
167 |
168 | loadConfig();
169 |
170 | pnlBorder.setBorder(BorderFactory.createLineBorder(burpGrey));
171 | pnlBorder.setLocation(new Point(1, 1));
172 |
173 | lblExtensionName.setFont(lblExtensionName.getFont().deriveFont(
174 | lblExtensionName.getFont().getStyle() | Font.BOLD, lblExtensionName.getFont().getSize() + 3));
175 | lblExtensionName.setForeground(burpOrange);
176 | lblExtensionName.setText(BurpExtender.extensionName);
177 |
178 | lblSelectAlgorithm.setText("Select hash algorithms to enable.");
179 |
180 | chkMd5.setText("MD5");
181 | chkMd5.addActionListener(this::chkMd5ActionPerformed);
182 |
183 | chkSha1.setText("SHA-1");
184 | chkSha1.addActionListener(this::chkSha1ActionPerformed);
185 |
186 | chkSha256.setText("SHA-256");
187 | chkSha256.addActionListener(this::chkSha256ActionPerformed);
188 |
189 | chkSha224.setText("SHA-224");
190 | chkSha224.addActionListener(this::chkSha224ActionPerformed);
191 |
192 | chkSha384.setText("SHA-384");
193 | chkSha384.addActionListener(this::chkSha384ActionPerformed);
194 |
195 | chkSha512.setText("SHA-512");
196 | chkSha512.addActionListener(this::chkSha512ActionPerformed);
197 |
198 | GroupLayout pnlAlgorithmLayout = new GroupLayout(pnlAlgorithm);
199 | pnlAlgorithm.setLayout(pnlAlgorithmLayout);
200 | pnlAlgorithmLayout
201 | .setHorizontalGroup(pnlAlgorithmLayout
202 | .createParallelGroup(Alignment.LEADING)
203 | .addGroup(
204 | pnlAlgorithmLayout
205 | .createSequentialGroup()
206 | .addContainerGap()
207 | .addGroup(
208 | pnlAlgorithmLayout
209 | .createParallelGroup(
210 | Alignment.LEADING)
211 | .addComponent(
212 | lblSelectAlgorithm)
213 | .addGroup(
214 | pnlAlgorithmLayout
215 | .createSequentialGroup()
216 | .addGroup(
217 | pnlAlgorithmLayout
218 | .createParallelGroup(
219 | Alignment.LEADING)
220 | .addComponent(
221 | chkSha256)
222 | .addComponent(
223 | chkSha1)
224 | .addComponent(
225 | chkMd5))
226 | .addGap(18,
227 | 18,
228 | 18)
229 | .addGroup(
230 | pnlAlgorithmLayout
231 | .createParallelGroup(
232 | Alignment.LEADING)
233 | .addComponent(
234 | chkSha224)
235 | .addComponent(
236 | chkSha384)
237 | .addComponent(
238 | chkSha512))))
239 | .addContainerGap(
240 | GroupLayout.DEFAULT_SIZE,
241 | Short.MAX_VALUE)));
242 | pnlAlgorithmLayout.setVerticalGroup(pnlAlgorithmLayout
243 | .createParallelGroup(Alignment.LEADING).addGroup(
244 | pnlAlgorithmLayout
245 | .createSequentialGroup()
246 | .addContainerGap()
247 | .addComponent(lblSelectAlgorithm)
248 | .addPreferredGap(ComponentPlacement.RELATED)
249 | .addGroup(
250 | pnlAlgorithmLayout
251 | .createParallelGroup(
252 | Alignment.BASELINE)
253 | .addComponent(chkMd5)
254 | .addComponent(chkSha224))
255 | .addPreferredGap(ComponentPlacement.RELATED)
256 | .addGroup(
257 | pnlAlgorithmLayout
258 | .createParallelGroup(
259 | Alignment.BASELINE)
260 | .addComponent(chkSha1)
261 | .addComponent(chkSha384))
262 | .addPreferredGap(ComponentPlacement.RELATED)
263 | .addGroup(
264 | pnlAlgorithmLayout
265 | .createParallelGroup(
266 | Alignment.BASELINE)
267 | .addComponent(chkSha256)
268 | .addComponent(chkSha512))
269 | .addContainerGap(GroupLayout.DEFAULT_SIZE,
270 | Short.MAX_VALUE)));
271 |
272 | buttonGroup1.add(rbReport);
273 | rbReport.setText("Report Only");
274 | rbReport.addActionListener(this::rbReportActionPerformed);
275 |
276 | buttonGroup1.add(rbMatch);
277 | rbMatch.setText("Match and Report Hashes");
278 | rbMatch.addActionListener(this::rbMatchActionPerformed);
279 |
280 | lblBehavior.setText("Select hashing behavior.");
281 |
282 | GroupLayout pnlBehaviorLayout = new GroupLayout(pnlBehavior);
283 | pnlBehavior.setLayout(pnlBehaviorLayout);
284 | pnlBehaviorLayout
285 | .setHorizontalGroup(pnlBehaviorLayout
286 | .createParallelGroup(Alignment.LEADING)
287 | .addGroup(
288 | pnlBehaviorLayout
289 | .createSequentialGroup()
290 | .addContainerGap()
291 | .addGroup(
292 | pnlBehaviorLayout
293 | .createParallelGroup(
294 | Alignment.LEADING)
295 | .addComponent(
296 | lblBehavior)
297 | .addGroup(
298 | pnlBehaviorLayout
299 | .createSequentialGroup()
300 | .addComponent(
301 | rbMatch)
302 | .addPreferredGap(
303 | ComponentPlacement.UNRELATED)
304 | .addComponent(
305 | rbReport)))
306 | .addContainerGap(
307 | GroupLayout.DEFAULT_SIZE,
308 | Short.MAX_VALUE)));
309 | pnlBehaviorLayout.setVerticalGroup(pnlBehaviorLayout
310 | .createParallelGroup(Alignment.LEADING).addGroup(
311 | Alignment.TRAILING,
312 | pnlBehaviorLayout
313 | .createSequentialGroup()
314 | .addContainerGap()
315 | .addComponent(lblBehavior)
316 | .addPreferredGap(ComponentPlacement.RELATED)
317 | .addGroup(
318 | pnlBehaviorLayout
319 | .createParallelGroup(
320 | Alignment.BASELINE)
321 | .addComponent(rbMatch)
322 | .addComponent(rbReport))
323 | .addContainerGap(GroupLayout.DEFAULT_SIZE,
324 | Short.MAX_VALUE)));
325 |
326 | btnSelectFile.setText("Select file ...");
327 | btnSelectFile.addActionListener(this::btnSelectFileActionPerformed);
328 |
329 | txtFileName.setEnabled(false);
330 | txtFileName.addActionListener(this::btnSelectFileActionPerformed);
331 |
332 | lblSelectFile
333 | .setText("Select the output file to which hashes and parameters will be saved.");
334 |
335 | GroupLayout pnlSelectFileLayout = new GroupLayout(pnlSelectFile);
336 | pnlSelectFile.setLayout(pnlSelectFileLayout);
337 | pnlSelectFileLayout
338 | .setHorizontalGroup(pnlSelectFileLayout
339 | .createParallelGroup(Alignment.LEADING)
340 | .addGroup(
341 | pnlSelectFileLayout
342 | .createSequentialGroup()
343 | .addContainerGap()
344 | .addGroup(
345 | pnlSelectFileLayout
346 | .createParallelGroup(
347 | Alignment.LEADING)
348 | .addGroup(
349 | pnlSelectFileLayout
350 | .createSequentialGroup()
351 | .addComponent(
352 | btnSelectFile)
353 | .addPreferredGap(
354 | ComponentPlacement.RELATED)
355 | .addComponent(
356 | txtFileName,
357 | GroupLayout.PREFERRED_SIZE,
358 | 320,
359 | GroupLayout.PREFERRED_SIZE))
360 | .addComponent(
361 | lblSelectFile))
362 | .addContainerGap(172, Short.MAX_VALUE)));
363 | pnlSelectFileLayout
364 | .setVerticalGroup(pnlSelectFileLayout
365 | .createParallelGroup(Alignment.LEADING)
366 | .addGroup(
367 | pnlSelectFileLayout
368 | .createSequentialGroup()
369 | .addContainerGap()
370 | .addComponent(lblSelectFile)
371 | .addPreferredGap(
372 | ComponentPlacement.RELATED)
373 | .addGroup(
374 | pnlSelectFileLayout
375 | .createParallelGroup(
376 | Alignment.BASELINE)
377 | .addComponent(
378 | btnSelectFile)
379 | .addComponent(
380 | txtFileName,
381 | GroupLayout.PREFERRED_SIZE,
382 | GroupLayout.DEFAULT_SIZE,
383 | GroupLayout.PREFERRED_SIZE))
384 | .addContainerGap(
385 | GroupLayout.DEFAULT_SIZE,
386 | Short.MAX_VALUE)));
387 |
388 | btnReinitDatabase.setText("Reinitialize Database");
389 | btnReinitDatabase.addActionListener(this::btnReinitDatabaseActionPerformed);
390 |
391 | btnResetDefaults.setText("Reset Defaults");
392 | btnResetDefaults.addActionListener(this::btnResetDefaultsActionPerformed);
393 |
394 | GroupLayout pnlBottomButtonsLayout = new GroupLayout(pnlBottomButtons);
395 | pnlBottomButtons.setLayout(pnlBottomButtonsLayout);
396 | pnlBottomButtonsLayout.setHorizontalGroup(pnlBottomButtonsLayout
397 | .createParallelGroup(Alignment.LEADING).addGroup(
398 | pnlBottomButtonsLayout
399 | .createSequentialGroup()
400 | .addContainerGap()
401 | .addComponent(btnResetDefaults)
402 | .addGap(18, 18, 18)
403 | .addComponent(btnReinitDatabase)
404 | .addContainerGap(GroupLayout.DEFAULT_SIZE,
405 | Short.MAX_VALUE)));
406 | pnlBottomButtonsLayout
407 | .setVerticalGroup(pnlBottomButtonsLayout
408 | .createParallelGroup(Alignment.LEADING)
409 | .addGroup(
410 | Alignment.TRAILING,
411 | pnlBottomButtonsLayout
412 | .createSequentialGroup()
413 | .addContainerGap(
414 | GroupLayout.DEFAULT_SIZE,
415 | Short.MAX_VALUE)
416 | .addGroup(
417 | pnlBottomButtonsLayout
418 | .createParallelGroup(
419 | Alignment.BASELINE)
420 | .addComponent(
421 | btnResetDefaults)
422 | .addComponent(
423 | btnReinitDatabase))
424 | .addContainerGap()));
425 |
426 | GroupLayout pnlBorderLayout = new GroupLayout(pnlBorder);
427 | pnlBorder.setLayout(pnlBorderLayout);
428 | pnlBorderLayout
429 | .setHorizontalGroup(pnlBorderLayout
430 | .createParallelGroup(Alignment.LEADING)
431 | .addGroup(
432 | Alignment.TRAILING,
433 | pnlBorderLayout
434 | .createSequentialGroup()
435 | .addContainerGap()
436 | .addGroup(
437 | pnlBorderLayout
438 | .createParallelGroup(
439 | Alignment.TRAILING)
440 | .addComponent(
441 | pnlBottomButtons,
442 | GroupLayout.DEFAULT_SIZE,
443 | GroupLayout.DEFAULT_SIZE,
444 | Short.MAX_VALUE)
445 | .addComponent(
446 | pnlAlgorithm,
447 | GroupLayout.DEFAULT_SIZE,
448 | GroupLayout.DEFAULT_SIZE,
449 | Short.MAX_VALUE)
450 | .addComponent(
451 | jSeparator1,
452 | Alignment.LEADING)
453 | .addComponent(
454 | pnlBehavior,
455 | GroupLayout.DEFAULT_SIZE,
456 | GroupLayout.DEFAULT_SIZE,
457 | Short.MAX_VALUE)
458 | .addComponent(
459 | jSeparator2,
460 | Alignment.LEADING)
461 | .addComponent(
462 | pnlSelectFile,
463 | Alignment.LEADING,
464 | GroupLayout.DEFAULT_SIZE,
465 | GroupLayout.DEFAULT_SIZE,
466 | Short.MAX_VALUE)
467 | .addGroup(
468 | Alignment.LEADING,
469 | pnlBorderLayout
470 | .createSequentialGroup()
471 | .addComponent(
472 | lblExtensionName)
473 | .addGap(0,
474 | 0,
475 | Short.MAX_VALUE)))
476 | .addContainerGap()));
477 | pnlBorderLayout.setVerticalGroup(pnlBorderLayout.createParallelGroup(
478 | Alignment.LEADING).addGroup(
479 | pnlBorderLayout
480 | .createSequentialGroup()
481 | .addContainerGap()
482 | .addComponent(lblExtensionName)
483 | .addGap(18, 18, 18)
484 | .addComponent(pnlAlgorithm, GroupLayout.PREFERRED_SIZE,
485 | GroupLayout.DEFAULT_SIZE,
486 | GroupLayout.PREFERRED_SIZE)
487 | .addPreferredGap(ComponentPlacement.RELATED)
488 | .addComponent(jSeparator1, GroupLayout.PREFERRED_SIZE,
489 | GroupLayout.DEFAULT_SIZE,
490 | GroupLayout.PREFERRED_SIZE)
491 | .addPreferredGap(ComponentPlacement.RELATED)
492 | .addComponent(pnlBehavior, GroupLayout.PREFERRED_SIZE,
493 | GroupLayout.DEFAULT_SIZE,
494 | GroupLayout.PREFERRED_SIZE)
495 | .addPreferredGap(ComponentPlacement.RELATED)
496 | .addComponent(jSeparator2, GroupLayout.PREFERRED_SIZE,
497 | GroupLayout.DEFAULT_SIZE,
498 | GroupLayout.PREFERRED_SIZE)
499 | .addPreferredGap(ComponentPlacement.RELATED)
500 | .addComponent(pnlSelectFile,
501 | GroupLayout.PREFERRED_SIZE,
502 | GroupLayout.DEFAULT_SIZE,
503 | GroupLayout.PREFERRED_SIZE)
504 | .addPreferredGap(ComponentPlacement.RELATED, 60,
505 | Short.MAX_VALUE)
506 | .addComponent(pnlBottomButtons,
507 | GroupLayout.PREFERRED_SIZE,
508 | GroupLayout.DEFAULT_SIZE,
509 | GroupLayout.PREFERRED_SIZE).addContainerGap()));
510 |
511 | GroupLayout pnlMainLayout = new GroupLayout(pnlMain);
512 | pnlMain.setLayout(pnlMainLayout);
513 | pnlMainLayout.setHorizontalGroup(pnlMainLayout.createParallelGroup(
514 | Alignment.LEADING).addGroup(
515 | pnlMainLayout
516 | .createSequentialGroup()
517 | .addContainerGap()
518 | .addComponent(pnlBorder, GroupLayout.DEFAULT_SIZE,
519 | GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
520 | .addContainerGap()));
521 | pnlMainLayout.setVerticalGroup(pnlMainLayout.createParallelGroup(
522 | Alignment.LEADING).addGroup(
523 | pnlMainLayout
524 | .createSequentialGroup()
525 | .addContainerGap()
526 | .addComponent(pnlBorder, GroupLayout.DEFAULT_SIZE,
527 | GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
528 | .addContainerGap()));
529 |
530 | }
531 |
532 | private void loadConfig() {
533 | chkMd5.setSelected(config.isHashEnabled(HashAlgorithmName.MD5));
534 | chkSha1.setSelected(config.isHashEnabled(HashAlgorithmName.SHA_1));
535 | chkSha224.setSelected(config.isHashEnabled(HashAlgorithmName.SHA_224));
536 | chkSha256.setSelected(config.isHashEnabled(HashAlgorithmName.SHA_256));
537 | chkSha384.setSelected(config.isHashEnabled(HashAlgorithmName.SHA_384));
538 | chkSha512.setSelected(config.isHashEnabled(HashAlgorithmName.SHA_512));
539 | rbMatch.setSelected(!config.reportHashesOnly);
540 | rbReport.setSelected(config.reportHashesOnly);
541 | txtFileName.setText(config.databaseFilename);
542 | }
543 |
544 | private void rbMatchActionPerformed(ActionEvent evt) {
545 | config.reportHashesOnly = false;
546 | config.save();
547 | }
548 |
549 | private void rbReportActionPerformed(ActionEvent evt) {
550 | config.reportHashesOnly = true;
551 | config.save();
552 | }
553 |
554 | private File selectDatabaseFile() {
555 | JFileChooser fc = new JFileChooser();
556 | fc.setSelectedFile(new File(config.databaseFilename));
557 | if (fc.showOpenDialog(pnlMain) == JFileChooser.APPROVE_OPTION) {
558 | return fc.getSelectedFile();
559 | }
560 | return null;
561 | }
562 | }
563 |
--------------------------------------------------------------------------------
/src/burp/BurpExtender.java:
--------------------------------------------------------------------------------
1 | package burp;
2 |
3 | import java.io.PrintWriter;
4 | import java.io.UnsupportedEncodingException;
5 | import java.net.URL;
6 | import java.nio.charset.StandardCharsets;
7 | import java.security.NoSuchAlgorithmException;
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.Base64;
11 | import java.util.List;
12 | import java.util.regex.MatchResult;
13 | import java.util.regex.Matcher;
14 | import java.util.regex.Pattern;
15 | import java.net.URLDecoder;
16 |
17 | /**
18 | * This is the "main" class of the extension. Burp begins by
19 | * calling {@link BurpExtender#registerExtenderCallbacks(IBurpExtenderCallbacks)}.
20 | */
21 | public class BurpExtender implements IBurpExtender, IScannerCheck
22 | {
23 | static final String extensionName = "burp-hash";
24 | static final String moduleName = "Scanner";
25 | static final String extensionUrl = "https://burp-hash.github.io/";
26 | public Pattern b64Regex = Pattern.compile("(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?");
27 | public Pattern emailRegex = Pattern.compile("[^=\"&;:\\s]*[a-zA-Z0-9-_\\.]+@[a-zA-Z0-9-\\.]+.[a-zA-Z]+");
28 | public Pattern ccRegex = Pattern.compile("[0-9]{4}[-]*[0-9]{4}[-]*[0-9]{4}[-]*[0-9]{4}");
29 | private IBurpExtenderCallbacks callbacks;
30 | private Config config;
31 | private Database db;
32 | private GuiTab guiTab;
33 | private List hashes = new ArrayList<>();
34 | private List issues = new ArrayList<>();
35 | private IExtensionHelpers helpers;
36 | private PrintWriter stdErr;
37 | private PrintWriter stdOut;
38 |
39 | @Override
40 | public void registerExtenderCallbacks(final IBurpExtenderCallbacks c)
41 | {
42 | callbacks = c;
43 | helpers = callbacks.getHelpers();
44 | stdErr = new PrintWriter(callbacks.getStderr(), true);
45 | stdOut = new PrintWriter(callbacks.getStdout(), true);
46 |
47 | callbacks.setExtensionName(extensionName);
48 | callbacks.registerScannerCheck(this); // register with Burp as a scanner
49 |
50 | loadConfig();
51 | loadDatabase();
52 | loadGui();
53 | }
54 |
55 | @Override
56 | public int consolidateDuplicateIssues(IScanIssue existingIssue, IScanIssue newIssue)
57 | {
58 | //TODO: determine if we want to remove dupes or not
59 | //TODO: determine if we need better dupe comparisons
60 | // return 0;
61 | if (existingIssue.getIssueDetail().equals(newIssue.getIssueDetail())) {
62 | return -1; // discard new issue
63 | } else {
64 | return 0; // use both issues
65 | }
66 | }
67 |
68 | /**
69 | * Active Scanning is not implemented with this plugin.
70 | */
71 | @Override
72 | public List doActiveScan(IHttpRequestResponse baseRequestResponse, IScannerInsertionPoint insertionPoint)
73 | {
74 | return null; // doActiveScan is required but not used
75 | }
76 |
77 | /**
78 | * Implements the main entry point to Burp's Extension API for Passive Scanning.
79 | * Algorithm:
80 | * - Grab the request/response
81 | * - Locate and save all parameters
82 | * - Hash parameters against configured and observed hash functions
83 | * - Locate any hashes and match against pre-computed parameters' hashes
84 | * - If any new hash algorithm types are observed, go back and check previously saved parameters
85 | */
86 | @Override
87 | public List doPassiveScan(IHttpRequestResponse baseRequestResponse)
88 | {
89 | URL url = helpers.analyzeRequest(baseRequestResponse).getUrl();
90 | if (!callbacks.isInScope(url))
91 | {
92 | // only scan in-scope URLs for performance reasons
93 | return null;
94 | }
95 | stdOut.println("Scanner: Begin passive scanning: " + url + "\n...");
96 | if (config.reportHashesOnly && config.debug) stdOut.println(moduleName + ": reporting observed hashes only, hashing parameters is disabled.");
97 |
98 | //First locate params and generate hashes (if enabled)
99 | if (!config.reportHashesOnly)
100 | {
101 | //TODO: something in here may be generating duplicate hashes in memory (not in sqlite)
102 | // the dupe is redundant for matching hashes to params:
103 | hashNewParameters(findNewParameters(baseRequestResponse));
104 | }
105 |
106 | //Observe hashes in request/response
107 | hashes = new ArrayList<>();
108 | issues = new ArrayList<>();
109 | findHashes(baseRequestResponse, SearchType.REQUEST);
110 | findHashes(baseRequestResponse, SearchType.RESPONSE);
111 |
112 | //Note any discoveries and create burp issues
113 | List discoveredHashIssues = createHashDiscoveredIssues(baseRequestResponse);
114 | discoveredHashIssues = sortIssues(discoveredHashIssues);
115 | if (discoveredHashIssues.size() > 0)
116 | {
117 | stdOut.println(moduleName + ": Added " + discoveredHashIssues.size() + " 'Hash Discovered' issues.");
118 | }
119 |
120 | List matchedHashIssues = matchParamsToHashes(baseRequestResponse);
121 | matchedHashIssues = sortIssues(matchedHashIssues);
122 | if (!matchedHashIssues.isEmpty())
123 | {
124 | stdOut.println(moduleName + ": Added " + matchedHashIssues.size() + " 'Hash Matched' issues.");
125 | }
126 | issues.addAll(matchedHashIssues);
127 | issues.addAll(discoveredHashIssues);
128 | return issues;
129 | }
130 |
131 | protected List
- findNewParameters(IHttpRequestResponse baseRequestResponse)
132 | {
133 | List
- items = new ArrayList<>();
134 | IRequestInfo req = helpers.analyzeRequest(baseRequestResponse);
135 | if (req != null)
136 | {
137 | items.addAll(saveHeaders(req.getHeaders()));
138 | for (IParameter param : req.getParameters())
139 | {
140 | //TODO: Consider hashing the parameter with any hash algorithms missing from DB
141 | if (config.debug) stdOut.println(moduleName + ": Found Request Parameter: '" + param.getName() + "':'" + param.getValue() + "'");
142 | if (db.saveParam(param.getValue()))
143 | {
144 | items.add(new Item(param));
145 | }
146 | try
147 | {
148 | String urldecoded = URLDecoder.decode(param.getValue(), "UTF-8");
149 | if (!urldecoded.equals(param.getValue()))
150 | {
151 | if (config.debug) stdOut.println(moduleName + ": Found UrlDecoded Request Parameter: '" + param.getName() + "':'" + urldecoded + "'");
152 | if (db.saveParam(urldecoded))
153 | {
154 | Item i = new Item(param);
155 | i.setValue(urldecoded);
156 | items.add(i);
157 | }
158 | }
159 | } catch (UnsupportedEncodingException e)
160 | {
161 | e.printStackTrace();
162 | }
163 | }
164 | String wholeRequest = new String(baseRequestResponse.getRequest(), StandardCharsets.UTF_8);
165 | items.addAll(saveNewValueParams(findEmailRegex(wholeRequest)));
166 | items.addAll(saveNewValueParams(findParamsInJson(baseRequestResponse)));
167 | try
168 | {
169 | String urlDecodedWholeRequest = URLDecoder.decode(wholeRequest, StandardCharsets.UTF_8.toString());
170 | items.addAll(saveNewValueParams(findEmailRegex(urlDecodedWholeRequest)));
171 | }
172 | catch (UnsupportedEncodingException e)
173 | {
174 | if (config.debug) stdOut.println(moduleName + ": encoding exception: " + e);
175 | }
176 | }
177 | IResponseInfo resp = helpers.analyzeResponse(baseRequestResponse.getResponse());
178 | if (resp != null)
179 | {
180 | items.addAll(saveHeaders(resp.getHeaders()));
181 | for (IParameter cookie : getCookieItems(resp.getCookies()))
182 | {
183 | if (config.debug) stdOut.println(moduleName + ": Found Response Cookie: '" + cookie.getName() + "':'" + cookie.getValue() + "'");
184 | if (db.saveParam(cookie.getName())) //check the cookie name
185 | {
186 | items.add(new Item(cookie));
187 | }
188 | if (db.saveParam(cookie.getValue())) //as well as its value
189 | {
190 | items.add(new Item(cookie));
191 | }
192 | }
193 | //TODO: Find params in html body response via common regexes (email, user ID, credit card, etc.)
194 | String wholeResponse = new String(baseRequestResponse.getResponse(), StandardCharsets.UTF_8);
195 | items.addAll(saveNewValueParams(findEmailRegex(wholeResponse)));
196 | items.addAll(saveNewValueParams(findParamsInJson(baseRequestResponse)));
197 | // if (config.debug) stdOut.println("Items stored: " + items.size());
198 | }
199 | return items;
200 | }
201 |
202 | protected List
- saveHeaders(List headers)
203 | {
204 | //TODO: Find cookies from request
205 | //TODO: Find params in request headers
206 | List
- items = new ArrayList<>();
207 | for (String header : headers)
208 | {
209 | // if (config.debug) stdOut.println(moduleName + ": header = " + header);
210 | if (header.startsWith("Date:") || header.startsWith("Content-Length:"))
211 | {
212 | //Don't want to fill the db with server response time stamps and content length headers
213 | continue;
214 | }
215 | if (db.saveParam(header))
216 | {
217 | items.add(new Item(header)); //save and hash entire header
218 | }
219 | }
220 | return items;
221 | }
222 |
223 | protected List
- saveNewValueParams(List
- items)
224 | {
225 | List
- savedItems = new ArrayList<>();
226 | for (Item item : items)
227 | {
228 | if (db.saveParam(item.getValue()))
229 | {
230 | savedItems.add(item);
231 | }
232 | }
233 | return savedItems;
234 | }
235 |
236 | protected List
- findEmailRegex(String msg)
237 | {
238 | List
- items = new ArrayList<>();
239 | Matcher matcher = emailRegex.matcher(msg);
240 | while (matcher.find())
241 | {
242 | String email = matcher.group();
243 | if (email.contains("&"))
244 | {
245 | email = email.split("&")[0];
246 | }
247 | if (config.debug) stdOut.println(moduleName + ": Found Email by Regex: " + email);
248 | items.add(new Item(email));
249 | }
250 | return items;
251 | }
252 |
253 | protected List
- findParamsInJson(IHttpRequestResponse msg)
254 | {
255 | byte[] body;
256 | List headers;
257 | boolean isJson;
258 | List
- items = new ArrayList<>();
259 | final String jsonRegex = "^content-type:.*json.*$";
260 | // TODO: add support for number values to kvRegex and supporting code below
261 | final String kvRegex = "(?:\"([^\"]+)\"\\s*|\'([^\']+)\')\\s*:\\s*(?:\"([^\"]+)\"\\s*|\'([^\']+)\')";
262 | Matcher matcher;
263 | Pattern patJson = Pattern.compile(jsonRegex, Pattern.CASE_INSENSITIVE);
264 | Pattern patKeyValue = Pattern.compile(kvRegex);
265 |
266 | // search the request
267 | byte[] req = msg.getRequest();
268 | IRequestInfo reqInfo = helpers.analyzeRequest(req);
269 | headers = reqInfo.getHeaders();
270 | isJson = false;
271 | for (String header : headers) {
272 | if (patJson.matcher(header).matches()) {
273 | isJson = true;
274 | break;
275 | }
276 | }
277 | if (isJson) {
278 | body = Arrays.copyOfRange(req, reqInfo.getBodyOffset(), req.length);
279 | // "body" should contain some sort of JSON at this point
280 | matcher = patKeyValue.matcher(new String(body, StandardCharsets.UTF_8));
281 | while (matcher.find()) {
282 | String key;
283 | String value;
284 | if (matcher.group(1) == null) {
285 | if (matcher.group(2) == null) {
286 | break;
287 | } else {
288 | key = matcher.group(2);
289 | }
290 | } else {
291 | key = matcher.group(1);
292 | }
293 | if (matcher.group(3) == null) {
294 | if (matcher.group(4) == null) {
295 | break;
296 | } else {
297 | value = matcher.group(4);
298 | }
299 | } else {
300 | value = matcher.group(3);
301 | }
302 | //stdOut.println("Key: "+key+" ||| value: "+value);
303 | items.add(new Item(value));
304 | }
305 | }
306 |
307 | // search the response
308 | byte[] resp = msg.getResponse();
309 | IResponseInfo respInfo = helpers.analyzeResponse(resp);
310 | headers = respInfo.getHeaders();
311 | isJson = false;
312 | for (String header : headers) {
313 | if (patJson.matcher(header).matches()) {
314 | isJson = true;
315 | break;
316 | }
317 | }
318 | if (isJson) {
319 | body = Arrays.copyOfRange(resp, respInfo.getBodyOffset(), resp.length);
320 | // "body" should contain some sort of JSON at this point
321 | matcher = patKeyValue.matcher(new String(body, StandardCharsets.UTF_8));
322 | while (matcher.find()) {
323 | String key;
324 | String value;
325 | if (matcher.group(1) == null) {
326 | if (matcher.group(2) == null) {
327 | break;
328 | } else {
329 | key = matcher.group(2);
330 | }
331 | } else {
332 | key = matcher.group(1);
333 | }
334 | if (matcher.group(3) == null) {
335 | if (matcher.group(4) == null) {
336 | break;
337 | } else {
338 | value = matcher.group(4);
339 | }
340 | } else {
341 | value = matcher.group(3);
342 | }
343 | //stdOut.println("Key: "+key+" ||| value: "+value);
344 | items.add(new Item(value));
345 | }
346 | }
347 | return items;
348 | }
349 |
350 | protected List hashNewParameters(List
- items)
351 | {
352 | List parameters = new ArrayList<>();
353 | for(Item item : items)
354 | {
355 | //TODO: validate this works:
356 | /*if (isItemAHash(item))
357 | {
358 | continue; // don't rehash the hashes
359 | //but probably want to add them to the parameter DB at some point
360 | }*/
361 | Parameter param = new Parameter();
362 | param.name = item.getName();
363 | param.value = item.getValue();
364 | for (HashAlgorithm algorithm : config.hashAlgorithms)
365 | {
366 | if (!algorithm.enabled)
367 | {
368 | if (config.debug) stdOut.println(moduleName + ": " + algorithm.name.text + " disabled.");
369 | continue;
370 | }
371 | try
372 | {
373 | ParameterWithHash paramWithHash = new ParameterWithHash();
374 | paramWithHash.parameter = param;
375 | paramWithHash.algorithm = algorithm.name;
376 | paramWithHash.hashedValue = HashEngine.Hash(param.value, algorithm.name);
377 | if (db.saveParamWithHash(paramWithHash))
378 | {
379 | if (config.debug) stdOut.println(moduleName + ": " + algorithm.name.text + " saved hash for: " + param.value + " hash=" + paramWithHash.hashedValue);
380 | continue;
381 | }
382 | if (config.debug) stdOut.println(moduleName + ": " + algorithm.name.text + " hash already in db (" + paramWithHash.hashedValue + ")");
383 | }
384 | catch (Exception e)
385 | {
386 | stdOut.println(moduleName + ": " + e);
387 | }
388 | }
389 | parameters.add(param);
390 | }
391 | return parameters;
392 | }
393 |
394 | protected void findHashes(IHttpRequestResponse baseRequestResponse, SearchType searchType)
395 | {
396 | String s;
397 | if (searchType.equals(SearchType.REQUEST))
398 | {
399 | s = new String(baseRequestResponse.getRequest(), StandardCharsets.UTF_8);
400 | }
401 | else
402 | {
403 | s = new String(baseRequestResponse.getResponse(), StandardCharsets.UTF_8);
404 | }
405 | for(HashAlgorithm hashAlgorithm : config.hashAlgorithms)
406 | {
407 | if (config.debug) stdOut.println(moduleName + ": Searching for " + hashAlgorithm.name.text + " hashes.");
408 | findHashRegex(s, hashAlgorithm.pattern, hashAlgorithm);
409 | for(HashRecord hash : hashes)
410 | {
411 | if (hash.reported)
412 | {
413 | continue;
414 | }
415 | hash.reported = true;
416 | hash.searchType = searchType;
417 | stdOut.println(moduleName + ": Found " + hashAlgorithm.name.text + " hash in " + searchType + ": " + hash.record);
418 | //TODO: same hash string with different marker values gets lost
419 | // ^ No longer believe this is true, need to test. [TM]
420 | db.saveHash(hash);
421 | if (!hashAlgorithm.enabled)
422 | {
423 | config.toggleHashAlgorithm(hashAlgorithm.name, true);
424 | if (config.debug) stdOut.println(moduleName + ": Dynamic hash detection enabled " + hashAlgorithm.name.text + ".");
425 | rehashSavedParameters(hashAlgorithm);
426 | }
427 | break; //to avoid a false 'match' with a shorter hash algorithm
428 | }
429 | }
430 | }
431 |
432 | private void rehashSavedParameters(HashAlgorithm algorithm)
433 | {
434 | List paramsWithoutNewHash = db.getParamsWithoutHashType(algorithm);
435 | if (config.debug) stdOut.println(moduleName + ": Preparing to update " + paramsWithoutNewHash.size() +
436 | " parameters with " + algorithm.name.text + " hashes...");
437 | for (String param : paramsWithoutNewHash)
438 | {
439 | try
440 | {
441 | HashRecord hash = new HashRecord();
442 | hash.algorithm = algorithm;
443 | hash.record = HashEngine.Hash(param, algorithm.name);
444 | db.saveHash(hash);
445 | }
446 | catch (NoSuchAlgorithmException e)
447 | {
448 | stdErr.println(moduleName + ": " + e);
449 | }
450 | }
451 | }
452 |
453 | protected List createHashDiscoveredIssues(IHttpRequestResponse baseRequestResponse)
454 | {
455 | List issues = new ArrayList<>();
456 | if (baseRequestResponse == null)
457 | {
458 | throw new IllegalArgumentException(moduleName + ": base request/response object cannot be null.");
459 | }
460 | for(HashRecord hash : hashes)
461 | {
462 | IHttpRequestResponse[] message;
463 | if (hash.searchType.equals(SearchType.REQUEST))
464 | { //apply markers to the request
465 | message = new IHttpRequestResponse[] { callbacks.applyMarkers(baseRequestResponse, hash.markers, null) };
466 | }
467 | else
468 | { //apply markers to the response
469 | message = new IHttpRequestResponse[] { callbacks.applyMarkers(baseRequestResponse, null, hash.markers) };
470 | }
471 | HashDiscoveredIssueText issueText = new HashDiscoveredIssueText(hash);
472 | Issue issue = new Issue(
473 | baseRequestResponse.getHttpService(),
474 | helpers.analyzeRequest(baseRequestResponse).getUrl(),
475 | message,
476 | issueText.Name,
477 | issueText.Details,
478 | issueText.Severity,
479 | issueText.Confidence,
480 | issueText.RemediationDetails,
481 | issueText.Background,
482 | issueText.RemediationBackground);
483 | issues.add(issue);
484 | }
485 | return issues;
486 | }
487 |
488 | protected List matchParamsToHashes(IHttpRequestResponse baseRequestResponse)
489 | {
490 | if (config.debug) stdOut.println(moduleName + ": Matching Params to " + hashes.size() + " observed hashes.");
491 | List issues = new ArrayList<>();
492 | for(HashRecord hash : hashes)
493 | {
494 | String paramValue = db.getParamByHash(hash);
495 | if (paramValue != null)
496 | {
497 | stdOut.println(moduleName + ": " + hash.algorithm.name.text + " ***HASH MATCH*** for parameter'" + paramValue + "' = '" + hash.getNormalizedRecord() + "'");
498 | IHttpRequestResponse[] message;
499 | if (hash.searchType.equals(SearchType.REQUEST))
500 | { //apply markers to the request
501 | message = new IHttpRequestResponse[] { callbacks.applyMarkers(baseRequestResponse, hash.markers, null) };
502 | }
503 | else
504 | { //apply markers to the response
505 | message = new IHttpRequestResponse[] { callbacks.applyMarkers(baseRequestResponse, null, hash.markers) };
506 | }
507 |
508 | HashMatchesIssueText issueText = new HashMatchesIssueText(hash, paramValue);
509 | Issue issue = new Issue(
510 | baseRequestResponse.getHttpService(),
511 | helpers.analyzeRequest(baseRequestResponse).getUrl(),
512 | message,
513 | issueText.Name,
514 | issueText.Details,
515 | issueText.Severity,
516 | issueText.Confidence,
517 | issueText.RemediationDetails,
518 | issueText.Background,
519 | issueText.RemediationBackground);
520 | issues.add(issue);
521 | }
522 | else
523 | {
524 | if (config.debug) stdOut.println(moduleName + ": Did not find plaintext match for " + hash.algorithm.name.text + " hash: '" + hash.getNormalizedRecord() + "'");
525 | }
526 | }
527 | return issues;
528 | }
529 |
530 | protected boolean isDupeHash(HashRecord hash)
531 | {
532 | //if the markers show this starts at the same spot on the same request/response object
533 | //and the longer record includes the shorter record, this is either an exact dupe or
534 | //a larger hash (e.g. SHA-512) mistaken for a shorter hash (e.g. SHA-256)
535 | for (HashRecord h : hashes)
536 | {
537 | if (h.markers.get(0).equals(hash.markers.get(0)))
538 | {
539 | if (h.record.length() > hash.record.length())
540 | {
541 | if (h.record.startsWith(hash.record))
542 | {
543 | return true;
544 | }
545 | }
546 | else
547 | {
548 | if (hash.record.startsWith(h.record))
549 | {
550 | return true;
551 | }
552 | }
553 | }
554 | }
555 | return false;
556 | }
557 |
558 | protected void findHashRegex(String s, Pattern pattern, HashAlgorithm algorithm)
559 | {
560 | //TODO: Add support for f0:a3:cd style encoding (!MVP)
561 | //TODO: Add support for 0xFF style encoding (!MVP)
562 | //TODO: Consider adding support for double-url encoded values (!MVP)
563 | Matcher matcher = pattern.matcher(s);
564 | // search for hashes in raw request/response
565 | while (matcher.find())
566 | {
567 | String result = matcher.group();
568 | //enforce char length of the match here, rather than regex which has false positives
569 | if (result.length() != algorithm.charWidth)
570 | {
571 | continue;
572 | }
573 | if (matcher.end() + 1 < s.length())
574 | {
575 | String nextChars = s.substring(matcher.end(), matcher.end() + 1);
576 | Matcher next = pattern.matcher(nextChars);
577 | //stdOut.println("Next: '" + nextChars + "' pattern: " + next.pattern().toString());
578 | if (next.find())
579 | {
580 | //stdOut.println("the next char is also [a-fA-F0-9] so this is a false positive");
581 | continue;
582 | }
583 | }
584 | HashRecord hash = new HashRecord();
585 | hash.markers.add(new int[] { matcher.start(), matcher.end() });
586 | hash.record = matcher.group();
587 | hash.algorithm = algorithm;
588 | hash.encodingType = EncodingType.Hex;
589 | hash.sortMarkers();
590 | if (!isDupeHash(hash))
591 | {
592 | hashes.add(hash);
593 | }
594 | }
595 | findB64HashRegex(s, pattern, algorithm);
596 |
597 | //TODO: this url decoding will probably throw the match markers off.
598 | // Oh well. Fix it later. Better to have markers off than miss the hashes.
599 | String urldecoded = "";
600 | try
601 | {
602 | urldecoded = URLDecoder.decode(s, "UTF-8");
603 | }
604 | catch (UnsupportedEncodingException e)
605 | {
606 | // TODO Auto-generated catch block
607 | }
608 | findB64HashRegex(urldecoded, pattern, algorithm);
609 | }
610 |
611 | protected void findB64HashRegex(String s, Pattern pattern, HashAlgorithm algorithm)
612 | {
613 | Matcher matcher = pattern.matcher(s);
614 | // search for Base64-encoded data
615 | Matcher b64matcher = b64Regex.matcher(s);
616 | while (b64matcher.find())
617 | {
618 | String b64EncodedHash = b64matcher.group();
619 | // save some cycles
620 | if (b64EncodedHash.isEmpty() || b64EncodedHash.length() < 16)
621 | {
622 | continue;
623 | }
624 | //stdOut.println("B64: " + b64EncodedHash);
625 | try
626 | {
627 | // find base64-encoded hex strings representing hashes
628 | byte[] byteHash = Base64.getDecoder().decode(b64EncodedHash);
629 | String strHash = new String(byteHash, StandardCharsets.UTF_8);
630 | stdOut.println("B64 hex string: " + strHash);
631 | matcher = pattern.matcher(strHash);
632 | //enforce char width here to prevent smaller hashes from false positives with larger hashes:
633 | if (matcher.matches() && matcher.group().length() == algorithm.charWidth)
634 | {
635 | stdOut.println(moduleName + ": Base64 Match: " + b64EncodedHash + " <<" + strHash + ">>");
636 | HashRecord hash = new HashRecord();
637 | int i = s.indexOf(b64EncodedHash);
638 | hash.markers.add(new int[] { i, (i + b64EncodedHash.length()) });
639 | hash.record = b64EncodedHash;
640 | hash.algorithm = algorithm;
641 | hash.encodingType = EncodingType.StringBase64;
642 | hash.sortMarkers();
643 | if (!isDupeHash(hash))
644 | {
645 | hashes.add(hash);
646 | }
647 | }
648 |
649 | // find base64-encoded raw hashes
650 | String hexHash = Utilities.byteArrayToHex(Base64.getDecoder().decode(b64EncodedHash));
651 | stdOut.println("B64 raw hash: " + hexHash);
652 | matcher = pattern.matcher(hexHash);
653 |
654 | if (!matcher.matches())
655 | {
656 | try
657 | {
658 | String urldecoded = URLDecoder.decode(strHash, "UTF-8");
659 | if (!urldecoded.equals(hexHash))
660 | {
661 | if (config.debug) stdOut.println(moduleName + ": Detected URL Encoded Base 64 parameter: " + hexHash);
662 | matcher = pattern.matcher(urldecoded);
663 | //TODO: this will probably throw the match markers off. Oh well. Fix it later.
664 | }
665 | }
666 | catch (UnsupportedEncodingException e)
667 | {
668 | // TODO Auto-generated catch block
669 | }
670 | }
671 |
672 | //enforce char width here to prevent smaller hashes from false positives with larger hashes:
673 | if (matcher.matches() && matcher.group().length() == algorithm.charWidth)
674 | {
675 | stdOut.println(moduleName + ": Base64 Match: " + b64EncodedHash + " <<" + hexHash + ">>");
676 | HashRecord hash = new HashRecord();
677 | int i = s.indexOf(b64EncodedHash);
678 | hash.markers.add(new int[] { i, (i + b64EncodedHash.length()) });
679 | hash.record = b64EncodedHash;
680 | hash.algorithm = algorithm;
681 | hash.encodingType = EncodingType.Base64;
682 | hash.sortMarkers();
683 | if (!isDupeHash(hash))
684 | {
685 | hashes.add(hash);
686 | }
687 | }
688 | }
689 | catch (IllegalArgumentException e)
690 | {
691 | stdErr.println(e);
692 | }
693 | }
694 | }
695 |
696 | IBurpExtenderCallbacks getCallbacks() {
697 | return callbacks;
698 | }
699 |
700 | Config getConfig() {
701 | return config;
702 | }
703 |
704 | protected List
- getCookieItems(List cookies)
705 | {
706 | List
- items = new ArrayList<>();
707 | for (ICookie cookie : cookies)
708 | {
709 | items.add(new Item(cookie));
710 | }
711 | return items;
712 | }
713 |
714 | Database getDatabase() {
715 | return db;
716 | }
717 |
718 | PrintWriter getStdErr() {
719 | return stdErr;
720 | }
721 |
722 | PrintWriter getStdOut() {
723 | return stdOut;
724 | }
725 |
726 | protected boolean isItemAHash(Item item)
727 | {
728 | //TODO: implement a check to see if the item is already a hash
729 | for(HashRecord hash : hashes)
730 | {
731 | if (hash.record == item.getValue())
732 | return true;
733 | }
734 | return false;
735 | }
736 |
737 | //TODO: add method to (re)build hashAlgorithms on config change
738 | private void loadConfig()
739 | {
740 | try
741 | {
742 | config = Config.load(this); // load configuration
743 | }
744 | catch (Exception e)
745 | {
746 | stdErr.println(moduleName + ": Error loading config: " + e);
747 | e.printStackTrace(stdErr);
748 | return;
749 | }
750 | if (config.hashAlgorithms != null || !config.hashAlgorithms.isEmpty())
751 | {
752 | //stdOut.println(moduleName + ": Succesfully loaded hash algorithm configuration.");
753 | }
754 | }
755 |
756 | /**
757 | * SQLite
758 | * TODO: load db on demand, close when not in use
759 | * TODO: save only when asked by user? (!MVP)
760 | */
761 | private void loadDatabase() {
762 | db = new Database(this);
763 | if (!db.verify()) {
764 | db.init();
765 | if (!db.verify()) {
766 | stdErr.println(moduleName + ": Unable to initialize database.");
767 | } else {
768 | stdOut.println(moduleName + ": Database verified.");
769 | }
770 | } else {
771 | stdOut.println(moduleName + ": Database verified.");
772 | }
773 | //db.close();
774 | }
775 |
776 | private void loadGui() {
777 | guiTab = new GuiTab(this);
778 | callbacks.addSuiteTab(guiTab);
779 | }
780 |
781 | private List sortIssues(List issues)
782 | {
783 | List sorted = new ArrayList<>();
784 | IScanIssue previous = null;
785 | for (IScanIssue issue : issues)
786 | {
787 | if (previous == null)
788 | {
789 | previous = issue;
790 | sorted.add(issue);
791 | continue;
792 | }
793 | boolean unique = true;
794 | for (IScanIssue i : sorted)
795 | {
796 | if (i.getIssueDetail().equals(issue.getIssueDetail()))
797 | {
798 | unique = false;
799 | break;
800 | }
801 | }
802 | if (unique)
803 | {
804 | sorted.add(issue);
805 | }
806 | }
807 | return sorted;
808 | }
809 | }
810 |
--------------------------------------------------------------------------------