Turn PHP Built in Web Server into a Proxy

PHPMonkey? I wanted to use PHP to alter web pages and http traffic, so I downloaded many scripts that said they were PHP proxy. I didn't find any that really worked, they all just fetched the page, mangled all the links, and puked it back at me. I couldn't log in to any website, and if the site I was visiting used javascript to inject links, scripts, and all sorts of other dynamic content these so called proxies failed horribly. So, I figured I'd try writing one myself, and I've come up with something that works.

What I really wanted, was to be able to configure my browser to use my proxy for all requests. That way it wouldn't matter how my browser was triggered to download content, everything would be routed through my proxy. So far so good right? Yes. This is the trick. What I had to do was get PHP to take note of the headers when my request was sent to it and have it repeat them when making the request for some page. Once PHP got the page, it needed to take note of the headers in the response and repeat them back to my browser when it sent the response back to me. So far so good, right? Yes. I made a script that does exactly what I've described and I'm able to use foxyproxy to tell firefox that my server running on localhost is a proxy. The PHP script gets to tinker with all of my http traffic.

What this means is that I can do whatever preprocessing I want on the pages. I have access to the document before the browser does, so I'm a few steps ahead of when Greasemonkey gets ahold of the page (after everything has loaded). This means that if I want to completely remove JavaScript from my live website and see how it will react to a new version of said scripts, I can do it. I can browse around in realtime and see exactly how my site will perform without altering anything on my server. For what it can do, whole teams of people could be working on things without the need for multiple development servers. This is good stuff, really, think about it. We've got PHPUnit, Selenium, and a whole mess of other testing tools and analyzers floating around out there.

I really like the idea of programs like Fiddler, Burp, mitmproxy, and middlefiddle. I want something like that for PHP. Now I haven't got the https part figured out and I don't know if I can. If anyone can point me in the right direction I'd appreciate it. The code below works to proxy plain http traffic using the PHP built in server. Just set the code as your router script and tell your browser to use your server as a proxy. I haven't tried it on any other servers but I plan to set up XAMPP with a self signed cert and see if I can't figure out the https proxying.

The following code (router script) will allow PHP to proxy http traffic for you. You are free to modify and use it however you want. Just use this router script with PHP's built in web server and set your browsers proxy to whatever port you're running the server on.

<?php
/**
 * Rev. 1 Atropa mod_proxy for php built in webserver
 * ☭ Hial Atropa!! ☭
 */
class atropa_mod_proxy
{
    protected function is_external_resource() {
        $host = parse_url($_SERVER['REQUEST_URI'], PHP_URL_HOST);
        if(isset($host) && $host !== $_SERVER['SERVER_NAME']) {
            return true;
        } else {
            return false;
        }
    }
    
    protected function local_resource_handler() {
        return false;
    }
    
    protected function external_resource_handler() {
        $ext = $this->get_page();
        $this->show_page($ext);
    }
    
    public function process_request() {
        if($this->is_external_resource()) {
            return $this->external_resource_handler();
        } else {
            return $this->local_resource_handler();
        }
    }
    
    public function get_request_headers() {
        $arr = array();
        foreach($_SERVER as $svar => $sval) {
            if(substr($svar, 0, 4) === 'HTTP') {
                $svar = substr($svar, 5);
                $svar = preg_replace('/_/', ' ', $svar);
                $svar = ucwords(strtolower($svar));
                $svar = preg_replace('/ /', '-', $svar);
                $arr[$svar] = $sval;
            }
        }
        return $arr;
    }
    
    public function pack_request_headers($headers_array) {
        $packed = '';
        foreach($headers_array as $header_name => $header_value) {
            $packed .= $header_name . ': ' . $header_value . "\r\n";
        }
        return $packed;
    }
    
    public function echo_response_headers($http_response_header_array) {
        foreach($http_response_header_array as $val) {
            if(strpos(strtolower($val), 'connection') !== 0) {
                header($val);
            }
        }
    }
    
    protected function get_page() {
        $request_headers = $this->get_request_headers();
        $request_headers = $this->pack_request_headers($request_headers);
        $method = $_SERVER["REQUEST_METHOD"];
        $scheme = parse_url($_SERVER['REQUEST_URI'], PHP_URL_SCHEME);
        $opts = array(
            $scheme => array(
                'method' => $method,
                'header' => $request_headers
            )
        );
        if(count($_POST) > 0) {
            $content = http_build_query($_POST);
            $opts[$scheme]['content'] = $content;
        }
        $context = stream_context_create($opts);
        $ext = array();
        $ext['page'] = file_get_contents($_SERVER['REQUEST_URI'], false, $context);
        $ext['http_response_header'] = $http_response_header;
        return $ext;
    }
    
    protected function show_page($ext) {
        header_remove();
        $this->echo_response_headers($ext['http_response_header']);
        echo $ext['page'];
    }
}
?>

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.