Package org.owasp.webscarab.plugin.proxy

Source Code of org.owasp.webscarab.plugin.proxy.ConnectionHandler

/***********************************************************************
*
* $CVSHeader$
*
* This file is part of WebScarab, an Open Web Application Security
* Project utility. For details, please see http://www.owasp.org/
*
* Copyright (c) 2002 - 2004 Rogan Dawes
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*
* Getting Source
* ==============
*
* Source for this application is maintained at Sourceforge.net, a
* repository for free software projects.
*
* For details, please see http://www.sourceforge.net/projects/owasp
*
*/

package org.owasp.webscarab.plugin.proxy;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.security.cert.X509Certificate;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import org.owasp.webscarab.httpclient.HTTPClient;
import org.owasp.webscarab.httpclient.HTTPClientFactory;
import org.owasp.webscarab.httpclient.URLFetcher;
import org.owasp.webscarab.model.ConversationID;
import org.owasp.webscarab.model.HttpUrl;
import org.owasp.webscarab.model.Request;
import org.owasp.webscarab.model.Response;
import org.owasp.webscarab.util.HtmlEncoder;

public class ConnectionHandler implements Runnable {

  private ProxyPlugin[] _plugins = null;
  private Proxy _proxy;
  private Socket _sock = null;
  private HttpUrl _base;

  private HTTPClient _httpClient = null;

  private Logger _logger = Logger.getLogger(getClass().getName());

  private InputStream _clientIn = null;
  private OutputStream _clientOut = null;

  public ConnectionHandler(Proxy proxy, Socket sock, HttpUrl base) {
    _proxy = proxy;
    _sock = sock;
    _base = base;
    _plugins = _proxy.getPlugins();
    try {
      _sock.setTcpNoDelay(true);
      _sock.setSoTimeout(30 * 1000);
    } catch (SocketException se) {
      _logger.warning("Error setting socket parameters");
    }
  }

