第一节:HTTP中间人代理实现

想实现HTTPS的中间人代理,我们先定个小目标,先实现一个HTTP的中间人代理。

HTTP中间人代理

HTTP中间人代理示意图:
第一节:HTTP中间人代理实现 - 图1

由于HTTP的传输内容都是明文,想实现中间人代理就变得非常简单。

部分代码实现如下:

  1. const http = require('http');
  2. const url = require('url');
  3. let httpMitmProxy = new http.Server();
  4. // 启动端口
  5. let port = 6789;
  6. httpMitmProxy.listen(port, () => {
  7. console.log(`HTTP中间人代理启动成功,端口:${port}`);
  8. });
  9. // 代理接收客户端的转发请求
  10. httpMitmProxy.on('request', (req, res) => {
  11. // 解析客户端请求
  12. var urlObject = url.parse(req.url);
  13. let options = {
  14. protocol: 'http:',
  15. hostname: req.headers.host.split(':')[0],
  16. method: req.method,
  17. port: req.headers.host.split(':')[1] || 80,
  18. path: urlObject.path,
  19. headers: req.headers
  20. };
  21. console.log(`请求方式:${options.method},请求地址:${options.protocol}//${options.hostname}:${options.port}${options.path}`);
  22. // 根据客户端请求,向真正的目标服务器发起请求。
  23. let realReq = http.request(options, (realRes) => {
  24. // 设置客户端响应的http头部
  25. Object.keys(realRes.headers).forEach(function(key) {
  26. res.setHeader(key, realRes.headers[key]);
  27. });
  28. // 设置客户端响应状态码
  29. res.writeHead(realRes.statusCode);
  30. // 通过pipe的方式把真正的服务器响应内容转发给客户端
  31. realRes.pipe(res);
  32. });
  33. // 通过pipe的方式把客户端请求内容转发给目标服务器
  34. req.pipe(realReq);
  35. realReq.on('error', (e) => {
  36. console.error(e);
  37. })
  38. })

完整源码:../code/chapter1/httpMitmProxy.js

npm script运行方式

  1. npm run httpMitmProxy

上面的代码实现了一个最简单的http代理。

流程概括如下:

  • 1、接收客户端的转发请求。
  • 2、根据客户端请求,向真正的目标服务器发起请求。
  • 3、把客户端请求内容转发给目标服务器。
  • 4、把真正的服务器响应内容转发给客户端。

设置代理

代理服务启动好后,我们需要把本机上的http请求都通过代理做转发。

Windows 下设置代理方式

第一步

第一节:HTTP中间人代理实现 - 图2

第二步

第一节:HTTP中间人代理实现 - 图3

第三步

第一节:HTTP中间人代理实现 - 图4

MAC 下设置代理方式

第一步

第一节:HTTP中间人代理实现 - 图5

第二步

第一节:HTTP中间人代理实现 - 图6

第三步

第一节:HTTP中间人代理实现 - 图7

作为中间人,能做什么?

通过该代理服务我们已经成功的与通信的两端(服务器和客户端)同时建立了连接。作为”中间人”,轻而易举就能篡改经过的请求和响应。

通过http代理修改html内容:

  1. const http = require('http');
  2. const url = require('url');
  3. const through = require('through2');
  4. let httpMitmProxy = new http.Server();
  5. // 启动端口
  6. let port = 6789;
  7. httpMitmProxy.listen(port, () => {
  8. console.log(`HTTP中间人代理启动成功,端口:${port}`);
  9. });
  10. // 代理接收客户端的转发请求
  11. httpMitmProxy.on('request', (req, res) => {
  12. // 解析客户端请求
  13. var urlObject = url.parse(req.url);
  14. let options = {
  15. protocol: 'http:',
  16. hostname: req.headers.host.split(':')[0],
  17. method: req.method,
  18. port: req.headers.host.split(':')[1] || 80,
  19. path: urlObject.path,
  20. headers: req.headers
  21. };
  22. // 为了方便起见,直接去掉客户端请求所支持的压缩方式
  23. delete options.headers['accept-encoding'];
  24. console.log(`请求方式:${options.method},请求地址:${options.protocol}//${options.hostname}:${options.port}${options.path}`);
  25. // 根据客户端请求,向真正的目标服务器发起请求。
  26. let realReq = http.request(options, (realRes) => {
  27. // 设置客户端响应的http头部
  28. Object.keys(realRes.headers).forEach(function(key) {
  29. res.setHeader(key, realRes.headers[key]);
  30. });
  31. // 设置客户端响应状态码
  32. res.writeHead(realRes.statusCode);
  33. // 通过响应的http头部判断响应内容是否为html
  34. if (/html/i.test(realRes.headers['content-type'])) {
  35. realRes.pipe(through(function(chunk, enc, callback) {
  36. let chunkString = chunk.toString();
  37. // 给html注入的alert的js代码
  38. let script = '<script>alert("Hello https-mitm-proxy-handbook!")</script>'
  39. chunkString = chunkString.replace(/(<\/head>)/ig, function (match) {
  40. return script + match;
  41. });
  42. this.push(chunkString);
  43. callback();
  44. })).pipe(res);
  45. } else {
  46. realRes.pipe(res);
  47. }
  48. });
  49. // 通过pipe的方式把客户端请求内容转发给目标服务器
  50. req.pipe(realReq);
  51. realReq.on('error', (e) => {
  52. console.error(e);
  53. })
  54. })

完整源码:../code/chapter1/httpMitmProxy.js

npm script运行方式

  1. npm run httpMitmProxy

相比第一个代理只实现了单纯的转发,这一次的实现的代理对客户端请求和服务器响应都做了篡改。

1、在处理客户端请求时,直接去掉了accept-encoding的http头部。
2、修改了服务器响应内容,给所有html注入了一段alert的js代码<script>alert("Hello https-mitm-proxy-handbook!")</script>。为了方便的修改pipe流的内容,这里用到了through2

现在通过代理来访问任何http的网站都会弹出alert框

第一节:HTTP中间人代理实现 - 图8

HTTP的安全问题

从上面的http代理的实现可以看到,单纯的http协议是没有任何安全保障。想想平时身边的黑Wi-Fi、黑心运营商,想窥探或篡改通过http传输的内容是多么容易的一件事。

第二节:如何代理HTTPS请求