/* * XSSCRLF.java *------------------------------------------------------------------------------ * TODO: * - Add ability to detect stored XSS; * - Add POST parameters to the ones being tested; *------------------------------------------------------------------------------ * */ package org.owasp.webscarab.plugin.xsscrlf; import java.io.File; import java.io.IOException; import java.io.FileReader; import java.util.logging.Logger; import java.io.BufferedReader; import org.owasp.webscarab.model.ConversationID; import org.owasp.webscarab.model.Request; import org.owasp.webscarab.model.HttpUrl; import org.owasp.webscarab.model.Response; import org.owasp.webscarab.model.StoreException; import org.owasp.webscarab.plugin.Framework; import org.owasp.webscarab.plugin.Hook; import org.owasp.webscarab.plugin.Plugin; import org.owasp.webscarab.httpclient.FetcherQueue; import org.owasp.webscarab.httpclient.ConversationHandler; import org.owasp.webscarab.plugin.xsscrlf.XSSCRLFModel; import org.owasp.webscarab.model.NamedValue; import org.owasp.webscarab.util.Encoding; import java.net.MalformedURLException; /** * * @author meder */ public class XSSCRLF implements Plugin, ConversationHandler { private Framework _framework; private XSSCRLFModel _model; private Logger _logger = Logger.getLogger(getClass().getName()); private FetcherQueue _fetcherQueue = null; private int _threads = 4; private int _delay = 100; public static int MINLENGTH=3; /** Creates a new instance of XSSCRLF */ public XSSCRLF(Framework framework) { _framework = framework; _model = new XSSCRLFModel(framework.getModel()); } public void analyse(ConversationID id, Request request, Response response, String origin) { HttpUrl url = request.getURL(); if (_framework.getModel().getConversationOrigin(id).equals(getPluginName())) return; // is this something we should check? String contentType = response.getHeader("Content-Type"); if (contentType == null) return; if (!contentType.matches("text/.*") && !contentType.equals("application/x-javascript")) return; byte[] responseContent = response.getContent(); if ((responseContent == null || responseContent.length == 0) && !response.getStatus().startsWith("3")) return; // prepare the response body, and headers String responseBody = null; if (responseContent != null) responseBody = new String(responseContent).toUpperCase(); NamedValue[] headers = response.getHeaders(); NamedValue[] ucHeaders = new NamedValue[headers.length]; for (int i=0; i<headers.length; i++) { ucHeaders[i] = new NamedValue(headers[i].getName().toUpperCase(), headers[i].getValue().toUpperCase()); } String queryString = request.getURL().getQuery(); if (queryString != null && queryString.length() > 0) { NamedValue[] params = NamedValue.splitNamedValues(queryString, "&", "="); checkParams(id, url, params, "GET", ucHeaders, responseBody); } if (request.getMethod().equals("POST")) { contentType = request.getHeader("Content-Type"); if ("application/x-www-form-urlencoded".equals(contentType)) { byte[] requestContent = request.getContent(); if (requestContent != null && requestContent.length>0) { String requestBody = new String(requestContent); NamedValue[] params = NamedValue.splitNamedValues(requestBody, "&", "="); checkParams(id, url, params, "POST", ucHeaders, responseBody); } } } } private void checkParams(ConversationID id, HttpUrl url, NamedValue[] params, String paramLocation, NamedValue[] headers, String body) { if (params == null) return; for (int i=0; i<params.length; i++) { String value = params[i].getValue().toUpperCase(); if (value.length() >= MINLENGTH) { if (isInHeaders(value, headers)) { _model.markAsCRLFSuspicious(id, url, paramLocation, params[i].getName()); } if (body != null && body.indexOf(value) > -1) { _model.markAsXSSSuspicious(id, url, paramLocation, params[i].getName()); } } } } /** * Checks headers (header name and header value) for the presence of the expression. * @param expression the expression to look for. * @param headers the array of header name/value pairs. */ private boolean isInHeaders(String expression, NamedValue[] headers) { if (expression.length() < MINLENGTH) return false; expression = Encoding.urlDecode(expression.toUpperCase()); for (int i=0; i < headers.length; i++) { if (headers[i].getValue().toUpperCase().indexOf(expression) != -1 || headers[i].getName().toUpperCase().indexOf(expression) != -1) return true; } return false; } public void flush() throws StoreException { } public String getPluginName() { return "XSS/CRLF"; } public Object getScriptableObject() { return null; } public Hook[] getScriptingHooks() { return new Hook[0]; } public String getStatus() { return _model.getStatus(); } public boolean isBusy() { return _model.isBusy(); } public boolean isModified() { return _model.isModified(); } public boolean isRunning() { return _model.isRunning(); } public void run() { Request req; _model.setRunning(true); _model.setStatus("Started"); _model.setStopping(false); // start the fetchers _fetcherQueue = new FetcherQueue(getPluginName(), this, _threads, _delay); _model.setRunning(true); while (!_model.isStopping()) { req = _model.dequeueRequest(); if (req != null) { _fetcherQueue.submit(req); } } _model.setRunning(false); _model.setStatus("Stopped"); } public void responseReceived(Response response) { String body = new String(response.getContent()); ConversationID id = null; if (body != null && body.length() >= _model.getXSSTestString().length() && body.indexOf(_model.getXSSTestString()) != -1) { _logger.info("XSS - Possibly Vulnerable: " + response.getRequest().getURL()); id = _framework.addConversation(response.getRequest(), response, getPluginName()); _model.setXSSVulnerable(id, response.getRequest().getURL()); } if (response.getHeader(_model.getCRLFInjectedHeader()) != null) { _logger.info("CRFL - Possibly Vulnerable: " + response.getRequest().getURL()); if (id == null) id = _framework.addConversation(response.getRequest(), response, getPluginName()); _model.setCRLFVulnerable(id, response.getRequest().getURL()); } } public void requestError(Request request, IOException ioe) { } public void setSession(String type, Object store, String session) throws StoreException { } public boolean stop() { _model.setRunning(false); return _model.isRunning(); } public XSSCRLFModel getModel() { return _model; } public void stopChecks() { // Stop checks, let the other thread return ASAP System.out.println("stopChecks()"); } public synchronized String loadString(File file) throws IOException { StringBuffer buf = new StringBuffer(); String line; BufferedReader input = new BufferedReader(new FileReader(file)); while ((line = input.readLine()) != null) { buf.append(line); } return buf.toString(); } public void checkSelected (ConversationID []ids) { Request req; for (int j=0; j < ids.length; j++) { req = _model.getRequest(ids[j]); checkConversation(ids[j], req, "GET"); checkConversation(ids[j], req, "POST"); } } private void checkConversation(ConversationID id, Request req, String where) { String[] params = _model.getCRLFSuspiciousParameters(id, where); if (params != null && params.length>0) { for (int i=0; i<params.length; i++) { _logger.info("Testing for CRLF - Conversation ID: "+id+" Parameter:" + params[i]); submitCRLFTest(req, where, params[i]); } } params = _model.getXSSSuspiciousParameters(id, where); if (params != null && params.length>0) { for (int i=0; i<params.length; i++) { _logger.info("Testing for XSS - Conversation ID: "+id+" Parameter:" + params[i]); submitXSSTest(req, "GET", params[i]); } } } private void submitXSSTest(Request origReq, String where, String param) { String testString = Encoding.urlEncode(_model.getXSSTestString()); Request req = new Request(origReq); req.setURL(getURLwithTestString(req.getURL(), param, testString)); _model.enqueueRequest(req, param); } private void submitCRLFTest(Request origReq, String where, String param) { String testString = _model.getCRLFTestString(); Request req = new Request(origReq); req.setURL(getURLwithTestString(req.getURL(), param, testString)); _model.enqueueRequest(req, param); } private HttpUrl getURLwithTestString(HttpUrl url, String name, String value) { StringBuffer buf = new StringBuffer("?"); String querystring = url.getQuery(); if (querystring == null) return null; NamedValue[] params = NamedValue.splitNamedValues(querystring, "&", "="); for (int i=0; i < params.length; i++) { if (params[i].getName().equals(name)) { buf.append(params[i].getName() + "=" + value); } else { buf.append(params[i].getName() + "=" + params[i].getValue()); } if (i < params.length-1) buf.append("&"); } try { return new HttpUrl(url.getSHPP() + buf.toString()); } catch (MalformedURLException e) { _logger.info("Exception: "+e); return null; } } }