gotoscan
前言
项目地址
https://github.com/ErSuan/gotoscan
结合自己学习到的Go相关知识,通过实现这个简易的CMS指纹识别工具来锻炼一下自己写代码的能力。
常见的指纹识别的方式:
- 特定文件的MD5值:一些CMS的特定的静态资源:图片、js文件、css文件一般是不会修改的。所以可以根据这些文件的md5值来进行匹配。
- 正常页面或错误页面中包含的关键字:比如说在
robots.txt
等,可能包含了标识CMS的关键字。或者是一些报错页面的报错信息也包含了CMS关键字,比如thinkphp3
的错误页面。 - 请求头关键字信息的匹配
- 部分URL中包含的关键字
整体介绍
在这块,我们解析的特征文件也只包含了上述常见方式的前两种,即对比文件md5值和页面中的关键字。
代码整体逻辑还是比较好理解的,解析包含CMS特征的json文件,并根据解析的内容发起请求,并验证其是否包含了CMS的特征。
所以代码也主要由三个块内容:负责解析json文件的parsecms.go
、负责发起请求的request.go
、和最后实现测试的scancms.go
。
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"
}, {
...
}],
.....
json文件主要还是由每一个小的json数据块组成,是CMS名字和对应的特征。首先需要定义一个将json数据解析为Go结构体的类型。通常我们将json数据解析成Go中的map[][]
,那么我们对上面的json就可以由如下Go结构体来对应。
//对应CMS特征的内容
type CmsFeature struct {
Path string `json:"path"`
Option string `json:"option"`
Content string `json:"content"`
}
//通过map来对应json形式的数据,k就是cms名,v就是对应的特征切片。
map[string][]CmsFeature
通过借助"encoding/json"
包的方法来实现对json文件的解析。将解析后的内容存放在map[string][]CmsFeature
中返回。具体有关"encoding/json"
包内的其他方法可以去看看api文档。
json.Unmarshal(data, &cmslist);
request.go
发起请求这一块主要是先对服务器发起head
请求,head
请求状态码为200再发起get
请求。先发起head
请求主要是由于其响应头和get
是完全一样的,但是服务器不会返回请求的实体数据,避免了传输请求、响应体的数据浪费。head
请求时非常快的。
scancms.go
扫描这一部分最重要的部分就是并发。一方面是同时扫描多台主机,另一方面时同时扫描多个CMS。
Go语言的并发是易得的,只需要再你想要的并发的函数前面加上关键字go
就实现了简单的并发操作。
控制同时扫描多台主机
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
}
//使用通道来控制线程,因为发送的工作单元数量和接收到结果的数量是相同的,所以程序直到何时关闭通道并随后关闭cmsWorker线程。
for i := 0; i < len(hosts); i++ {
//程序会在这里阻塞直到有数据传入通道。
result := <-resultChan
resultList = append(resultList, result)
}
close(hostsChan)
close(resultChan)
return resultList
}
控制同时扫描多个CMS特征。与上述不同这里我们需要用到sync.WaitGroup
来控制线程。因为这里发送和接收是不同的。
//对多个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)
}
//这里需要先判断是否已经匹配到了,没匹配到计数器才能+1。匹配到了就直接return
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)
}
}
总结
至此一个简单的CMS工具就实现了,其他部分的代码可以直接点击左下角去我的GitHub上看源码就好了。当然啦CMS识别不仅仅是需要你多种可以匹配的方式,最重要的还是需要足够完备的特征库。
这次通过这个工具,还是学到了很多Go一些包的用法以及一些接口。代码还是还是要多敲。
Todo
- 请求头关键字信息的匹配
- 同时扫描多个特征
- 根据cmd参数来控制线程