fscan源码学习和POC模版引擎改造
前言:自己的工作需要,所以这边先把fscan源码学习一次,然后这篇笔记将其作为记录
参考文章:https://github.com/shadow1ng/fscan
参考文章:https://github.com/jjf012/gopoc
参考文章:https://tekton.dev/docs/triggers/cel_expressions/
参考文章:https://xz.aliyun.com/t/9857
参考文章:https://yaml.org/spec/1.2-old/spec.html#style/block/
参考文章:https://github.com/niudaii/zpscan/blob/14e816cb12c04f5ac64cf0cdc3c8c8dc90f7ac97/pkg/webscan/jsjump.go
fscan的结构
fscan是基于面向过程来进行编写的,所以它的代码结构十分的简单,这样的话学习起来就简单易懂
fscan的目录结构
这边下载的fscan源码是最新版的,版本号为1.8.3
fscan的目录结构主要就是三个目录,分别是common目录、Plugins目录和Webscan目录
common目录
common目录里面存储的主要就是数据的解析和结构体以及变量的存储
数据的解析就包括了对输入的命令行参数的解析,还有ip cidr形式的解析生成
下面的图中就可以看到对命令行参数的解析
这里的话就是对参数的解析赋值变量,如下图所示
变量其中就包括了一些默认要扫描的默认端口,还有一些就是默认的字典,以及存储结果的文本名称
ip cidr形式的数据解析生成
稍微提及下这边还提供了类似workflow的形式,比如直接提供172的话就是直接扫描172.16.0.0/12网段,但是这边的话是有个bug,那也就是192的时候为什么是解析为192.168.0.0/8
的呢,这边可以注意下
Plugins目录
Plugins目录里面存储就是插件,其中都是各个端口服务的搜集或者是利用以及调度插件的实现,如下图所示
扫描利用插件
比如关于web端口的话都是通过webtitle.go来进行探测搜集
又比如ms17010的话,这边的话就是通过ms17010-exp.go来进行利用
base.go配置扫描
其中还有一个base.go是添加扩展插件来使用的,如下图所示,如果你在Plugins目录中添加了其他插件的话,那么这边的话就需要在base.go文件中的PluginList变量添加一个对应函数映射
Webscan目录中自实现了基于yml格式的web扫描指纹插件,如下图所示
info/rules.go中自定义了解析web系统框架指纹的识别格式,如下图所示
相关的指纹都可以在info/rules.go的RuleDatas变量中添加应用,如下图所示
scanner.go调度插件的实现
这边的话再看scanner.go文件,该代码负责了框架的调度流程,其中包括如下所示
- 先进行初始化HTTP客户端
- 主机探测ICMP/PING探测
- 选择webpoc,webonly,portscan,hostname其中的扫描模式来进行扫描存活主机
- 端口服务扫描,其中包括HTTP服务探测以及第三方服务的漏洞利用
程序的插件扫描执行流程
上面可以看到在端口服务扫描完成之后,就将相关的target的端口根据对应已经实现的插件利用的端口进行对比,然后进行AddScan任务添加利用,如下图所示
可以看到AddScan中主要还是ScanFunc函数
这边的话调用对应的插件利用是通过reflect反射先获取插件的名称,然后再进行调用
PluginList中有对应插件名称的函数,如下图所示,至此整个fscan的调用流程就是这样
其他插件的实现
这边的话简单添加一个vnc的模块的爆破实现
首先在PluginLIst中写好对应的方法映射,如下图所示
接着的话就是在Plugins目录中编写好对应的利用代码,如下图所示
Webscan插件的实现
其实主要就是分为两类,一个就是webtitle插件,一个就是其他插件,webtitle插件占据了fscan的大部分的代码,这边的话主要还是学习这个插件的实现,其中包含了指纹的识别以及漏洞扫描等实现
webtitle插件同样也是在PluginList中,如下图所示
Plugins/webtitle.go:23,这边可以看到WebTitle主要里面就是两个部分,一个是getWebInfo,一个是WebScan
其中每次WebTitle函数传入的参数都是目标的一个target站点,具体站点都是在的是在info *common.HostInfo对象中,其中包含了host字段,port字段,url字段,如下图所示
type HostInfo struct {
Host string
Ports string
Url string
Infostr []string
}
getWebInfo
getWebInfo函数实现会先请求一次目标站点进行获取相关的信息,这里的话先通过HostInfo对象中的字段拼接要请求的地址
接着就是开始请求目标站点,其中处理了比较多的情况,有如下几种
- 如果重定向的话,那么会跟随重定向
- 如果请求到的地址状态码为400的话,那么会将schema改为https进行第二次请求
在进行完了目标之后,会开始对目标进行指纹识别,这里的话会通过调用WebScan.WebInfoCheck
如果WebScan.WebInfoCheck没有识别出对应的指纹的话,那么会默认去请求/favicon.ico路径进行针对性的md5指纹识别
WebScan
上面的信息都收集完了之后就开始进行漏洞利用,如下图所示,这边就会开始调用WebScan,如下图所示
一开始的话先通过调用initpoc方法来初始化poc,默认初始化的就是在pocs目录下面
接着先通过匹配info.Infostr中的特征调用对应的poc模块,如下图所示
// 没有通过-pocname参数来指定的话,那么就是直接默认全部poc
// 这里的infostr就是前面匹配到的指纹,如果没匹配到的话,那么就是空字符串切片
for _, infostr := range info.Infostr {
// 匹配是否有关键字,如果有匹配特定系统指纹的话则就选取特定的poc来进行调度
pocinfo.PocName = lib.CheckInfoPoc(infostr)
Execute(pocinfo)
}
在Execute函数中主要就是封装要发送请求的Request对象,其中filterPoc就是来筛选对应的poc(前提是前面指纹有匹配到对应的poc模块,如果前面没有匹配到的话默认就是所有的poc)
最后的话就是开始调用CheckMultiPoc函数,启动对应worker个数的来进行监听任务队列,将即将所有分发的Task进行处理
Poc模版cel-go实现
参考文章:https://github.com/jjf012/gopoc
参考文章:https://tekton.dev/docs/triggers/cel_expressions/
参考文章:https://xz.aliyun.com/t/9857
参考文章:https://yaml.org/spec/1.2-old/spec.html#style/block/
这边随便拿一个fscan的pocs目录下的yml文件,可以看到它的实现如下所示,那么这种实现是如何实现的呢
这边用到了cel-go库,这个自己不是很了解,其实看了挺久的,但是比较复杂,后面如果还有学习的话会来补上,这边的话就直接会用就行
=补充关于cel-go=
模版添加自定义favicon md5函数
看了下fscan中并没有在poc模版中直接提供能够计算body的md5值的,所以这边自己可以尝试定义favicon_md5的函数
这边在cel-go创建环境的WebScan/lib/eval.go:97函数中添加一行如下代码,去申明一个重载函数favicon_md5_bytes,这边定义是接收一个bytes类型的函数,这边的body就是bytes类型
然后在cel.EnvOption声明中加上对应的全局函数的定义,这边的名称为favicon_md5
这样之后就可以直接在yml,yaml模版中进行使用了,这边主要测试的是xxljob的指纹,如下图所示
然后测试下该指纹是否可以进行使用,如下图所示可以看到正常可以进行识别
改造方面
同目录多模块识别
这边的话想让pocs目录下面实现这样的一个效果,如下图所示
这边的话主要修改的就是如下图所示
这边的话修改为递归查找即可,initPoc方法代码如下所示
func initPoc() {
// 默认加载的路径的话就是pocs
err := fs.WalkDir(
Pocs,
"pocs",
func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml") {
return nil
}
// 读取 poc目录+poc产品名称 下面的每个poc文件
pocEntries, err := Pocs.ReadDir(path)
if err != nil {
log.Printf("[-] init poc error: %v", err)
return err
}
var tempAllPocs []*lib.Poc
for _, poc := range pocEntries {
pocPath := fmt.Sprintf("%s/%s", path, poc.Name())
if strings.HasSuffix(pocPath, ".yaml") || strings.HasSuffix(pocPath, ".yml") {
if poc, _ := lib.LoadPoc(pocPath, Pocs); poc != nil {
tempAllPocs = append(tempAllPocs, poc)
}
}
}
if len(tempAllPocs) > 1 {
// 如果为两个以上的话,那么需要手动进行检查一次挑选出priority为1的情况
priorityFlag := true
for _, tempPoc := range tempAllPocs {
if tempPoc.Priority {
AllPocs = append(AllPocs, tempPoc)
priorityFlag = false
}
}
// 如果循环完都没有发现存在有priority的话,那么就是编写poc的人出现了问题,默认的话就直接把两个都加上好了
if priorityFlag {
for _, tempPoc := range tempAllPocs {
AllPocs = append(AllPocs, tempPoc)
}
}
} else {
// 如果个数只有1的话,那么直接添加即可解决
for _, poc := range tempAllPocs {
AllPocs = append(AllPocs, poc)
}
}
return nil
})
if err != nil {
log.Printf("[-] init poc error: %v", err)
return
}
}
这边的话用两个同目录下的poc来进行测试,分别为poc-myscan-apache-activemq.yml和poc-myscan-apache-activemq2.yml,区别就是一个存在priority字段一个不存在priority字段,如下图所示
在加载完成之后这边统一打印下加载的poc,打印代码和运行结果如下图所示
for _, pocs := range AllPocs {
fmt.Println(pocs)
}
可以看到只加载了priority为true标签的poc,这边的话就是poc-myscan-apache-activemq.yml的poc,如下图所示
支持js识别跳转
支持js识别跳转其实是遇到了一个情况,当访问nc ufida组件根路径的时候会存在如下的情况,如下图所示
可以看到访问根路径的话是存在js重定向的情况,不止这一个组件,但是自己这边的话就目前想到的就这个,其实实战中碰到了好几个这种情况了
所以这边的话需要这种情况的处理,处理类似情况的函数在fscan是在webtitle.go中的getUrl函数中,如下图所示
这边可能有人会考虑到能不能直接在yml poc层面加上index.jsp的处理,这边的其实可以,但是会比较影响效率,相当于所有的url都要请求一次index.jsp,那么就浪费了全部url请求一次地址的时间,相比直接识别出js跳转那么直接url请求一次来,添加js识别会划算的来
favicon.ico识别的处理
参考文章:qs师傅的netmap
如果所有的url的地址都请求一次/favicon.ico那么说实话相当于指纹识别方面上就花费了正常的两倍时间,那么在内网中肯定会比较浪费时间的,但是如果不favicon.ico识别的话会导致错过很多的指纹,所以这边取折中的方法,在根路径中匹配符合href=".......favicon.ico"
,如果存在的话则进行访问然后匹配关于favicon.ico的指纹
最终实现的代码如下所示
func getFaviconUrl(body string, url string) string {
var faviconPaths [][]string
var faviconUrl string
// 匹配是否存在href="....favicon" 这种格式
faviconPaths = regFavicon.FindAllStringSubmatch(body, -1)
if len(faviconPaths) > 0 {
// 如果匹配到了的话,那么就想这个favicon相关的href取出来,然后进行访问
catchFaviconPath := faviconPaths[0][1]
if catchFaviconPath[:2] == "//" {
faviconUrl = "http:" + catchFaviconPath
} else {
if catchFaviconPath[:4] == "http" {
faviconUrl = catchFaviconPath
} else {
faviconUrl = url + catchFaviconPath
}
}
} else {
// 如果没有匹配到的话,那么自己去添加一个/favicon.ico 这种形式去进行访问
faviconUrl = url + "/" + "favicon.ico"
}
return faviconUrl
}
测试,如下图所示,这边的话找yapi的站点进行测试,因为该产品的favicon.ico不是直接根路径favicon.ico,而是/image/favicon.png
这边运行进行测试如下,可以看到成功匹配到了,如下图所示
poc引擎支持detect和exec模式
目标是想实现如下的poc格式,支持两种格式,分别是detect形式和exec形式,如下图所示
为什么要实现上面这种情况的呢,目前经过上面的改造,fscan的识别和扫描流程已经是如下所示了
1、首先在webtitle扫描的时候在/根路径先匹配所有url根路径的特征
2、如果有的话就直接到对应的code或者header的指纹里面进行匹配,如果没有的话然后在/路径内容中匹配正则 href="......favicon....",然后进行拼接访问再去favicon指纹里面匹配
3、如果code或者header指纹,以及根路径中的内容没有匹配到href="...favicon..."这种情况的话,那么默认会访问/favicon.ico的指纹再去favicon指纹里面匹配
4、如果访问/favicon.ico还是没有在favicon指纹里面匹配到对应的指纹的话,那么webtitle就getWebInfo就到此为止了
5、接下来就是poc的扫描环节,因为一级目录下的情况该识别和探测的工作都已经做好了,那么接下来就是需要去考虑二级目录的情况以及漏洞利用的情况
6、在poc的detect形式下面的话就是针对二级目录的指纹进行探测,然后exec形式的话主要就是对漏洞的利用
这里想要修改支持detect形式和exec形式的话,需要对cel-go进行学习和了解,主要修改的代码是在executepoc函数中
仅detect探测,执行效果如下图所示
仅exec利用,执行效果如下图所示
默认的话就是attack模式,包含了detect模式和exec模式,执行效果如下图所示
优化指纹识别与利用模式之间的流程
利用模式detect的时候
- 当前url如果在访问根路径的时候指纹没有识别出来,需要使用所有poc的detect指纹探测步骤
这里拿naocs的组件来进行测试,下面进行detect模式的探测,在根路径没有识别出来的情况下,会发送所有poc的detect指纹探测
- 当前url如果在访问根路径的时候指纹已经识别出来,需要跳过所有poc的detect指纹探测步骤
下面进行detect模式的探测,在根路径识别出来的情况下,仅使用了一个包,后面的模块的detect都不会用到
利用模式exec的时候
- 当前url如果在访问根路径的时候指纹没有识别出来,需要跳过所有的detect指纹探测步骤,直接使用所有poc的exec步骤进行利用
这里拿nacos组件来进行测试,在根路径指纹没有识别出来是,那么将调用所有poc的exec步骤进行测试
- 当前url如果在访问根路径的时候指纹已经识别出来,需要跳过所有poc的detect指纹探测步骤,直接匹配对应指纹的poc的exec步骤进行利用
这里拿activemq组件来进行测试,在抓包过程中就看到两个包,分别就是识别和对应poc的exec模块,直接跳过了其他poc的detect指纹探测步骤
利用模式attack的时候
- 当前url如果在访问根路径的时候指纹没有识别出来,需要执行所有poc的detect指纹探测步骤,如果detect指纹存在那么继续进行exec的模式
这里拿nacos组件进行测试,可以看到把所有的detect指纹都进行了探测,识别到了nacos组件和xxljob组件之后会进行exec的利用
- 当前url如果在访问根路径的时候指纹已经识别出来,需要跳过所有的poc的detect指纹探测步骤,直接匹配对应指纹的poc的exec步骤进行利用
这里拿activemq组件进行测试,可以看到根路径访问的时候已经识别出来了, 所以这边的话只调用对应的activemq的exec步骤进行利用,只有两个请求包
重写fscan
这个留在后面进行搞,最近有项目。。。