Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cURL as proxy, deal with HTTPS/CONNECT method

This script listens on an IP/port and intends to act as a HTTP(S) proxy.

Requests to HTTP URLs work fine, but I'm stumbling on how to deal with HTTPS requests and more specifically, an SSLv3 handshake after the client sends a CONNECT request to the proxy.

The closest I've came to what looks like an answer is:

  • CURLOPT_HTTPPROXYTUNNEL libcurl option to tunnel data between client and target server
  • stream_socket_enable_crypto() to possibly "do stuff" with the encrypted data

I'm really not sure, so a pointer as to how to deal with this would be greatly appreciated.

Here is a sample request: http://pastebin.com/xkWhGyjW

<?php

class proxy {

    static $server;
    static $client;

    static function headers($str) { // Parses HTTP headers into an array
        $tmp = preg_split("'\r?\n'",$str);
        $output = array();
        $output[] = explode(' ',array_shift($tmp));
        $post = ($output[0][0] == 'POST' ? true : false);

            foreach($tmp as $i => $header) {
                if($post && !trim($header)) {
                    $output['POST'] = $tmp[$i+1];
                    break;
                }
                else {
                    $l = explode(':',$header,2);
                    $output[$l[0]] = $l[0].': '.ltrim($l[1]);
                }
            }
        return $output;
    }

    public function output($curl,$data) {
        socket_write(proxy::$client,$data);
        return strlen($data);
    }
}




$ip = "127.0.0.1";
$port = 50000;

proxy::$server = socket_create(AF_INET,SOCK_STREAM, SOL_TCP);
socket_set_option(proxy::$server,SOL_SOCKET,SO_REUSEADDR,1);
socket_bind(proxy::$server,$ip,50000);
socket_getsockname(proxy::$server,$ip,$port);
socket_listen(proxy::$server);

while(proxy::$client = socket_accept(proxy::$server)) {

    $input = socket_read(proxy::$client,4096);
    preg_match("'^([^\s]+)\s([^\s]+)\s([^\r\n]+)'ims",$input,$request);
    $headers = proxy::headers($input);

        echo $input,"\n\n";
            if(preg_match("'^CONNECT '",$input)) { // HTTPS
                // Tell the client we can deal with this
                socket_write(proxy::$client,"HTTP/1.1 200 Connection Established\r\n\r\n");
                // Client sends binary data here (SSLv3, TLS handshake, Client hello?)
                // socket_read(proxy::$client,4096);
                // ?
            }
            else { // HTTP

                        $input = preg_replace("'^([^\s]+)\s([a-z]+://)?[a-z0-9\.\-]+'","\\1 ",$input);
                        $curl = curl_init($request[2]);
                        curl_setopt($curl,CURLOPT_HEADER,1);
                        curl_setopt($curl,CURLOPT_HTTPHEADER,$headers);
                        curl_setopt($curl,CURLOPT_TIMEOUT,15);
                        curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);
                        curl_setopt($curl,CURLOPT_NOPROGRESS,1);
                        curl_setopt($curl,CURLOPT_VERBOSE,1);
                        curl_setopt($curl,CURLOPT_AUTOREFERER,true);
                        curl_setopt($curl,CURLOPT_FOLLOWLOCATION,1);
                        curl_setopt($curl,CURLOPT_WRITEFUNCTION, array("proxy","output"));
                        curl_exec($curl);
                        curl_close($curl);
            }
    socket_close(proxy::$client);
}
socket_close(proxy::$server);


?>
like image 687
innvo Avatar asked Jun 05 '13 07:06

innvo


People also ask

Does Curl go through proxy?

To use a proxy with Curl, you must pass the required proxy address using the -x (or --proxy) command-line option and proxy credentials using the -U (or --proxy-user) command-line switch. Proxy credentials may also be passed in the proxy string and will be URL decoded by Curl.

Do HTTP proxies work with https?

Modern proxy servers can be used as gateways for requests that access both HTTP and HTTPS resources.

What does Curl -- proxy do?

Proxies - Everything curl. A proxy is a machine or software that does something on behalf of you, the client. You can also see it as a middle man that sits between you and the server you want to work with, a middle man that you connect to instead of the actual remote server.


1 Answers

If I understand correctly, you're writing a HTTP proxy server in PHP. The CURLOPT_HTTPPROXYTUNNEL option is used when you want to connect to a proxy server using the PHP cURL library and use CONNECT instead of GET. In this case it's not relevant.

