HTTP PROXY及net.Sockethttp.createServer => 返回http.Server类 http.get , http.request => 返回http.ClientRequest类 http.Server => 内部创建http.ServerResponse类 http.Server , http.ClienRequest => 内部创建http.IncomingMessage类
http.IncomingMessage类实现可读流接口 http.ClientRequest类实现可写流接口 http.ServerResponse类实现可写流接口 socket流是duplex stream , 即可写也可读
net.Socket简单的交流利用 事件、流 ,可以很容易的完成一个 “简单交流“ 的功能, 而不用去写多线程 server: const net = require('net'); const process = require('process');
const server = net.createServer(); //创建Server服务器 server.on('connection',(c)=>{ //触发connection事件时 c.pipe(process.stdout); //将客户端socket重定向到控制台 process.stdin.pipe(c); //将控制台输入重定向到客户端socket })
server.listen(1111); //监听端口
client: const net = require('net'); const process = require('process'); const options = { host: 'localhost', port: 1111, }
const s = net.connect(options,()=>{ //连接Server服务器,触发connection事件 console.log('server connected'); process.stdin.pipe(s); //将控制台输入重定向到服务端socket s.pipe(process.stdout); //将服务端socket重定向到控制台
这是一个简单的客户端与服务端通过socket连接进行通信的例子。服务端创建服务,监听端口,等待连接。客户端发起连接进行三次握手,然后进行数据交互。 在nodejs中,我们使用通过监听事件来处理相应操作,上述例子中,我们创建的服务监听了connection事件,即: server.on('connection',(c)=>{ //触发connection事件时 c.pipe(process.stdout); //将客户端socket重定向到控制台 process.stdin.pipe(c); //将控制台输入重定向到客户端socket })
当客户端发起连接并成功连接到服务端监听的端口,触发connection事件。同理客户端也监听connection事件,对后续连接成功做好准备。 socket流是duplex stream,即可写也可读。pipe是常使用的管道符,将流的输出通过管道导入到流的输入。socket流即可写也可读,即可理解为可输出输入。服务端通过pipe将客户端通过socket传输的数据重定向到终端输出中,将终端输入重定向到socket通道中,客户端通过同样的方式来处理终端和socket中的数据流。 我们并没有使用像python多线程的一样的写法,来使用不同的线程来分别处理socket输出和终端输入,这是nodejs本身的事件机制带来的好处,通过nodejs自身线程池,事件循环,异步处理,来避免了多线程复杂的写法。 连接时三次握手: 通过TCP socket传输数据: 获取 http服务本身是基于TCP协议封装,所以同样会涉及到socket的使用,nodejs本身的http库,支持对http服务连接中socket的调用,这样我们便可以在http服务中,操作socket,来完成更多功能,如:http隧道代理 1.http.ClientRequest 类 - connect事件:
- response
- socket
- head
- sokcet事件:
- socket
- upgrade事件:
- response
- socket
- head
- request.connection:
- 返回
- request.socket:
- 返回
2.http.Server 类 - clientError 事件:
- exception < Eroor >
- socket
- connect 事件:
- request
- socket
- head
- upgrade 事件:
- request
- socket
- head
- connection 事件:
- socket
3.http.ServerResponse类 - response.connection:
- 返回
- response.socket:
- 返回
4.http.IncomingMessage类 - message.socket:
- 返回
5.http.createServer([options][,requestListener]) - requestListener:
- 自动添加http.Server类的 request事件
6.net.Server类 - connetiton事件
- 返回
创建net.Socket1.net.createConnection() - new net.Socket([options]) && socket.connect(option)
- fd
- allowHalfOpen
- readable
- writable
- ( 以上为new net.Socket([options]) )
- port
- host
- localAddress
- localPort
- family
- hints
- lookup
- net.createConnection(port[, host][, connectListener])
- port
- host
- socket.connect(path[, connectListener])
- path ( IPC )
2.net.connect() - 同createConnection()
搭建 HTTP 代理简单代理- 对浏览器的 http 简单代理
普通代理: const url = require('url'); const httpServer = http.createServer(); httpServer.on('request',(req,res)=>{ const targetUrl = url.parse(req.url); const reqOptions = { host: targetUrl.host, path: targetUrl.path || "", headers: req.headers, } const proxyReq = http.request(reqOptions,(proxyRes)=>{ console.log(proxyRes.headers); res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); }); proxyReq.on('error',(e)=>{ res.end(); }) proxyReq.end(); }) httpServer.on('error',(e)=>{ console.log(e); }) httpServer.listen(1111);
带使用普通代理时,客户端检测到存在http代理,便会先将http头信息转发给http代理服务器。上图可以看到,代理服务器接收到的http头相对客户端直接发送给服务端的http头有一点小差异,即代理服务器接收的http头url是一个绝对的url地址,具有完整url结构。 代理服务器从这个http头中,提取关键要素:url地址,端口,header头。代理发送请求,将接收到的http头,状态码先发送给客户端,再将接收到body数据通过pipe重定向到http.ServerResponse可写流,客户端相应接收代理服务器发送数据。 隧道代理:隧道代理主要 针对与使用CONNECT方式请求的HTTP代理 const net = require('net'); const http = require('http'); const url = require('url');
const server = http.createServer() server.on('connect',(req,clientSock,header)=>{ const u = url.parse(`https://${req.url}`); const proxySock = net.connect(u.port,u.hostname,()=>{ //建立一个socket隧道 将客户端和服务端数据通过隧道进行传输 这样就可以完成在http代理对https请求的代理 clientSock.write('HTTP/1.1 200 Connection Established\r\n\r\n'); proxySock.pipe(clientSock); }).on('error',(e)=>{ clientSock.end(); }); clientSock.pipe(proxySock); }) server.listen(1111)
隧道代理的关键,在于代理服务器不直接参与客户端和服务端数据交互的中转,而是通过代理服务器在客户端和服务端建立一条socket通道,使客户端和服务端在这条新的socket隧道中进行数据交互。完成的原理是利用代理服务器分别获取 客户端到代理的socket 和 代理到服务器的socket,再将两个socket使用pipe进行连通。 CONNECT连接方式常使用在https的代理上,普通代理无法完成对https的代理,因为代理服务器无法直接从客户端请求中提取关键的信息,https的内容是使用服务端公钥加密,在代理服务器没有服务端私钥的情况下,无法解密信息。而对于CONNECT隧道连接来说,所有的数据请求都是通过socket隧道来传输,代理只需要从连接中获取socket,从请求中获取域名和端口,无需获取完整的http头信息,所以代理https的数据请求。 隧道代理认证: 代理服务器使用 407 状态码回复客户端代理认证 - 对http服务器简单代理
const http = require('http'); const url = require('url'); const targetUrl = 'http://jwc.cuit.edu.cn'; const server = http.createServer(); server.on('request',(req,res)=>{ const u = url.parse(targetUrl); const reqOptions = { hostname: u.hostname, port: u.port || 80, path: url.parse(req.url).path || '', } const proxyReq = http.request(reqOptions,(proxyRes)=>{ res.writeHead(proxyRes.statusCode,proxyRes.headers); proxyRes.pipe(res); }); proxyReq.on('error',(e)=>{ console.log(e); }) proxyReq.end(); }); server.listen(1111)
原理:自身建立http服务,或https服务,在获取客户端请求的时候,将请求本身的地址,替换为目标地址,同样转发相应的http heaers头,body信息。 通过修改,可代理http和https的网站,这里的代理,需要区别上面的请求代理。这里的对http服务的代理,是指定对象,对特定的网站进行代理。代理自身开启http服务或https服务,转发对目标地址的请求地址,请求头。目标地址请求,由代理服务器发起。 - 利用http-proxy库:
const http = require('http'); const httpproxy = require('http-proxy'); const url = require('url'); const proxy = httpproxy.createProxy({});
http.createServer((req,res)=>{ const target = 'http://jwc.cuit.edu.cn'; const parsedUrl = url.parse(target); const options = { target: target, headers: { host: parsedUrl.hostname }, } proxy.web(req,res,options); }).listen(1111);
同样支持target为https , http-proxy在请求的使用 https.request() 分析 http-proxy库目录结构: -- http-proxy -- passes -- web-incoming.js //对客户端进行代理请求 -- web-outgoing.js //对服务端返回数据进行处理 -- ws-incoming.js //处理ws服务的请求 -- conmon.js //包含公共方法 -- index.js //启动器 决定是调用http(s)或ws服务 -- http-proxy.js //入口main函数
HTTP PROXY漏洞历史http proxy的攻击主要集中在中间人攻击上,通过代理来记录或篡改HTTP访问中的数据,如何构建一个http proxy代理 成为一个入手点。 0x00 HTTPOXY链接1 链接2 环境 利用: curl -H "Proxy: xxxxx.com:1233" http://xx.com //自定义添加Proxy header头
关键流程: - HTTP服务器接收并解析HTTP请求,并配置必要的环境变量。
- HTTP服务器调用CGI脚本,并通过脚本的标准输入传递请求数据。
- CGI脚本处理环境变量及其输入,并使用脚本的标准输出将响应发送回HTTP服务器。
- HTTP服务器将CGI脚本生成的响应发送回客户端
原理: “Protocol-Specific Meta-Variables”(协议特定元变量) HTTP服务器会将HTTP Headers种的值注册到环境变量种 注册过程将Headers头全部大写 “-“替换为”” 上前缀”HTTP” CGI在脚本无法区分HTTP_PROXY是来自HTTP Headers注册变量 还是系统环境变量 导致通过HTTP Headers头注册Proxy被CGI当作系统环境变量 错误的代理处理HTTP请求 危害: CGI内部的请求会被代理给暴露 0x01TinyproxyTinyproxy在子网掩码生成实现上存在一个错误,当配置允许网络段时(如"Allow 192.168.0.0/24"相对与默认的"Allow 127.0.0.1"),会允许任意IP地址连接,使其成为一个开放代理。 0x02 Node-HTTP-Proxy 案例- 错误标头导致程序崩溃
nodejs 在安全发布中提到: 为了解决这个缺陷,Node.js中针对请求和响应的HTTP头解析正在接近正式的HTTP规范。包含令牌有效集之外的字符的HTTP标头将被拒绝。对Node.js HTTP服务器和客户端的请求和响应执行此检查。
通过向代理发送不正确的 HTTP 头来,来导致程序异常报错终止。 如: content-type: "�?@"
相关文档HTTP 基本规则基本规则链接 对于字段包含注释内容 参考wiki: 某些字段中可以包含注释内容(例如User-Agent、Server和Via字段中),这些注释内容可由应用程序忽略
|