Package org.parosproxy.paros.core.scanner

Source Code of org.parosproxy.paros.core.scanner.Analyser

/*
*
* Paros and its related class files.
*
* Paros is an HTTP/HTTPS proxy for assessing web application security.
* Copyright (C) 2003-2004 Chinotec Technologies Company
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Clarified Artistic License
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* Clarified Artistic License for more details.
*
* You should have received a copy of the Clarified Artistic License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
package org.parosproxy.paros.core.scanner;

import java.io.IOException;
import java.util.Random;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.parosproxy.paros.model.HistoryReference;
import org.parosproxy.paros.model.SiteNode;
import org.parosproxy.paros.network.HttpHeader;
import org.parosproxy.paros.network.HttpMessage;
import org.parosproxy.paros.network.HttpSender;
import org.parosproxy.paros.network.HttpStatusCode;

public class Analyser {

  /** remove HTML HEAD as this may contain expiry time which dynamic changes */
  private static final String p_REMOVE_HEADER = "(?m)(?i)(?s)<HEAD>.*?</HEAD>";
  private static final Pattern patternNotFound = Pattern.compile(
      "(\\bnot\\b(found|exist))|(\\b404\\berror\\b)|(\\berror\\b404\\b)",
      Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

  private static Random staticRandomGenerator = new Random();
  private static final String[] staticSuffixList = {
    ".cfm",
    ".jsp",
    ".php",
    ".asp",
    ".aspx",
    ".dll",
    ".exe",
    ".pl"
  };

  private HttpSender httpSender = null;
  private TreeMap<String, SampleResponse> mapVisited = new TreeMap<String, SampleResponse>();
  private boolean isSkipOrStop = false;

  public Analyser() {

  }

  public Analyser(HttpSender httpSender) {
    this.httpSender = httpSender;
  }

  public boolean isSkipOrStop() {
    return isSkipOrStop;
  }
 
  public void skipOrStop() {
    isSkipOrStop = true;
  }

  public void start(SiteNode node) {
    inOrderAnalyse(node);
  }

  private void addAnalysedHost(URI uri, HttpMessage msg, int errorIndicator) {
    mapVisited.put(uri.toString(), new SampleResponse(msg, errorIndicator));
  }

  /**
   * Analyse a single folder entity. Results are stored into
   * mAnalysedEntityTable.
   */
  private void analyse(SiteNode node) throws Exception {

    // if analysed already, return;
    // move to host part
    if (node.getHistoryReference() == null) {
      return;
    }

    HttpMessage baseMsg = (HttpMessage) node.getHistoryReference().getHttpMessage();
    URI baseUri = (URI) baseMsg.getRequestHeader().getURI().clone();

    baseUri.setQuery(null);
    // System.out.println("analysing: " + baseUri.toString());

    // already exist one. no need to test
    if (mapVisited.get(baseUri.toString()) != null) {
      return;
    }

    String path = getRandomPathSuffix(node, baseUri);
    HttpMessage msg = baseMsg.cloneRequest();

    URI uri = (URI) baseUri.clone();
    uri.setPath(path);
    msg.getRequestHeader().setURI(uri);
    // System.out.println("analysing 2: " + uri);

    sendAndReceive(msg);

    // standard RFC response, no further check is needed

    if (msg.getResponseHeader().getStatusCode() == HttpStatusCode.NOT_FOUND) {
      addAnalysedHost(baseUri, msg, SampleResponse.ERROR_PAGE_RFC);
      return;
    }

    if (HttpStatusCode.isRedirection(msg.getResponseHeader().getStatusCode())) {
      addAnalysedHost(baseUri, msg, SampleResponse.ERROR_PAGE_REDIRECT);
      return;
    }

    if (msg.getResponseHeader().getStatusCode() != HttpStatusCode.OK) {
      addAnalysedHost(baseUri, msg, SampleResponse.ERROR_PAGE_NON_RFC);
      return;
    }

    HttpMessage msg2 = baseMsg.cloneRequest();
    URI uri2 = msg2.getRequestHeader().getURI();
    String path2 = getRandomPathSuffix(node, uri2);
    uri2 = (URI) baseUri.clone();
    uri2.setPath(path2);
    msg2.getRequestHeader().setURI(uri2);
    sendAndReceive(msg2);

    // remove HTML HEAD as this may contain expiry time which dynamic
    // changes
    String resBody1 = msg.getResponseBody().toString().replaceAll(p_REMOVE_HEADER, "");
    String resBody2 = msg2.getResponseBody().toString().replaceAll(p_REMOVE_HEADER, "");

    // check if page is static. If so, remember this static page
    if (resBody1.equals(resBody2)) {
      msg.getResponseBody().setBody(resBody1);
      addAnalysedHost(baseUri, msg, SampleResponse.ERROR_PAGE_STATIC);
      return;
    }

    // else check if page is dynamic but deterministic
    resBody1 = resBody1.replaceAll(getPathRegex(uri), "")
      .replaceAll("\\s[012]\\d:[0-5]\\d:[0-5]\\d\\s", "");
    resBody2 = resBody2.replaceAll(getPathRegex(uri2), "")
      .replaceAll("\\s[012]\\d:[0-5]\\d:[0-5]\\d\\s", "");
    if (resBody1.equals(resBody2)) {
      msg.getResponseBody().setBody(resBody1);
      addAnalysedHost(baseUri, msg, SampleResponse.ERROR_PAGE_DYNAMIC_BUT_DETERMINISTIC);
      return;
    }

    // else mark app "undeterministic".
    addAnalysedHost(baseUri, msg, SampleResponse.ERROR_PAGE_UNDETERMINISTIC);

  }

  /**
   * Get a suffix from the children which exists in staticSuffixList. An
   * option is provided to check recursively. Note that the immediate children
   * are always checked first before further recursive check is done.
   *
   * @param entity
   *            The current entity.
   * @param performRecursiveCheck
   *            True = get recursively the suffix from all the children.
   * @return The suffix ".xxx" is returned. If there is no suffix found, an
   *         empty string is returned.
   */
  private String getChildSuffix(SiteNode node, boolean performRecursiveCheck) {

    String resultSuffix = "";
    String suffix = null;
    SiteNode child = null;
    HistoryReference ref = null;
    HttpMessage msg = null;
    try {

      for (int i = 0; i < staticSuffixList.length; i++) {
        suffix = staticSuffixList[i];
        for (int j = 0; j < node.getChildCount(); j++) {
          child = (SiteNode) node.getChildAt(j);
          ref = child.getHistoryReference();
          try {
            msg = ref.getHttpMessage();
            if (msg.getRequestHeader().getURI().getPath().endsWith(suffix)) {
              return suffix;
            }
          } catch (Exception e) {
          }
        }
      }

      if (performRecursiveCheck) {
        for (int j = 0; j < node.getChildCount(); j++) {
          resultSuffix = getChildSuffix((SiteNode) node.getChildAt(j),performRecursiveCheck);
          if (!resultSuffix.equals("")) {
            return resultSuffix;
          }
        }
      }

    } catch (Exception e) {
    }

    return resultSuffix;
  }

  private String getPathRegex(URI uri) throws URIException {
    URI newUri = (URI) uri.clone();
    String query = newUri.getQuery();
    StringBuffer sb = new StringBuffer(100);

    // case should be sensitive
    // sb.append("(?i)");

    newUri.setQuery(null);

    sb.append(newUri.toString().replaceAll("\\.", "\\."));
    if (query != null) {
      String queryPattern = "(\\?" + query + ")?";
      sb.append(queryPattern);
    }

    return sb.toString();
  }

  /**
   * Get a random path relative to the current entity. Whenever possible, use
   * a suffix exist in the children according to a priority of
   * staticSuffixList.
   *
   * @param entity
   *            The current entity.
   * @param uri
   *            The uri of the current entity.
   * @return A random path (eg /folder1/folder2/1234567.chm) relative the
   *         entity.
   * @throws URIException
   */
  private String getRandomPathSuffix(SiteNode node, URI uri)
      throws URIException {
    String resultSuffix = getChildSuffix(node, true);

    String path = "";
    path = (uri.getPath() == null) ? "" : uri.getPath();
    path = path + (path.endsWith("/") ? "" : "/") + Long.toString(Math.abs(staticRandomGenerator.nextLong()));
    path = path + resultSuffix;

    return path;

  }

  /**
   * Analyse node (should be a folder unless it is host level) in-order.
   */
  private void inOrderAnalyse(SiteNode node) {

    SiteNode tmp = null;

    if (isSkipOrStop) {
      return;
    }
   

    if (node == null) {
      return;
    }

    // analyse entity if not root and not leaf.
    // Leaf is not analysed because only folder entity is used to determine
    // if path exist.
    try {
      if (!node.isRoot()) {
        if (!node.isLeaf() || node.isLeaf() && ((SiteNode) node.getParent()).isRoot()) {
          analyse(node);
        }
      }
    } catch (Exception e) {

    }

    for (int i = 0; i < node.getChildCount() && !isSkipOrStop(); i++) {
      try {
        tmp = (SiteNode) node.getChildAt(i);
        inOrderAnalyse(tmp);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  public boolean isFileExist(HttpMessage msg) {

    if (msg.getResponseHeader().isEmpty()) {
      return false;
    }

    // RFC
    if (msg.getResponseHeader().getStatusCode() == HttpStatusCode.NOT_FOUND) {
      return false;
    }

    URI uri = (URI) msg.getRequestHeader().getURI().clone();
    try {
      // strip off last part of path - use folder only
      uri.setQuery(null);
      String path = uri.getPath();
      path = path.replaceAll("/[^/]*$", "");
      uri.setPath(path);
    } catch (Exception e1) {
    }

    String sUri = uri.toString();

    // get sample with same relative path position when possible.
    // if not exist, use the host only
    SampleResponse sample = (SampleResponse) mapVisited.get(sUri);
    if (sample == null) {
      try {
        uri.setPath(null);
      } catch (URIException e2) {
      }
      String sHostOnly = uri.toString();
      sample = (SampleResponse) mapVisited.get(sHostOnly);
    }

    // check if any analysed result.

    if (sample == null) {
      if (msg.getResponseHeader().getStatusCode() == HttpStatusCode.OK) {
        // no anlaysed result to confirm, assume file exist and return
        return true;
      } else {
        return false;
      }
    }

    // check for redirect response. If redirect to same location, then file
    // does not exist
    if (HttpStatusCode.isRedirection(msg.getResponseHeader().getStatusCode())) {
      try {
        if (sample.getMessage().getResponseHeader().getStatusCode() == msg.getResponseHeader().getStatusCode()) {
          String location = msg.getResponseHeader().getHeader(HttpHeader.LOCATION);
          if (location != null && location.equals(sample.getMessage().getResponseHeader().getHeader(HttpHeader.LOCATION)))
          {
            return false;
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
      return true;
    }

    // Not success code
    if (msg.getResponseHeader().getStatusCode() != HttpStatusCode.OK) {
      return false;
    }

    // remain only OK response here

    // nothing more to determine. Check for possible not found page pattern.
    Matcher matcher = patternNotFound.matcher(msg.getResponseBody().toString());
    if (matcher.find()) {
      return false;
    }

    // static response
    String body = msg.getResponseBody().toString().replaceAll(p_REMOVE_HEADER, "");
    if (sample.getErrorPageType() == SampleResponse.ERROR_PAGE_STATIC) {
      if (sample.getMessage().getResponseBody().toString().equals(body)) {
        return false;
      }
      return true;
    }

    uri = msg.getRequestHeader().getURI();
    try {
      if (sample.getErrorPageType() == SampleResponse.ERROR_PAGE_DYNAMIC_BUT_DETERMINISTIC) {
       
        body = msg.getResponseBody().toString()
          .replaceAll(getPathRegex(uri), "")
          .replaceAll("\\s[012]\\d:[0-5]\\d:[0-5]\\d\\s", "");
         
        // ZAP: FindBugs fix - added call to HttpBody.toString()
        if (sample.getMessage().getResponseBody().toString().equals(body)) {
          return false;
        }
        return true;
      }
    } catch (Exception e) {
      e.printStackTrace();

    }

    return true;
  }

  private void sendAndReceive(HttpMessage msg) throws HttpException, IOException {
    httpSender.sendAndReceive(msg, true);
  }

}
TOP

Related Classes of org.parosproxy.paros.core.scanner.Analyser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.