作为一名安全研究人员,我需要定期对大量的目标主机进行安全扫描。最近,我遇到了一个挑战:需要在短时间内向250万台主机发送5亿次非RFC标准的HTTP/1.1请求,理想情况下是在几个小时内完成。经过一番研究和实践,我成功地使用Go语言构建了一个高效的“HTTP大炮”,并成功完成了任务。
在众多编程语言中,我最终选择了Go语言作为实现工具,主要原因有三点:
当然,我也尝试过使用Rust语言来实现,但异步tokio类型的复杂性让我望而却步。相比之下,Go语言的并发模型更加直观易懂,即使是JS开发者也能轻松驾驭。
你可能会问,5亿次HTTP/1.1请求到底意味着什么?这是一个很大的数字吗?答案是肯定的。
如果使用curl命令逐个发送这些请求,即使每秒发送2个请求,也需要7.9年才能完成。在实际情况下,由于服务器的速率限制和网络延迟,所需时间会更长。
从数据传输的角度来看,5亿次HTTP/1.1请求的数据量并不算太大:
真正的挑战在于如何高效地建立连接、发送请求和处理响应。
虽然在代码层面,发送一个HTTP/1.1请求只需要简单的几行代码,例如:
resp, err := http.Get("https://example.com")
但在底层,HTTP库需要执行一系列操作:
需要注意的是,上述任何一个步骤都可能失败,因此需要进行错误处理和重试。
为了提高发送效率,我们需要尽可能地减少每个请求的耗时。通过分析单个HTTP请求的步骤,我们可以找到优化的方向:
基于上述优化思路,我设计了一个多级流水线式的HTTP请求发送器,主要包括三个模块:
为了提高内存利用率和减少对象创建的开销,我使用了对象池来管理HTTP连接和请求/响应对象。同时,为了避免单个目标服务器过载,我对每个目标服务器的请求频率进行了限制。
为了追求极致的性能,我选择了fasthttp库来替代Go语言标准库中的net/http。fasthttp是一个轻量级、高性能的HTTP库,经过 benchmark 测试,其速度比net/http快了将近10倍。
为了跳过DNS解析步骤,我自定义了一个Dial函数,直接使用预先解析好的IP地址建立TCP连接。
req.SetDial(func(addr string) (net.Conn, error) { return customDialer.Dial(resolved_ip)})
由于我发送的是手工构造的非RFC标准HTTP请求,因此可以禁用fasthttp库中的请求标准化功能,进一步提高性能。
req := rawfasthttp.AcquireRequest()resp := rawfasthttp.AcquireResponse()rawBytes := []byte("GET / HTTP/1.1/r/nHost: example.com/r/n/r/n")req.SetRequestRaw(rawBytes)err := client.Do(req, resp)
为了进一步提高发送效率,我将HTTP请求发送器部署到了DigitalOcean的Kubernetes集群中。DigitalOcean提供了每月2TB的免费流量,足以满足我的测试需求。
为了实现自动化的弹性伸缩,我编写了一个简单的JavaScript脚本,根据任务队列的长度动态调整Kubernetes Deployment的副本数量。
在测试过程中,我遇到了一些挑战,例如:
最终,我成功地构建了一个高效的HTTP请求发送器,并在几个小时内完成了向250万台主机发送5亿次HTTP/1.1请求的任务。
通过这次实践,我深刻体会到了Go语言在网络编程方面的强大能力,也学习到了很多关于HTTP协议和网络安全的知识。我相信,这些经验将会对我未来的安全研究工作有所帮助。
本文链接:http://www.28at.com/showinfo-26-99168-0.htmlGo语言助力安全测试:24小时内发送5亿次HTTP/1.1请求
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 我们一起聊聊审核平台前端新老仓库迁移
下一篇: 阿里面试:说说@Async实现原理?