Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pull back Portswigger changes #6

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
23 changes: 23 additions & 0 deletions BappDescription.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<p>This extension tests applications for the Web Cache Deception vulnerability.</p>

<p>It adds a new Active Scanner check. Additionally, a context menu item is available to perform a targeted test.</p>

<p><b>Vulnerability</b></p>

<p>In February 2017, security researcher Omer Gil unveiled a new attack vector dubbed <a href="https://omergil.blogspot.co.il/2017/02/web-cache-deception-attack.html">Web Cache Deception</a>.</p>

<p>The Web Cache Deception attack could be devastating in consequences, but is very simple to execute:</p>

<ol>
<li>Attacker coerces victim to open a link on the valid application server containing the payload.</li>
<li>Attacker opens newly cached page on the server using the same link, to see the exact same page as the victim.</li>
</ol>

<p>The attack depends on a very specific set of circumstances to make the application vulnerable:</p>

<ol>
<li>The application only reads the first part of the URL to determine the resource to return, e.g. <i>/my_profile</i> can be accessed as <i>/my_profile_test</i>.</li>
<li>The application stack caches resources according to their file extensions, rather than by cache header values, e.g. <i>/my_profile.jpg</i> is cached.</li>
</ol>

<p><a href="https://www.trustwave.com/Resources/SpiderLabs-Blog/Airachnid--Web-Cache-Deception-Burp-Extender/">Read more</a></p>
12 changes: 12 additions & 0 deletions BappManifest.bmf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Uuid: 7c1ca94a61474d9e897d307c858d52f0
ExtensionType: 1
Name: Web Cache Deception Scanner
RepoName: web-cache-deception-scanner
ScreenVersion: 1.1
SerialVersion: 3
MinPlatformVersion: 0
ProOnly: True
Author: Johan Snyman, Trustwave
ShortDescription: Detect web cache misconfigurations with Burp.
EntryPoint: build/libs/web-cache-deception-scanner-all.jar
BuildCommand: gradle fatJar
Binary file removed WebCacheDeceptionScanner.jar
Binary file not shown.
16 changes: 16 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apply plugin: 'java'

repositories {
mavenCentral()
}

dependencies {
compile 'net.portswigger.burp.extender:burp-extender-api:1.7.22'
compile 'org.apache.commons:commons-lang3:3.5'
}

