1

I am trying to create a forward proxy capable of handling HTTPS websites as well. I am trying to observe and modify traffic for different sites. This is my code which works for http sites but not for https sites.

httpProxy.createServer(function(req, res, next) {
   //custom logic
   next();
}, function(req, res) {
   var proxy = new httpProxy.RoutingProxy();
   var buffer = httpProxy.buffer(req);
   var urlObj = url.parse(req.url);
   req.headers.host = urlObj.host;
   req.url = urlObj.path;
   console.log(urlObj.protocol);
  setTimeout(function() {
     proxy.proxyRequest(req, res, {
        host: urlObj.host,
        port: 80,
        buffer: buffer,
    }
   )}, 5000);

}).listen(9000, function() {
console.log("Waiting for requests...");
});

Thanks for your help guys!

everconfusedGuy
  • 2,709
  • 27
  • 43

5 Answers5

8

There are https options which must be specified when handling the https traffic. Here is what I am doing in my proxy setup.

var fs = require('fs'),
    httpProxy = require('http-proxy');

var proxyTable = {};

proxyTable['domain.com'] = 'localhost:3001';
proxyTable['domain.com/path'] = 'localhost:3002';
proxyTable['sub.domain.com'] = 'localhost:3003';

var httpOptions = {
    router: proxyTable
};

var httpsOptions = {
    router: proxyTable,
    https: {
        passphrase: 'xxxxxxx',
        key: fs.readFileSync('/path/to/key'),
        ca: fs.readFileSync('/path/to/ca'),
        cert: fs.readFileSync('/path/to/crt')}
};

httpProxy.createServer(httpOptions).listen(80);
httpProxy.createServer(httpsOptions).listen(443);

The documentation for https is on their github page as well.

https://github.com/nodejitsu/node-http-proxy

Timothy Strimple
  • 22,920
  • 6
  • 69
  • 76
  • So I am running the proxy on my localhost and need to proxy https sites as well..Does this mean I need to create a self signed certificate for my localhost? – everconfusedGuy Jul 25 '13 at 02:00
  • You'll need a certificate for whatever SSL site you're proxying. In my example, I have a wildcard for domain.com and all of the proxy items are available as both http and https. – Timothy Strimple Jul 25 '13 at 02:09
  • Say I want to proxy the response of Facebook, modify the response and then send it to the user, does this mean I need to have the certificate of Facebook? I would like to do this for all the https sites the user might browse to.. – everconfusedGuy Jul 25 '13 at 02:29
  • just to clarify, I am running the node proxy on my localhost and proxying all the requests and responses to my localhost server by manually setting the proxy to localhost and my port through the system settings.. – everconfusedGuy Jul 25 '13 at 02:49
  • Just to be clear about your scenario... you're setting up a local proxy, and modifying the hosts file so an internet request goes through your local connection and back out to the intended target? In that scenario, yes you would need a certificate signed for Facebook.com. A self signed certificate would encrypt the traffic, but Facebook wouldn't be able to decrypt the subsequent request. The browser would also throw up warning / error pages regarding the certificate. All of these things are designed to protect against what you're trying to do which amounts to a man in the middle attack. – Timothy Strimple Jul 25 '13 at 02:49
  • Nope. I am sorry if I have not been clear. My current proxy just forwards the requests as it is to the intended target and I want to modify the response sent by Facebook, say add in more HTML, before it is displayed to the user. I am currently able to do this for non-HTTPS sites. The HTTPS responses are not captured by the proxy and hence I am not able to modify them – everconfusedGuy Jul 25 '13 at 03:01
  • Steps I currently take; run 'node proxy.js' in my terminal. Go to the network settings in Linux, set proxy to manual and set the port to 9000 (from my example) and start browsing the internet. Now all the requests and responses (for non HTTPS sites) go through my nodejs script where I modify the responses sent and send it to the client. – everconfusedGuy Jul 25 '13 at 03:07
  • Sorry, not clear on how that is different from my scenario above. You cannot proxy https requests to 3rd party websites without having their ssl certificate. It is intentionally designed that way. Take a look at this answer on the security StackExchange: http://security.stackexchange.com/questions/8145/does-https-prevent-man-in-the-middle-attacks-by-proxy-server – Timothy Strimple Jul 25 '13 at 03:13
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/34114/discussion-between-timothy-strimple-and-everconfusedguy) – Timothy Strimple Jul 25 '13 at 03:16
4

