httpx is a fast and multi-purpose HTTP toolkit allows to run multiple probers using retryablehttp library, it is designed to maintain the result reliability with increased threads.
httpx是一个多功能的http请求工具包,通常可以用它来批量探测资产的一些基本信息。
源码地址:https://github.com/projectdiscovery/httpx
支持的探针有
通常情况下,实现一个类似的工具是很容易的,如果用python来写
import requests url = "https://x.hacking8.com" r = requests.get(url) resp = r.text status_code = r.status_code
就可以获得大部分探针的信息,但要做成一个工程化的软件,就要考虑更多问题。
如何处理并发,如何加快请求速度,如何进行原生http请求以适应更多探针,以及如何工程化的组织go的源码框架,看了httpx的源码,这些都挺有收获的,所以写一篇源码记录。
基础结构
源码目录
.
├── cmd
│ └── httpx # 程序编译目录
├── common # 公共函数
│ ├── customheader # 自定义header的一些处理
│ ├── customports # 自定义端口的处理
│ ├── fileutil # 文件处理
│ ├── httputilz # http处理
│ ├── httpx # 一些探针
│ ├── iputil # ip处理
│ ├── slice # go切片处理
│ └── stringz # string函数
├── internal # 运行处理主目录
│ └── runner
├── scripts # 不重要
└── static # 不重要
cmd/httpx/httpx.go
是整个程序的主入口,流程一目了然,先解析命令,做初始化操作,按照命令运行,最后关闭。
package main import ( "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/httpx/internal/runner" ) func main() { // 解析参数 options := runner.ParseOptions() // 启动,初始化工作 run, err := runner.New(options) if err != nil { gologger.Fatalf("Could not create runner: %s\n", err) } // 运行 run.RunEnumeration() // 关闭 run.Close() }
Go的并发处理
在http的请求中,最好对go协程的并发做一些限制,类似于线程池
的概念。
httpx使用了https://github.com/remeh/sizedwaitgroup
的库
使用也很简单,初始化时定义协程的数量,初始化之后,在要使用协程之前wg.Add()
,使用协程后wg.Done()
代表结束即可。
以上只是对协程数做了限制,如果还想对每秒并行的协程数量做限制,可以参考projectdiscover另一个项目nuclei
,它在使用了sizedwaitgroup
的基础上,还使用了go.uber.org/ratelimit
使用更加简单
package main import ( "fmt" "time" "go.uber.org/ratelimit" ) func main() { rl := ratelimit.New(1) // per second prev := time.Now() for i := 0; i < 10; i++ { now := rl.Take() // 重点是它 if i > 0 { fmt.Println(i, now.Sub(prev)) } prev = now } }
初始化后在协程中调用Take()
方法即可。
探针
httpx支持了很多基于http的基础探针,看几个有意思的。
探针的实现大部分在common/httpx
目录。
Cdn check
common/httpx/cdn.go
在以前看naabu
时也看到过 https://x.hacking8.com/post-406.html
顾名思义是检测是否是CDN,跟踪一下,发现cdn检查调用的是
github.com/projectdiscovery/cdncheck
中的项目。主要是通过接口获取一些CDN的ip段,判断ip是否在这些ip段中
CSP
common/httpx/csp.go
主要是获取header中的响应头
// CSPHeaders is an incomplete list of most common CSP headers var CSPHeaders []string = []string{ "Content-Security-Policy", // standard "Content-Security-Policy-Report-Only", // standard "X-Content-Security-Policy-Report-Only", // non - standard "X-Webkit-Csp-Report-Only", // non - standard }
HTTP2.0
httpx支持http2.0
的探针
httpx.client2 = &http.Client{ Transport: &http2.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, AllowHTTP: true, }, Timeout: httpx.Options.Timeout, }
Pipeline 探针
http 1.1支持管道传输,例如在一个数据包中发送这样的包
GET / HTTP/1.1
HOST: 127.0.0.1
GET / HTTP/1.1
HOST: 127.0.0.1
GET / HTTP/1.1
HOST: 127.0.0.1
检测最后的返回是否合法。
package httpx import ( "crypto/tls" "fmt" "net" "strings" "time" ) // SupportPipeline checks if the target host supports HTTP1.1 pipelining by sending x probes // and reading back responses expecting at least 2 with HTTP/1.1 or HTTP/1.0 func (h *HTTPX) SupportPipeline(protocol, method, host string, port int) bool { addr := host if port == 0 { port = 80 if protocol == "https" { port = 443 } } if port > 0 { addr = fmt.Sprintf("%s:%d", host, port) } // dummy method while awaiting for full rawhttp implementation dummyReq := fmt.Sprintf("%s / HTTP/1.1\nHost: %s\n\n", method, addr) conn, err := pipelineDial(protocol, addr) if err != nil { return false } // send some probes nprobes := 10 for i := 0; i < nprobes; i++ { if _, err = conn.Write([]byte(dummyReq)); err != nil { return false } } gotReplies := 0 reply := make([]byte, 1024) for i := 0; i < nprobes; i++ { err := conn.SetReadDeadline(time.Now().Add(1 * time.Second)) if err != nil { return false } if _, err := conn.Read(reply); err != nil { break } // The check is very naive, but it works most of the times for _, s := range strings.Split(string(reply), "\n\n") { if strings.Contains(s, "HTTP/1.1") || strings.Contains(s, "HTTP/1.0") { gotReplies++ } } } // expect at least 2 replies return gotReplies >= 2 } func pipelineDial(protocol, addr string) (net.Conn, error) { // http if protocol == "http" { return net.Dial("tcp", addr) } // https return tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: true}) }
Title
httpx里面的title单独对中文编码做了优化
但是不太完美,只对返回头的Content-Type
做了判断,但有的网站是从html的met
a中定义编码的,有兴趣的老哥可以去提个pr。
HTTP 证书信息
go内置的网络库已经把tls信息获取并解析了,直接获取即可。
package httpx import ( "net/http" ) // TLSData contains the relevant Transport Layer Security information type TLSData struct { DNSNames []string `json:"dns_names,omitempty"` Emails []string `json:"emails,omitempty"` CommonName []string `json:"common_name,omitempty"` Organization []string `json:"organization,omitempty"` IssuerCommonName []string `json:"issuer_common_name,omitempty"` IssuerOrg []string `json:"issuer_organization,omitempty"` } // TLSGrab fills the TLSData func (h *HTTPX) TLSGrab(r *http.Response) *TLSData { if r.TLS != nil { var tlsdata TLSData for _, certificate := range r.TLS.PeerCertificates { tlsdata.DNSNames = append(tlsdata.DNSNames, certificate.DNSNames...) tlsdata.Emails = append(tlsdata.Emails, certificate.EmailAddresses...) tlsdata.CommonName = append(tlsdata.CommonName, certificate.Subject.CommonName) tlsdata.Organization = append(tlsdata.Organization, certificate.Subject.Organization...) tlsdata.IssuerOrg = append(tlsdata.IssuerOrg, certificate.Issuer.Organization...) tlsdata.IssuerCommonName = append(tlsdata.IssuerCommonName, certificate.Issuer.CommonName) } return &tlsdata } return nil }
检测virtualhost
检测是否是虚拟主机,方法很简单,将host换成一个随机值,和原来的返回进行比对,不一样则为虚拟主机。
比对的方法有很多,状态码,长度,单词数,行数,文本相似度对比等等。
package httpx import ( "fmt" "github.com/hbakhtiyor/strsim" retryablehttp "github.com/projectdiscovery/retryablehttp-go" "github.com/rs/xid" ) const simMultiplier = 100 // IsVirtualHost checks if the target endpoint is a virtual host func (h *HTTPX) IsVirtualHost(req *retryablehttp.Request) (bool, error) { httpresp1, err := h.Do(req) if err != nil { return false, err } // request a non-existing endpoint req.Host = fmt.Sprintf("%s.%s", xid.New().String(), req.Host) httpresp2, err := h.Do(req) if err != nil { return false, err } // Status Code // 比对状态码,不相同则返回True if !h.Options.VHostIgnoreStatusCode && httpresp1.StatusCode != httpresp2.StatusCode { return true, nil } // Content - Bytes Length // 比对content,不等则返回True if !h.Options.VHostIgnoreContentLength && httpresp1.ContentLength != httpresp2.ContentLength { return true, nil } // Content - Number of words (space separated) // 比对单词数量,不等则返回True if !h.Options.VHostIgnoreNumberOfWords && httpresp1.Words != httpresp2.Words { return true, nil } // Content - Number of lines (newline separated) // 比对行数 if !h.Options.VHostIgnoreNumberOfLines && httpresp1.Lines != httpresp2.Lines { return true, nil } // Similarity Ratio - if similarity is under threshold we consider it a valid vHost // 文本相似度对比 if int(strsim.Compare(httpresp1.Raw, httpresp2.Raw)*simMultiplier) <= h.Options.VHostSimilarityRatio { return true, nil } return false, nil }
不为人知的优化
autofdmax
// automatic fd max increase if running as root _ "github.com/projectdiscovery/fdmax/autofdmax
之前看naabu
时也说过哦
大多数类UNIX操作系统(包括Linux和macOS)在每个进程和每个用户的基础上提供了系统资源的限制和控制(如线程,文件和网络连接)的方法。 这些“ulimits”阻止单个用户使用太多系统资源。
这个库的作用是自动将限制调到最大。
dns缓存
httpx调用了github.com/projectdiscovery/fastdialer/fastdialer
会缓存dns记录(文件式),加快请求速度
http/https的识别转换
httpx默认会请求http/https两种协议,默认先请求https,如果失败了会再请求http,这样就能识别出使用了http还是https了。
在internal/runner/runner.go
识别指纹
之前给httpx提交过一个pr,基于wappalyzer的指纹。
PR详情: https://github.com/projectdiscovery/httpx/pull/205
若干天后,projectdiscover官方自己实现了一个这个库的,并作为go包的形式调用了。https://github.com/projectdiscovery/wappalyzergo
最新版已经可以使用,使用命令
-tech-detect
chaos -d hackerone.com -silent | ./httpx -tech-detect
__ __ __ _ __
/ /_ / /_/ /_____ | |/ /
/ __ \/ __/ __/ __ \| /
/ / / / /_/ /_/ /_/ / |
/_/ /_/\__/\__/ .___/_/|_|
/_/ v1.0.4-dev
projectdiscovery.io
Use with caution. You are responsible for your actions
Developers assume no liability and are not responsible for any misuse or damage.
https://mta-sts.hackerone.com [GitHub Pages,Ruby on Rails,Varnish]
https://mta-sts.managed.hackerone.com [Ruby on Rails,Varnish,GitHub Pages]
https://mta-sts.forwarding.hackerone.com [Varnish,GitHub Pages,Ruby on Rails]
https://www.hackerone.com [Varnish,Amazon EC2,Cloudflare,React,Drupal,PHP,Acquia Cloud Platform,Apache,Percona]
https://api.hackerone.com [Cloudflare,jsDelivr]
https://resources.hackerone.com
https://support.hackerone.com [Cloudflare]
https://docs.hackerone.com [jsDelivr,React,Gatsby,webpack,Varnish,GitHub Pages,Ruby on Rails]
可以学习的
结合GitHub action的自动编译发布
实现打个tag,github就自动帮你编译发布,在开源软件中还是比较方便的
新建.github/workflows
目录,新建release.yml
name: Release on: create: tags: - v* jobs: release: runs-on: ubuntu-latest steps: - name: "Check out code" uses: actions/checkout@v2 with: fetch-depth: 0 - name: "Set up Go" uses: actions/setup-go@v2 with: go-version: 1.14 - env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" name: "Create release on GitHub" uses: goreleaser/goreleaser-action@v2 with: args: "release --rm-dist" version: latest
编译分为三步,第一步签出代码,第二部安装go环境,第三部使用goreleaser/goreleaser-action
编译上传。
说下第三步,在根目录新建.goreleaser.yml
文件,文件内容用于配制编译以及上传选项,httpx的配置如下
builds: - binary: httpx main: cmd/httpx/httpx.go goos: - linux - windows - darwin goarch: - amd64 - 386 - arm - arm64 archives: - id: tgz format: tar.gz replacements: darwin: macOS format_overrides: - goos: windows format: zip
具体字段的介绍可查看:https://goreleaser.com/intro/
最后
projectdiscover为资产探测/收集造了很多go的基础轮子,用起来很方便,如果想学习go&&写扫描器,这个项目真是非常好的学习资料。
本文作者:w9ay
本文转载于先知社区
cesfe 22天前0
好的,谢谢昶之琴 24天前0
这个安装地址失效了,我在网上找了一个:https://xiazai.zol.com.cn/detail/35/344340.shtml 如果还是不行的话就需要您自己去网上找找了cesfe 25天前0
帆软部署 ,访问的地址访问不到昶之琴 2年前0
我以为只要提交就行了好想告诉你 2年前0
花巨资看一下