├── 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

"; 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 | --------------------------------------------------------------------------------