  public void run() {
    ScriptableConnection connection = new ScriptableConnection(_sock);
    _proxy.allowClientConnection(connection);
    if (_sock.isClosed())
      return;

    try {
      _clientIn = _sock.getInputStream();
      _clientOut = _sock.getOutputStream();
    } catch (IOException ioe) {
      _logger.severe("Error getting socket input and output streams! "
          + ioe);
      return;
    }
    ConversationID id = null;
    try {
      Request request = null;
      // if we do not already have a base URL (i.e. we operate as a normal
      // proxy rather than a reverse proxy), check for a CONNECT
      if (_base == null) {
        try {
          request = new Request();
          request.read(_clientIn);
        } catch (IOException ioe) {
          _logger.severe("Error reading the initial request" + ioe);
          return;
        }
      }
      // if we are a normal proxy (because request is not null)
      // and the request is a CONNECT, get the base URL from the request
      // and send the OK back. We set request to null so we read a new
      // one from the SSL socket later
      // If it exists, we pull the ProxyAuthorization header from the
      // CONNECT
      // so that we can use it upstream.
      String proxyAuth = null;
      if (request != null) {
        String method = request.getMethod();
        if (method == null) {
          return;
        } else if (method.equals("CONNECT")) {
          if (_clientOut != null) {
            try {
              _clientOut.write(("HTTP/1.0 200 Ok\r\n\r\n")
                  .getBytes());
              _clientOut.flush();
            } catch (IOException ioe) {
              _logger
                  .severe("IOException writing the CONNECT OK Response to the browser "
                      + ioe);
              return;
            }
          }
          _base = request.getURL();
          proxyAuth = request.getHeader("Proxy-Authorization");
          request = null;
        }
      }

      if (_httpClient == null)
        _httpClient = HTTPClientFactory.getInstance().getHTTPClient();

      HTTPClient hc = _httpClient;

      // if we are servicing a CONNECT, or operating as a reverse
      // proxy with an https:// base URL, negotiate SSL
      if (_base != null) {
        // There are two certificates involved in a connection: one for the
        // requested server, one for the client. First, the actual host must
        // be determined from the client (SNI). Next, that host name must be
        // taken to the server so it can return an appropriate certificate.
        // Finally, the attributes from that server cert (mainly Subject and
        // subjectAlternateName) can be copied back to the certificate
        // presented to the client.
        if (_base.getScheme().equals("https")) {
          _logger.fine("Intercepting SSL connection!");
          X509Certificate baseCrt = null;
          // Connect early so some SSL details can be copied into new cert
          URLFetcher uf = (URLFetcher) hc;
          try {
            uf.connect(_base);
          } catch (IOException ioe) {
            _logger.severe("Could not connect to remote server "
                + _base.toString() + ": " + ioe);
            return;
          }
          baseCrt = uf.getCertificate();
          _logger.log(Level.FINEST, "Certificate: {0}",
              baseCrt == null ? "null" :
              baseCrt.getSubjectX500Principal().getName());
          _sock = negotiateSSL(_sock, _base.getHost(), baseCrt);
          _clientIn = _sock.getInputStream();
          _clientOut = _sock.getOutputStream();
        }
      }

      // Maybe set SSL ProxyAuthorization here at a connection level?
      // I prefer it in the Request itself, since it gets archived, and
      // can be replayed trivially using netcat

      // layer the proxy plugins onto the recorder. We do this
      // in reverse order so that they operate intuitively
      // the first plugin in the array gets the first chance to modify
      // the request, and the last chance to modify the response
      if (_plugins != null) {
        for (int i = _plugins.length - 1; i >= 0; i--) {
          hc = _plugins[i].getProxyPlugin(hc);
        }
      }

      // do we add an X-Forwarded-For header?
      String from = _sock.getInetAddress().getHostAddress();
      if (from.equals("127.0.0.1"))
        from = null;

      // do we keep-alive?
      String keepAlive = null;
      String version = null;

      do {
        id = null;
        // if we are reading the first from a reverse proxy, or the
        // continuation of a CONNECT from a normal proxy
        // read the request, otherwise we already have it.
        if (request == null) {
          request = new Request();
          _logger.fine("Reading request from the browser");
          request.read(_clientIn, _base);
          if (request.getMethod() == null || request.getURL() == null) {
            return;
          }
          if (proxyAuth != null) {
            request.addHeader("Proxy-Authorization", proxyAuth);
          }
        }
        if (from != null) {
          request.addHeader("X-Forwarded-For", from);
        }
        try {
        _logger.fine("Browser requested : " + request.getMethod() + " "
            + request.getURL().toString());
        } catch (NullPointerException npe) {
          System.out.println("Request is: " + request);
        }
        // report the request to the listener, and get the allocated ID
        id = _proxy.gotRequest(request);

        // pass the request for possible modification or analysis
        connection.setRequest(request);
        connection.setResponse(null);
        _proxy.interceptRequest(connection);
        request = connection.getRequest();
        Response response = connection.getResponse();

        if (request == null)
          throw new IOException("Request was cancelled");
        if (response != null) {
          _proxy.failedResponse(id, "Response provided by script");
          _proxy = null;
        } else {

          // pass the request through the plugins, and return the
          // response
          try {
            response = hc.fetchResponse(request);
            if (response != null && response.getRequest() != null)
              request = response.getRequest();
          } catch (IOException ioe) {
            _logger
                .severe("IOException retrieving the response for "
                    + request.getURL() + " : " + ioe);
            ioe.printStackTrace();
            response = errorResponse(request, ioe);
            // prevent the conversation from being
            // submitted/recorded
            _proxy.failedResponse(id, ioe.toString());
            _proxy = null;
          }
          if (response == null) {
            _logger.severe("Got a null response from the fetcher");
            _proxy.failedResponse(id, "Null response");
            return;
          }
        }

        if (_proxy != null) {
          // pass the response for analysis or modification by the
          // scripts
          connection.setResponse(response);
          _proxy.interceptResponse(connection);
          response = connection.getResponse();
        }

        if (response == null)
          throw new IOException("Response was cancelled");

        try {
          if (_clientOut != null) {
            _logger.fine("Writing the response to the browser");
            response.write(_clientOut);
            _logger
                .fine("Finished writing the response to the browser");
          }
        } catch (IOException ioe) {
          _logger
              .severe("Error writing back to the browser : "
                  + ioe);
        } finally {
          response.flushContentStream(); // this simply flushes the
                          // content from the server
        }
        // this should not happen, but might if a proxy plugin is
        // careless
        if (response.getRequest() == null) {
          _logger.warning("Response had no associated request!");
          response.setRequest(request);
        }
        if (_proxy != null && !request.getMethod().equals("CONNECT")) {
          _proxy.gotResponse(id, response);
        }

        keepAlive = response.getHeader("Connection");
        version = response.getVersion();

        request = null;

        _logger.fine("Version: " + version + " Connection: "
            + connection);
      } while ((version.equals("HTTP/1.0") && "keep-alive"
          .equalsIgnoreCase(keepAlive))
          || (version.equals("HTTP/1.1") && !"close"
              .equalsIgnoreCase(keepAlive)));
      _logger.fine("Finished handling connection");
    } catch (Exception e) {
      if (id != null)
        _proxy.failedResponse(id, e.getMessage());
      _logger.severe("ConnectionHandler got an error : " + e);
      e.printStackTrace();
    } finally {
      try {
        if (_clientIn != null)
          _clientIn.close();
        if (_clientOut != null)
          _clientOut.close();
        if (_sock != null && !_sock.isClosed()) {
          _sock.close();
        }
      } catch (IOException ioe) {
        _logger.warning("Error closing client socket : " + ioe);
      }
    }
  }