task fatJar(type: Jar) {
baseName = project.name + '-all'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
Binary file removed lib/.DS_Store
Binary file not shown.
Binary file removed lib/burp.jar
Binary file not shown.
Binary file removed lib/commons-lang3-3.5.jar
Binary file not shown.
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = 'web-cache-deception-scanner'
68 changes: 30 additions & 38 deletions src/main/java/burp/BurpExtender.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.PrintStream;
import java.util.*;
import java.util.concurrent.*;

Expand Down Expand Up @@ -42,20 +43,10 @@ public void registerExtenderCallbacks(IBurpExtenderCallbacks iBurpExtenderCallba
helpers = iBurpExtenderCallbacks.getHelpers();
}

private List<IScanIssue> runScannerForRequest(IHttpRequestResponse iHttpRequestResponse) {
private void runScannerForRequest(IHttpRequestResponse iHttpRequestResponse) {
print("runScannerForRequest");
ExecutorService service = Executors.newFixedThreadPool(1);
Future<List<IScanIssue>> task = service.submit(new ScannerThread(iHttpRequestResponse));
List<IScanIssue> result = null;

try {
result = task.get();
} catch(final InterruptedException ex) {
ex.printStackTrace();
} catch(final ExecutionException ex) {
ex.printStackTrace();
}

return result;
service.execute(new ScannerThread(iHttpRequestResponse));
}

/**
Expand Down Expand Up @@ -94,11 +85,7 @@ class MenuItemListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
for (IHttpRequestResponse message : arr) {
List<IScanIssue> result = runScannerForRequest(message);

if (result != null) {
callbacks.addScanIssue(result.get(0));
}
runScannerForRequest(message);
}
}
}
Expand All @@ -113,7 +100,8 @@ public List<IScanIssue> doPassiveScan(IHttpRequestResponse iHttpRequestResponse)

@Override
public List<IScanIssue> doActiveScan(IHttpRequestResponse iHttpRequestResponse, IScannerInsertionPoint iScannerInsertionPoint) {
return runScannerForRequest(iHttpRequestResponse);
runScannerForRequest(iHttpRequestResponse);
return new ArrayList<>();
}

@Override
Expand All @@ -123,42 +111,46 @@ public int consolidateDuplicateIssues(IScanIssue existingIssue, IScanIssue newIs
}
}

class ScannerThread implements Callable<List<IScanIssue>> {
class ScannerThread implements Runnable {

private List<IScanIssue> result = null;
private IHttpRequestResponse reqRes;

ScannerThread(IHttpRequestResponse reqRes) {
this.reqRes = reqRes;
}

public List<IScanIssue> call() {
// Test One: Does appending to the URL return a similar response.
if (RequestSender.initialTest(reqRes)) {
Set<String> fileTypesCached = new HashSet<>();
@Override
public void run() {
try {
print("run");

// Test two: Check if caching is done by file type
for (String ext : RequestSender.INITIAL_TEST_EXTENSIONS) {
if (RequestSender.getFileTypeCached(reqRes, ext)) {
fileTypesCached.add(ext);
}
}
// Test One: Does appending to the URL return a similar response.
if (RequestSender.initialTest(reqRes)) {
Set<String> fileTypesCached = new HashSet<>();

if (fileTypesCached.size() != 0) {
for (String ext : RequestSender.OTHER_TEST_EXTENSIONS) {
// Test two: Check if caching is done by file type
for (String ext : RequestSender.INITIAL_TEST_EXTENSIONS) {
if (RequestSender.getFileTypeCached(reqRes, ext)) {
fileTypesCached.add(ext);
}
}

WebCacheIssue issue = new WebCacheIssue(reqRes);
issue.setVulnerableExtensions(fileTypesCached);
if (fileTypesCached.size() > 0) {
for (String ext : RequestSender.OTHER_TEST_EXTENSIONS) {
if (RequestSender.getFileTypeCached(reqRes, ext)) {
fileTypesCached.add(ext);
}
}

return Arrays.asList(issue);
WebCacheIssue issue = new WebCacheIssue(reqRes);
issue.setVulnerableExtensions(fileTypesCached);
callbacks.addScanIssue(issue);
}
}
}

return result;
} catch (Throwable t) {
t.printStackTrace(new PrintStream(callbacks.getStderr()));
}
}
}
}
48 changes: 24 additions & 24 deletions src/main/java/burp/RequestSender.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ class RequestSender {
private final static String WORD = "test";

protected final static String[] INITIAL_TEST_EXTENSIONS = {"css", "jpg", "js"};
protected final static String[] OTHER_TEST_EXTENSIONS = {"html", "htm", "gif", "png", "cgi", "pl", "java", "class",
"php", "php3", "shtm", "shtml", "cfm", "cfml", "doc", "log", "txt", "csv", "ppt", "m4a", "mid", "mp3", "flv",
"m4v", "mov", "tif", "svg", "pdf", "xls", "sql", "bat", "exe", "jsp", "asp", "aspx", "jpeg"};
protected final static String[] OTHER_TEST_EXTENSIONS = {"html", "gif", "png", "svg", "php", "txt", "pdf", "jsp", "asp"};
// , "php3", "shtm", "shtml", "cfm", "cfml", "doc", "log", "csv", "ppt", "m4a", "mid", "mp3", "flv",
// "m4v", "mov", "tif", "xls", "sql", "bat", "exe",, "aspx", "jpeg", "htm", "cgi", "pl", "java", "class"};

private static byte[] orgResponse;

Expand All @@ -29,47 +29,48 @@ class RequestSender {
* @return
*/
protected static boolean initialTest(IHttpRequestResponse message) {
// Send the original request again
// Send the original request again, to get the response
byte[] orgRequest = buildHttpRequest(message, null, null, true);
orgResponse = retrieveResponseBody(message.getHttpService(), orgRequest, true);
orgResponse = retrieveResponseBody(message.getHttpService(), orgRequest);

// Send an unauthenticated - to root out fp's. Unauthenticated should not be the same as original
byte[] unAuthedRequest = buildHttpRequest(message, null, null, false);
byte[] unAuthedResponse = retrieveResponseBody(message.getHttpService(), unAuthedRequest, false);
byte[] unAuthedResponse = retrieveResponseBody(message.getHttpService(), unAuthedRequest);

// Test that the original request and an unauthenticated request do not get the same response.
// Same here is similar, according to the thresholds set
boolean authed = testSimilar(new String(orgResponse), new String(unAuthedResponse));
if (authed) {
BurpExtender.print("Request not vulnerable");
boolean unauthed = testSimilar(new String(orgResponse), new String(unAuthedResponse));
if (unauthed) {
BurpExtender.print("Request not vulnerable, no auth: ");
return false;
}

// Send with /test appended, check that everything after is ignored
byte[] testRequest = buildHttpRequest(message, WORD, null, true);
byte[] testResponse = retrieveResponseBody(message.getHttpService(), testRequest, true);
byte[] testResponse = retrieveResponseBody(message.getHttpService(), testRequest);

boolean append = testSimilar(new String(orgResponse), new String(testResponse));
if (!append) {
BurpExtender.print("Request not vulnerable.");
BurpExtender.print("Request not vulnerable, appending to end not similar.");
}
return append;
}

protected static boolean getFileTypeCached(IHttpRequestResponse message, String extension) {
protected static boolean getFileTypeCached(IHttpRequestResponse message, String ext) {
// Send with extension, potentially create cached version of resource
byte[] extRequest = buildHttpRequest(message, WORD, extension, true);
byte[] extResponse = retrieveResponseBody(message.getHttpService(), extRequest, false);
byte[] extRequest = buildHttpRequest(message, WORD, ext, true);
byte[] extResponse = retrieveResponseBody(message.getHttpService(), extRequest);

// Send an unauthenticated, test if vulnerable
byte[] vulRequest = buildHttpRequest(message, WORD, extension, false);
byte[] vulResponse = retrieveResponseBody(message.getHttpService(), vulRequest, false);
byte[] vulRequest = buildHttpRequest(message, WORD, ext, false);
byte[] vulResponse = retrieveResponseBody(message.getHttpService(), vulRequest);

boolean eq = testSimilar(new String(extResponse), new String(vulResponse)); //= new String(extResponse).equals(new String(vulResponse));
boolean eq = testSimilar(new String(extResponse), new String(vulResponse));
if (!eq) {
BurpExtender.print("Request is not vulnerable.");
BurpExtender.print(ext + " cached adding extension to list");
} else {
BurpExtender.print("Request is not vulnerable, no caching.");
}

return eq;
}

Expand Down Expand Up @@ -132,14 +133,14 @@ private static byte[] buildHttpRequest(final IHttpRequestResponse reqRes, final
return result;
}

private static byte[] retrieveResponseBody(IHttpService service, byte[] request, boolean checkResponseCode) {
private static byte[] retrieveResponseBody(IHttpService service, byte[] request) {
byte[] result = null;

IHttpRequestResponse test = BurpExtender.getCallbacks().makeHttpRequest(service, request);
byte[] res = test.getResponse();
IResponseInfo responseInfo = BurpExtender.getHelpers().analyzeResponse(res);

if (checkResponseCode && responseInfo.getStatusCode() == 200) {
if (responseInfo.getBodyOffset() > 0) {
int len = res.length - responseInfo.getBodyOffset();
result = new byte[len];
System.arraycopy(res, responseInfo.getBodyOffset(), result, 0, len);
Expand Down Expand Up @@ -196,8 +197,7 @@ private static String createNewUrl(String url, String additional, String extensi
}

/**
* Testing if the responses of two requests are similar. This is the not the same as the same, rather there is a
* threshold set in the static parameters of the class.
* Testing if the responses of two requests are similar. This is the not the same as equals, but below a static threshold
* @param firstString
* @param secondString
* @return Test if similar
Expand All @@ -208,7 +208,7 @@ private static boolean testSimilar(String firstString, String secondString) {
int levenDist = StringUtils.getLevenshteinDistance(firstString, secondString);

// BurpExtender.print("============================================");
// BurpExtender.print("Fuzzy Distance:" + fuzzyDist);
// BurpExtender.print(" Fuzzy Distance:" + fuzzyDist);
// BurpExtender.print(" Jaro Winkler Distance:" + jaroDist);
// BurpExtender.print(" Levenshtein Distance:" + levenDist);
// BurpExtender.print("============================================");
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/burp/WebCacheIssue.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,20 @@ public String getIssueDetail() {
sb.append("The attack is similar to Cross-site Request Forgery (CSRF) in that the user must be coerced ");
sb.append("into clicking on/submitting a malformed link.<br/><br/>");
sb.append("In order to be vulnerable two conditions must be met:<br/>");
sb.append("<ol><li>The applications response remains the same when a request has appended characters forming ");
sb.append("<ol><li>The application's response remains the same when a request has appended characters forming ");
sb.append("an additional extension at the end of a URL. E.g. http://www.example.com/account.jsp and ");
sb.append("http://www.example.com/account.jsp/test.jpg return the same valid response.</li>");
sb.append("<li>Caching of files is performed by file extension as opposed to caching headers.</li></ol>");
sb.append("<br/>");
sb.append("Caching is a technique to speed up the response times of web applications. This is done by ");
sb.append("storing copies of static files at locations from where they can be retrieved when needed. An ");
sb.append("applications caching can also be configured to cache files by file type, rather than by the ");
sb.append("application's caching can also be configured to cache files by file type, rather than by the ");
sb.append("preferred caching header values as is sometimes the case with reverse proxies. The intention ");
sb.append("of the caching is to present a cached copy of the requested resource, without passing the ");
sb.append("request back to the application server. This relieves the application server of some load and ");
sb.append("lets it get on with preparing the dynamic pages that need to be returned. This works well ");
sb.append("when we request http://www.example.com/images/test.jpg but what if we could get the reverse proxy ");
sb.append("to cache http://www.example.com/account.jsp where a users account details are displayed?<br/><br/>");
sb.append("to cache http://www.example.com/account.jsp where a user's account details are displayed?<br/><br/>");
sb.append("Omer demonstrated was that this is possible if we find a situation where ");
sb.append("http://http://www.example.com/account.jsp returns the same response as ");
sb.append("http://www.example.com/account.jsp/test.jpg<br/><br/>");
Expand All @@ -80,7 +80,7 @@ public String getIssueDetail() {
sb.append("User opens link --> Account Page details returned --> Reverse proxy caches \"account.jsp/test.jpg\"<br/>");
sb.append("Attacker --> Views http://www.example.com/account.jsp/test.jpg<br/><br/>");

sb.append("URL's that can be used for caching deception:");
sb.append("URLs that can be used for caching deception:");
sb.append("<ul>");

for (String ext : extensions) {
Expand Down