Go语言实现指纹识别
指纹识别
指纹识别是一种用于识别网络设备、操作系统、应用程序或用户的技术。它通过收集和分析目标系统的特征信息,如网络协议、端口号、操作系统版本、应用程序版本等,来确定系统的身份或特征。指纹识别可以用于多种用途,包括网络侦察、入侵检测、安全审计等。
常用的指纹识别方法:
- 特定文件的MD5值:一些CMS的特定的静态资源:图片favicon.ico logo.ico之类的、js文件、css文件一般是不会修改的。通过爬虫对这些文件进行抓取并比对 md5 值,如果和规则库中的 Md5 一致则说明是同一 CMS。这种方式速度比较快,误报率相对低一些,但也不排除有些二次开发的 CMS 会修改这些文件。
- 正常页面或错误页面中包含的关键字:先访问首页或特定页面如 robots.txt 等,通过正则的方式去匹配某些关键字,如 Powered by Discuz、dedecms 等。或者可以构造错误页面,根据报错信息来判断使用的 CMS 或者中间件信息,比较常见的如 tomcat 的报错页面。
- 请求头关键字信息的匹配:根据网站 response 返回头信息进行关键字匹配,whatweb 和 Wappalyzer 就是通过 banner 信息来快速识别指纹,之前 fofa 的 web 指纹库很多都是使用的这种方法,效率非常高,基本请求一次就可以,但搜集这些规则可能会耗时很长。而且这些 banner 信息有些很容易被改掉。
根据 response header 一般有以下几种识别方式:
- 查看 http 响应报头的 X-Powered-By 字段来识别;
- 根据 Cookies 来进行判断,比如一些 waf 会在返回头中包含一些信息,如 360wzws、Safedog、yunsuo 等;
- 根据 header 中的 Server 信息来判断,如 DVRDVS-Webs、yunjiasu-nginx、Mod_Security、nginx-wallarm 等;
- 根据 WWW-Authenticate 进行判断,一些路由交换设备可能存在这个字段,如 NETCORE、huawei、h3c 等设备。
- 部分URL中包含的关键字:比如 wp-includes、dede 等 URL 关键特征。通过规则库去探测是否有相应目录,或者根据爬虫结果对链接 url 进行分析,或者对 robots.txt 文件中目录进行检测等等方式,通过 url 地址来判别是否使用了某 CMS,比如 wordpress 默认存在 wp-includes 和 wp-admin 目录,织梦默认管理后台为 dede 目录,solr 平台可能使用 /solr 目录,weblogic 可能使用 wls-wsat 目录等。
- 开发语言的识别:php?jsp?aspx?asp?
主要方式:
- 通过爬虫获取动态链接直接判断
- 通过 X-Powered-By :常见的有 X-Powered-By: ASP.NET 或者 X-Powered-By: PHP/7.1.8
- 通过 Set-Cookie 进行识别:Set-Cookie 中包含 PHPSSIONID 说明是 php、包含 JSESSIONID 说明是 java、包含 ASP.NET_SessionId 说明是 aspx 等
Goland,启动!
参考:https://github.com/newbe3three/gotoscan
选定指纹识别方式
对比文件md5值和页面中关键字
解析json文件 parsecms.go
首先分析json文件
"gowinsoft_jw": [{
"path": "/web/web/web/images/4bt1.jpg",
"option": "md5",
"content": "ef1ee9c8708cde1bd25a90054de85690"
}, {
...
}],
"maticsoftsns": [{
"path": "/msgbox/images/gb_tip_layer.png",
"option": "md5",
"content": "c8cb16e8b61bc549ebd339858e66fa5c"
}, {
...
}],
.....
可以看见cms对应特征有三个内容:path、option、content,我们需要将其解析成Go结构体。通常将json数据解析成map[][]
,然后借助encoding/json
包的方法实现对json文件的解析。因此定义:
//对应CMS特征的内容
type CmsFeature struct {
Path string `json:"path"`
Option string `json:"option"`
Content string `json:"content"`
}
//通过map来对应json形式的数据,k就是cms名,v就是对应的特征切片。
map[string][]CmsFeature
发起请求 request.go
先对服务器发起head
请求,状态码为200再发起get
请求。先发起head请求主要是由于其响应头和get是完全一样的,但是服务器不会返回请求的实体数据,避免了传输请求、响应体的数据浪费。head请求时非常快的。
首先会定义一个代理池,然后随机选择一个代理头。文件中除此之外就是两个处理url的函数
func HeadReq(url string) (resp *http.Response, err error) {
// 创建了自定义的http.Transport 结构体
tranCfg := &http.Transport{
// 设置了 TLSClientConfig 字段,将 InsecureSkipVerify 设置为 true
// 这表示跳过对服务器证书的验证,用于处理不安全的 HTTPS 连接
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
// 创建http.Client结构体,设置超时时间为10s
client := &http.Client{
Timeout: 10 * time.Second,
Transport: tranCfg,
}
req, err := http.NewRequest("HEAD", url, nil)
if err != nil {
return nil, err
}
req.Header.Add("User_Agent", get_random_ua())
//resp, err2 :=
// 返回响应和可能的错误
return client.Do(req)
}
func GetReq(url string) (content []byte, err error) {
tranCfg := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{
Timeout: 10 * time.Second,
Transport: tranCfg,
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// 读取http响应体标准用法
bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return bytes, nil
}
作者这儿的处理还是蛮简单的
实现测试 scancms.go
扫描部分重要的是并发————同时扫描多台主机;同时扫描多个cms
Go语言的并发是易得的,只需要再你想要的并发的函数前面加上关键字go就实现了简单的并发操作。
//对多个host的并发操作
func HostWorker(hosts []string, cmslist map[string][]CmsFeature, sortList CmsSortList) []string {
// 创建通道
hostsChan := make(chan string)
resultChan := make(chan string)
var resultList []string
// 根据获取到的目标数量开启并发
for i := 0; i < len(hosts); i++ {
go cmsWorker(hostsChan, cmslist, sortList, resultChan)
}
for _, host := range hosts {
hostsChan <- host
}
// 使用通道获取线程
for i := 0; i < len(hosts); i++ {
// 程序会在这里阻塞直到有数据传入通道
result := <-resultChan
resultList = append(resultList, result)
}
close(hostsChan)
close(resultChan)
return resultList
}
//对多个cms的并发操作
func cmsWorker(hosts chan string, cmslist map[string][]CmsFeature, sortList CmsSortList, resultChan chan string) {
for host := range hosts {
var scanStatus bool = false
cmsListChan := make(chan map[string][]CmsFeature, 10)
var wg sync.WaitGroup// 用来控制线程
for i := 0; i < cap(cmsListChan); i++ {
go featureWorker(host, cmsListChan, &wg, &scanStatus, resultChan)
}
// 如果匹配到了直接return,没匹配到计数器+1
for _, data := range sortList {
if !scanStatus {
wg.Add(1)
cmsListChan <- map[string][]CmsFeature{data.Name: cmslist[data.Name]}
} else {
//wg1.Done()
return
}
}
wg.Wait()
// 扫描完所有特征都没匹配到,向结果通道添加一条数据,也就是所谓发起和接收要一样。
// 每个host都应该有一个扫描结果
resultChan <- fmt.Sprintf("The host: %s has no matching results", host)
close(cmsListChan)
}
}
TODO
开发一个自己的指纹识别插件