  private Socket negotiateSSL(Socket sock, String host,
      X509Certificate baseCrt) throws Exception {
    SSLSocketFactory factory = _proxy.getSocketFactory(host, baseCrt);
    if (factory == null)
      throw new RuntimeException(
          "SSL Intercept not available - no keystores available");
    SSLSocket sslsock;
    try {
      sslsock = (SSLSocket) factory.createSocket(sock, sock
          .getInetAddress().getHostName(), sock.getPort(), true);
      // Workaround for Java 7 regression: http://stackoverflow.com/q/10687200/427545
      sslsock.setEnabledCipherSuites(new String[] {
          "SSL_RSA_WITH_RC4_128_MD5",
          "SSL_RSA_WITH_RC4_128_SHA",
          "TLS_RSA_WITH_AES_128_CBC_SHA",
          "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
          "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
          "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
          "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
          "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
          "SSL_RSA_WITH_DES_CBC_SHA",
          "SSL_DHE_RSA_WITH_DES_CBC_SHA",
          "SSL_DHE_DSS_WITH_DES_CBC_SHA",
          "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
          "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
          "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
          "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
          "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"});
      sslsock.setUseClientMode(false);
      _logger.fine("Finished negotiating SSL - algorithm is "
          + sslsock.getSession().getCipherSuite());
      return sslsock;
    } catch (Exception e) {
      _logger.severe("Error layering SSL over the socket: " + e);
      throw e;
    }
  }

  private Response errorResponse(Request request, Exception e) {
    Response response = new Response();
    response.setRequest(request);
    response.setVersion("HTTP/1.0");
    response.setStatus("500");
    response.setMessage("WebScarab error");
    response.setHeader("Content-Type", "text/html");
    response.setHeader("Connection", "Close");
    String template = "<HTML><HEAD><TITLE>WebScarab Error</TITLE></HEAD>";
    template = template
        + "<BODY>WebScarab encountered an error trying to retrieve <P><pre>"
        + HtmlEncoder.encode(request.toString()) + "</pre><P>";
    template = template + "The error was : <P><pre>"
        + HtmlEncoder.encode(e.getLocalizedMessage()) + "\n";
    StackTraceElement[] trace = e.getStackTrace();
    if (trace != null) {
      for (int i = 0; i < trace.length; i++) {
        template = template + "\tat " + trace[i].getClassName() + "."
            + trace[i].getMethodName() + "(";
        if (trace[i].getLineNumber() == -2) {
          template = template + "Native Method";
        } else if (trace[i].getLineNumber() == -1) {
          template = template + "Unknown Source";
        } else {
          template = template + trace[i].getFileName() + ":"
              + trace[i].getLineNumber();
        }
        template = template + ")\n";
      }
    }
    template = template + "</pre><P></HTML>";
    response.setContent(template.getBytes());
    return response;
  }

}
TOP

Related Classes of org.owasp.webscarab.plugin.proxy.ConnectionHandler

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.