If you're just doing a forward proxy there's a few things you'll have to take into account.

  • A regular request is NOT triggered on a proxy for a HTTPS request - instead you'll see a HTTP CONNECT.

Here's the sequence flow you'll need to handle.

  1. CONNECT event is sent from the browser to the proxy specified in the HTTPS section. You'll catch this here: http://nodejs.org/api/http.html#http_event_connect Note that this comes over the HTTP module, not the HTTPS connection.
  2. You create a new socket connection to the requested domain (or your mapped domain). [srvSocket]
  3. You'll respond back to the CONNECT socket with a 200
  4. You'll write the buffer you received with the CONNECT event to srvSocket, then pipe the two sockets together srvSocket.pipe(socket);
  5. socket.pipe(srvSocket);

Since you're trying to spoof the requested domain locally you'll need a few more things in place

  1. You'll need to generate a root CA.
  2. You will need to import this cert as a trusted authority to your OS
  3. You'll use this cert to create a new key/cert file for the domains you're trying to access
  4. Your mapped hosts will need to respond with the appropriate key/cert file generated in step 3 for EACH domain you are mapping.
Angelo R.
  • 2,285
  • 1
  • 17
  • 22
1

https://github.com/substack/bouncy

var bouncy = require('bouncy');

var server = bouncy(function (req, res, bounce) {
    if (req.headers.host === 'beep.example.com') {
    bounce(8001);
    }
    else if (req.headers.host === 'boop.example.com') {
        bounce(8002);
    }
    else {
        res.statusCode = 404;
        res.end('no such host');
    }
});
server.listen(8000);

If you specify opts.key and opts.cert, the connection will be set to secure mode using tls. Do this if you want to make an https router.

Jason Livesay
  • 6,317
  • 3
  • 25
  • 31
1

We can have a middleware as below

request = require("request"); app.use(function (req, res, next) { request('http://anotherurl.that.serves/the/request').pipe(res); });

See example https://github.com/manuks/proxy

Manu
  • 4,101
  • 1
  • 17
  • 23
0

Basically, underneath the http-proxy npm is some networking libraries Node uses (specifically http://nodejs.org/api/https.html and TLS). Even though my Apache was able to connect me just fine on a self-signed certificate w/o the proxy by accessing it in my browser:

https://localhost:8002    

You need to establish a certificate authority to get past the "unable to verify leaf signature" error in Node (I used the SSLCACertificateFile option). Then, you'll get hit with "self_signed_cert_in_chain". This led to some Google results indicating npm abandoned self-signed certificates, but I'm pretty sure this does not regard Node.

What you end up with are some people indicating you use process.env.NODE_TLS_REJECT_UNAUTHORIZED or rejectUnauthorized within your https agent. If you dig through the http-proxy souce, you'll find it accepts an agent option. Use this:

/**
 * Module dependencies
 */

// basic includes
express     = require('express');
fs          = require('fs');
http        = require('http');
https       = require('https');
httpProxy   = require('http-proxy');
require('child_process').spawn(__dirname+'/../../../dependencies/apache/bin/httpd.exe',['-f',__dirname+'/../../../dependencies/apache/conf/httpd-payments.conf']); 

var app     = module.exports = express();
app.set('port', process.env.PORT || 8001); // we sometimes change the port

// creates an output object for this particular request
//app.use(express.cookieParser(''));
//app.use(express.bodyParser());
//app.use(express.methodOverride());

proxy   = httpProxy.createProxyServer();
proxy.on('error', function (err, req, res) {
    console.log(err);
    res.send(500,err);
    res.end();
});

app.all('*',function(req,res,next) {
    var options = {
      hostname: '127.0.0.1',
      port: 8002,
      rejectUnauthorized: false,
      key: fs.readFileSync(__dirname+"/../../../deployment/server.key.pem"),
      cert: fs.readFileSync(__dirname+"/../../../deployment/server.crt.pem")
    };
    agent = new https.Agent(options);
    try {
        proxy.web(req,res, {
            target: "https://localhost:8002",
            proxyTimeout: 30,
            agent: agent
        });
    } catch(e) {
        // 500 error
        res.send(500,e);
    }
})

/** 
 * Start Server
 */

var options = {
  key: fs.readFileSync(__dirname+"/../../../deployment/server.key.pem"),
  cert: fs.readFileSync(__dirname+"/../../../deployment/server.crt.pem")
};
server  = https.createServer(options,app).listen(app.get('port'), function () {
  console.log('Running payments server on port ' + app.get('port'));
});
mikewhit
  • 121
  • 5