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识别跳转

参考文章:https://github.com/niudaii/zpscan/blob/14e816cb12c04f5ac64cf0cdc3c8c8dc90f7ac97/pkg/webscan/jsjump.go

支持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

这个留在后面进行搞,最近有项目。。。

posted @ 2023-11-21 00:39  zpchcbd  阅读(1794)  评论(0编辑  收藏  举报