When your proxy server (PROXY) receives the CONNECT request, it should connect to the specified host (ENDPOINT) using socket_create and socket_connect. Once the connection is established, let the client (CLIENT) know by sending HTTP/1.1 200 Connection Established. After that, you'll want to copy all data that the ENDPOINT sends to PROXY to the CLIENT and all data that the CLIENT sends to PROXY to the ENDPOINT.

Using cURL like in your example will create multiple connections. To handle multiple connections, I've used pcntl_fork, which forks a new process on every CONNECT request.

Here's a working example:

<?php

class proxy {

    static $server;
    static $client;

    static function headers($str) { // Parses HTTP headers into an array
        $tmp = preg_split("'\r?\n'",$str);
        $output = array();
        $output[] = explode(' ',array_shift($tmp));
        $post = ($output[0][0] == 'POST' ? true : false);

            foreach($tmp as $i => $header) {
                if($post && !trim($header)) {
                    $output['POST'] = $tmp[$i+1];
                    break;
                }
                else {
                    $l = explode(':',$header,2);
                    $output[$l[0]] = $l[0].': '.ltrim($l[1]);
                }
            }
        return $output;
    }

    public function output($curl,$data) {
        socket_write(proxy::$client,$data);
        return strlen($data);
    }
}




$ip = "127.0.0.1";
$port = 50000;

proxy::$server = socket_create(AF_INET,SOCK_STREAM, SOL_TCP);
socket_set_option(proxy::$server,SOL_SOCKET,SO_REUSEADDR,1);
socket_bind(proxy::$server,$ip,50000);
socket_getsockname(proxy::$server,$ip,$port);
socket_listen(proxy::$server);

while(proxy::$client = socket_accept(proxy::$server)) {

    $input = socket_read(proxy::$client,4096);
    preg_match("'^([^\s]+)\s([^\s]+)\s([^\r\n]+)'ims",$input,$request);
    $headers = proxy::headers($input);

        echo $input,"\n\n";
            if(preg_match("'^CONNECT ([^ ]+):(\d+) '",$input,$match)) { // HTTPS
                // fork to allow multiple connections
                if(pcntl_fork())
                    continue;

                $connect_host = $match[1];
                $connect_port = $match[2];

                // connect to endpoint
                $connection = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
                if(!socket_connect($connection, gethostbyname($connect_host), $connect_port))
                    exit;

                // let the client know that we're connected
                socket_write(proxy::$client,"HTTP/1.1 200 Connection Established\r\n\r\n");

                // proxy data
                $all_sockets = array($connection, proxy::$client);
                $null = null;
                while(($sockets = $all_sockets)
                      && false !== socket_select($sockets, $null, $null, 10)
                ) {
                    // can we read from the client without blocking?
                    if(in_array(proxy::$client, $sockets)) {
                        $buf = null;
                        socket_recv(proxy::$client, $buf, 8192, MSG_DONTWAIT);
                        echo "CLIENT => ENDPOINT (" . strlen($buf) . " bytes)\n";
                        if($buf === null)
                            exit;
                        socket_send($connection, $buf, strlen($buf), 0);
                    }

                    // can we read from the endpoint without blocking?
                    if(in_array($connection, $sockets)) {
                        $buf = null;
                        socket_recv($connection, $buf, 8192, MSG_DONTWAIT);
                        echo "ENDPOINT => CLIENT (" . strlen($buf) . " bytes)\n";
                        if($buf === null)
                            exit;
                        socket_send(proxy::$client, $buf, strlen($buf), 0);
                    }
                }

                exit;
            }
            else { // HTTP

                        $input = preg_replace("'^([^\s]+)\s([a-z]+://)?[a-z0-9\.\-]+'","\\1 ",$input);
                        $curl = curl_init($request[2]);
                        curl_setopt($curl,CURLOPT_HEADER,1);
                        curl_setopt($curl,CURLOPT_HTTPHEADER,$headers);
                        curl_setopt($curl,CURLOPT_TIMEOUT,15);
                        curl_setopt($curl,CURLOPT_RETURNTRANSFER,1);
                        curl_setopt($curl,CURLOPT_NOPROGRESS,1);
                        curl_setopt($curl,CURLOPT_VERBOSE,1);
                        curl_setopt($curl,CURLOPT_AUTOREFERER,true);
                        curl_setopt($curl,CURLOPT_FOLLOWLOCATION,1);
                        curl_setopt($curl,CURLOPT_WRITEFUNCTION, array("proxy","output"));
                        curl_exec($curl);
                        curl_close($curl);
            }
    socket_close(proxy::$client);
}
socket_close(proxy::$server);
like image 131
Albert Peschar Avatar answered Nov 04 '22 14:11

Albert Peschar