package burp.zn.csrf;
import burp.*;
import burp.zn.dirbuster.DirbusterHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CSRFTokenScanner implements IScannerCheck {
private static final Logger log = LogManager.getLogger(DirbusterHandler.class.getName());
private static final Pattern CONTENT_HTML = Pattern.compile("Content-Type: text/html", Pattern.CASE_INSENSITIVE);
private static final Pattern FORM_PATTERN = Pattern.compile("<form.+class=\"form-submit\".+>", Pattern.CASE_INSENSITIVE);
private static final Pattern CSRF_TOKEN_PATTERN = Pattern.compile("csrfToken=", Pattern.CASE_INSENSITIVE);
private final byte[] htmlFormPattern;
private final IExtensionHelpers helpers;
private final IBurpExtenderCallbacks callbacks;
public CSRFTokenScanner(IBurpExtenderCallbacks callbacks) {
this.callbacks = callbacks;
this.helpers = callbacks.getHelpers();
this.htmlFormPattern = helpers.stringToBytes("<form");
}
@Override
public List<IScanIssue> doPassiveScan(IHttpRequestResponse requestResponse) {
return doActiveScan(requestResponse, null);
}
@Override
public List<IScanIssue> doActiveScan(IHttpRequestResponse requestResponse, IScannerInsertionPoint insertionPoint) {
byte[] responseBytes = requestResponse.getResponse();
String responseString = helpers.bytesToString(responseBytes);
IResponseInfo responseInfo = helpers.analyzeResponse(responseBytes);
List<String> responseHeaders = responseInfo.getHeaders();
log.info("Trying to find Token form in: " + helpers.analyzeRequest(requestResponse).getUrl());
/**
* Check if content type is text/html
*/
boolean isHTML = responseHeaders.stream()
.filter(s -> CONTENT_HTML.matcher(s).find())
.findFirst()
.isPresent();
if (!isHTML) {
return null;
}
/**
* Check if html body contains form tags
*/
int formTagOffset = helpers.indexOf(responseBytes, htmlFormPattern, false, 0, responseBytes.length);
if (formTagOffset == -1) {
return null;
}
/**
* Start finding matched regions
*/
List<int[]> matchedRegions = new ArrayList<>();
Matcher matcher = FORM_PATTERN.matcher(responseString);
while (matcher.find()) {
int from = matcher.start();
int to = matcher.end();
if (!CSRF_TOKEN_PATTERN.matcher(responseString.substring(from, to)).find()) {
matchedRegions.add(new int[]{from, to});
}
}
if (matchedRegions.isEmpty()) {
return null;
}
/**
* Apply found markers for response
*/
log.info("Found form without token: " + helpers.analyzeRequest(requestResponse).getUrl());
return new ArrayList<IScanIssue>() {{
add(new CSRFTokenScanIssue(callbacks, callbacks.applyMarkers(requestResponse, null, matchedRegions)));
}};
}
@Override
public int consolidateDuplicateIssues(IScanIssue existingIssue, IScanIssue newIssue) {
if (Objects.equals(existingIssue.getIssueDetail(), newIssue.getIssueDetail()) &&
existingIssue.getIssueType() == newIssue.getIssueType() &&
existingIssue.getUrl().equals(newIssue.getUrl())) return -1;
else return 1;
}
}