多引擎的资产识别、信息收集

admin 2024年12月9日11:21:07评论8 views字数 10578阅读35分15秒阅读模式
文章首发于先知社区:https://xz.aliyun.com/t/16450

近期ScopeSentryt更新了插件系统,可以用这个插件系统将一些指纹识别工具进行集成,实现"多引擎"的指纹识别功能,本文以EHole为例编写一个指纹识别插件。

除了实现指纹识别的功能,也可以集成其他的工具,并且所有收集到的数据都可以进行二次处理再存储。比如丰富资产标签,拿到资产信息之后通过编写插件去获取域名的关联信息然后放入tag中存入结果,再比如获取到url、爬虫信息时,可以对一些特定的参数进行一些特定的测试。

模块流程图:

多引擎的资产识别、信息收集
新版本可能有些许细微的变动,具体的输入输出可以查看源码的modules中每个模块的module.go文件。

高清图:https://raw.githubusercontent.com/Autumn-27/ScopeSentry/main/%E6%B5%81%E7%A8%8B%E5%9B%BE.svg

插件下载:

https://plugin.scope-sentry.top/plugin/4fb56e0137e9fda3e9c76e9446868fbb

效果:

多引擎的资产识别、信息收集

插件模板

git clone https://github.com/Autumn-27/ScopeSentry-Plugin-Template.git

插件模板中提供了AssetHandle模块和SubdomainScan模块的demo

https://github.com/Autumn-27/ScopeSentry-Plugin-Template/tree/main/plugin/AssetHandle

https://github.com/Autumn-27/ScopeSentry-Plugin-Template/tree/main/plugin/SubdomainScan

系统模块

插件系统将整个流程分为了13个模块,所以创建插件也是在这些模块下创建的。

"TargetHandler","SubdomainScan","SubdomainSecurity","AssetMapping","PortScanPreparation","PortScan","PortFingerprint","AssetHandle","URLScan","URLSecurity","WebCrawler","DirScan","VulnerabilityScan",

创建插件

首先下载插件模板

https://github.com/Autumn-27/ScopeSentry-Plugin-Template.git

在plugin目录下对应的模块,创建插件文件夹(一般为插件名)并在该文件夹下创建两个文件plugin.go、info.json

info.json

其中info.json包含插件的基本信息。parameter是插件的默认参数,help是插件的提示字符 。name需要和plugin.go中的GetName保持一致,module也需要准确。

{"help":"-finger 指纹路径 -thread 并发限制","parameter":"-finger {dict.finger.finger.json} -thread 20","name":"EHole","module":"AssetHandle","version":"v1.0","introduction":"EHole 指纹识别/EHole Fingerprint recognition"}
  • • help:参数提示信息
  • • parameter:参数
  • • name:插件名称
  • • module:模块(模块需要准确)
  • • version:版本
  • • introduction:插件的简介

plugin.go

package pluginfunc GetName()string{return""}func Install() error {returnnil}func Check() error {returnnil}func Uninstall() error {returnnil}func Execute(input interface{}, op options.PluginOption)(interface{}, error){returnnil,nil}

plugin.go文件中包名需要为plugin

plugin.go需要五个方法

GetName

返回插件名称

func GetName()string{return"EHole"}

Install

在插件加载的时候会先执行install方法,如果插件需要下载一些可执行文件那么可以在Install方法中写。

func Install() error {returnnil}

Check

执行完Install方法后会执行check方法,如果有些插件需要一些验证可以在该方法内完成

func Check() error {returnnil}

Uninstall

当删除插件的时候会执行Uninstall方法

func Uninstall() error {returnnil}

Execute

插件实际上运行的就是Execute方法,该方法接收两个参数input interface{}和 op options.PluginOption

PluginOption的定义在

https://github.com/Autumn-27/ScopeSentry-Plugin-Template/blob/main/internal/options/plugin.go

type PluginOptionstruct{NamestringModulestringParameterstringPluginIdstringResultFunc func(interface{})Custominterface{}TaskIdstringTaskNamestringLog        func(msg string, tp ...string)Ctx        context.Context}

Name 为插件名称

Module 为插件所属模块

Parameter 为插件参数

在创建插件时,填写参数信息可以使用{}来引用字典和端口

{dict.subdomain.default} 这个参数会替换为字典中subdomain类别的default名称的文件id

{port.top1000} 这个参数会替换为端口管理处的name为top1000的端口

参数解析参考(调用工具类的ParseArgs方法):

args, err := utils.Tools.ParseArgs(parameter,"cdncheck","screenshot")if err !=nil{}else{for key, value := range args {if value !=""{switch key {case"cdncheck":          cdncheck = valuecase"screenshot":if value =="true"{            screenshot =true}default:continue}}}}

PluginId 插件id

ResultFunc 插件结果回调函数

比较特殊的是AssetHandle、PortScanPreparation、PortFingerprint模块,input输入为引用,直接修改input就是对结果的修改。

其余模块在获取到结果之后调用op.ResultFunc回调函数来返回结果

Custom 自定义的参数,未来用来拓展

TaskId 任务id

可以利用任务id来进行全局的去重

TaskName 任务名称

Log 日志打印函数

op.Log("test")// info类型日志op.Log("test","e")// error类型日志op.Log("test","d")// debug类型日志op.Log("test","w")// warning类型日志

插件可以调用的内置方法

目前由于插件系统的限制,无法调用第三方的库,所以在系统中内置了一些方法,可供调用,具体如下:github.com/Autumn-27/ScopeSentry-Scan/pkg/logger    // 日志包github.com/Autumn-27/ScopeSentry-Scan/pkg/utils     // 工具包,提供了一系列dns、request、tools方法github.com/Autumn-27/ScopeSentry-Scan/internal/options  github.com/Autumn-27/ScopeSentry-Scan/internal/bigcache  // 缓存包,可以列用该模块进行去重 github.com/Autumn-27/ScopeSentry-Scan/internal/config   // 配置github.com/Autumn-27/ScopeSentry-Scan/internal/contextmanager  // 任务上下文github.com/Autumn-27/ScopeSentry-Scan/internal/global// 全局变量github.com/Autumn-27/ScopeSentry-Scan/internal/interfaces   github.com/Autumn-27/ScopeSentry-Scan/internal/mongodb    // mongodb连接,可以直接调用进行存储数据github.com/Autumn-27/ScopeSentry-Scan/internal/notification // 通知模块,可以进行发送通知信息github.com/Autumn-27/ScopeSentry-Scan/internal/pool    // 协程池管理,可以自定义协程模块进行全局的并发限制github.com/Autumn-27/ScopeSentry-Scan/internal/redis   // redis连接github.com/Autumn-27/ScopeSentry-Scan/internal/results   // 结果发送模块github.com/Autumn-27/ScopeSentry-Scan/internal/types

上边提到的包下边所有方法都可以调用

各模块的输入输出

参考官网的https://scope-sentry.top/guide/2ixl876k/#targethandler

需要说明的,各模块的输入和输出无需完全遵循官网的标准,例如在目标处理模块,在获取到需要处理的目标时,可以直接声明一个types.DomainSkip类型的数据,将端口设置为80,然后返回到结果处,在目标处理模块接收结果的地方判断如果数据类型不是本模块的类型,则会发送到下个模块,那么这个DomainSkip类型的数据就会一路发送到PortScan模块进行端口扫描。其余模块也类似,如果判断不是该模块的类型会直接往后发送。

其中端口扫描的结果最后都会转换成types.AssetOther 或 types.AssetHttp。这两种数据会一直发送到最后一个漏洞扫描模块,url扫描的结果以及爬虫的结果也会发送到漏洞扫描模块,只是目前漏洞扫描模块只利用了AssetOther和AssetHttp。

EHole插件编写

其实就是将EHole官方https://github.com/EdgeSecurityTeam/EHole解析规则的代码抠出来放到Execute方法中

package pluginimport("encoding/json""fmt""github.com/Autumn-27/ScopeSentry-Scan/internal/global""github.com/Autumn-27/ScopeSentry-Scan/internal/options""github.com/Autumn-27/ScopeSentry-Scan/internal/types""github.com/Autumn-27/ScopeSentry-Scan/pkg/utils""os""path/filepath""regexp""strconv""strings""sync")func GetName()string{return"EHole"}func Install() error {returnnil}func Check() error {returnnil}func Uninstall() error {returnnil}type Fingerprintsstruct{Fingerprint[]Fingerprint}type Fingerprintstruct{CmsstringMethodstringLocationstringKeyword[]string}func iskeyword(str string, keyword []string)bool{var x bool    x =truefor _, k := range keyword {if strings.Contains(str, k){            x = x &&true}else{            x = x &&false}}return x}func isregular(str string, keyword []string)bool{var x bool    x =truefor _, k := range keyword {        re := regexp.MustCompile(k)if re.Match([]byte(str)){            x = x &&true}else{            x = x &&false}}return x}func RemoveDuplicatesAndEmpty(a []string)(ret []string){    a_len := len(a)for i :=0; i < a_len; i++{if(i >0&& a[i-1]== a[i])|| len(a[i])==0{continue}        ret = append(ret, a[i])}return}func Execute(input interface{}, op options.PluginOption)(interface{}, error){// 加载EHole指纹    data, ok := input.(*types.AssetHttp)if!ok {// 说明不是http的资产,直接返回returnnil,nil}    parameter := op.Parametervar filgerFile string    thread :=20if parameter !=""{        args, err := utils.Tools.ParseArgs(parameter,"finger","thread")if err !=nil{}else{for key, value := range args {if value !=""{switch key {case"finger":                        filgerFile = valuecase"thread":                        thread, _ = strconv.Atoi(value)}}}}}if filgerFile ==""{        op.Log("EHole 指纹文件为空","w")returnnil,nil}    fingerFilePath := filepath.Join(global.DictPath, filgerFile)    content, err := os.ReadFile(fingerFilePath)if err !=nil{        op.Log(fmt.Sprintf("read finger error: %v", err))returnnil,nil}var fingers Fingerprints    err = json.Unmarshal(content,&fingers)if err !=nil{        op.Log(fmt.Sprintf("json to fingers error: %v", err))returnnil,nil}    semaphore := make(chan struct{}, thread)var wg sync.WaitGroup// 使用 sync.Map 来保证并发安全    uniqueCms := sync.Map{}for _, finp := range fingers.Fingerprint{select{case<-op.Ctx.Done():breakdefault:            semaphore <-struct{}{}// 占用一个槽,限制并发数量            wg.Add(1)            go func(finger Fingerprint){                defer func(){<-semaphore // 释放一个槽,允许新的goroutine开始                    wg.Done()}()if finger.Location=="body"{if finger.Method=="keyword"{if iskeyword(data.ResponseBody, finger.Keyword){// 使用 sync.Map 进行并发安全操作                            uniqueCms.Store(finger.Cms,true)}}if finger.Method=="faviconhash"{if data.FavIconMMH3== finger.Keyword[0]{                            uniqueCms.Store(finger.Cms,true)}}if finger.Method=="regular"{if isregular(data.ResponseBody, finger.Keyword){                            uniqueCms.Store(finger.Cms,true)}}}if finger.Location=="header"{if finger.Method=="keyword"{if iskeyword(data.RawHeaders, finger.Keyword){                            uniqueCms.Store(finger.Cms,true)}}if finger.Method=="regular"{if isregular(data.RawHeaders, finger.Keyword){                            uniqueCms.Store(finger.Cms,true)}}}if finger.Location=="title"{if finger.Method=="keyword"{if iskeyword(data.Title, finger.Keyword){                            uniqueCms.Store(finger.Cms,true)}}if finger.Method=="regular"{if isregular(data.Title, finger.Keyword){                            uniqueCms.Store(finger.Cms,true)}}}}(finp)}}    wg.Wait()// 等待所有goroutine完成// 从 sync.Map 中获取去重后的结果var result []string    existingMap := make(map[string]bool)// 用于忽略大小写去重// 合并 data.Technologies 中的元素for _, v := range data.Technologies{        lowerV := strings.ToLower(v)// 转换为小写进行去重if _, exists := existingMap[lowerV];!exists {            result = append(result, v)// 保持原始大小写            existingMap[lowerV]=true// 标记该元素已添加}}// 将 uniqueCms 中的结果加入到 Technologies 中    uniqueCms.Range(func(key, value interface{})bool{        cms := key.(string)        lowerCms := strings.ToLower(cms)// 转换为小写进行去重if _, exists := existingMap[lowerCms];!exists {            result = append(result, cms)// 保持原始大小写            existingMap[lowerCms]=true// 标记该元素已添加}returntrue})// 将去重后的结果赋值给 Technologies    data.Technologies= resultreturnnil,nil}

主要是增加了一个参数用来设置规则文件以及并发设置

parameter := op.Parametervar filgerFile string    thread :=20if parameter !=""{        args, err := utils.Tools.ParseArgs(parameter,"finger","thread")if err !=nil{}else{for key, value := range args {if value !=""{switch key {case"finger":                        filgerFile = valuecase"thread":                        thread, _ = strconv.Atoi(value)}}}}}

插件调试

模拟实际环境运行插件

插件是实现是利用yaegi实现的

在plugin_cmd.go中编写测试用例

func main(){global.DatabaseEnabled=falseInit()global.AppConfig.Debug=false    _, filePath, _, ok := runtime.Caller(0)if!ok {        log.Fatalf("无法获取当前文件路径")}    parentDir := filepath.Dir(filePath)    plgPath := filepath.Join(parentDir,"..","..","plugin")    fmt.Println(plgPath)}

这段代码不需要动,用来找到插件所在的相对路径。

这段代码是使用yaegi来加载些的差劲

func TestEHole(plgPath string){// plugin id    plgId := utils.Tools.GenerateRandomString(8)// plugin module name    plgModule :="AssetHandle"// plugin path    plgPath = filepath.Join(plgPath,"AssetHandle","ehole","plugin.go")    plugin, err := plugins.LoadCustomPlugin(plgPath, plgModule, plgId)if err !=nil{return}    fmt.Printf("plugin name: %vn", plugin.GetName())    fmt.Printf("plugin module: %vn", plugin.GetModule())    fmt.Printf("plugin id: %vn", plugin.GetPluginId())    result := make(chan interface{})// 设置插件参数    plugin.SetParameter("-finger dwa -thread 20")// 设置任务id    plugin.SetTaskId("1111")// 设置任务名称    plugin.SetTaskName("demo")// 设置结果输出chan    plugin.SetResult(result)    fmt.Printf("AssetHttpData original Technologies: %vn",AssetHttpData.Technologies)// 调用执行插件,这里也就是执行插件的Execute,这里和插件不同只需要input,另一个参数实际上是程序自动帮助完成的  _, err = plugin.Execute(&AssetHttpData)if err !=nil{return}    fmt.Printf("AssetHttpData Technologies%vn",AssetHttpData.Technologies)}

可调式的测试用例

上边的测试样例是使用yaegi进行加载的,也就是按照实际插件运行的方式来调用插件,但是这样的话就无法对插件进行调试,所以还需要一个可以调试的测试样例,非常简单,直接调用即可

import(    plugin "github.com/Autumn-27/ScopeSentry-Scan/plugin/AssetHandle/ehole")// 引入插件,重命名为plugin// 手动设置op参数,然后直接调用插件的Execute方法func TestEHoleDebug(){    op := options.PluginOption{Name:"EHole",Module:"AssetHandle",Parameter:"-finger dwa -thread 20",PluginId:"11111",Ctx:       contextmanager.GlobalContextManagers.GetContext("111111"),}    plugin.Execute(&AssetHttpData, op)}

原文始发于微信公众号(SecSentry):多引擎的资产识别、信息收集

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年12月9日11:21:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   多引擎的资产识别、信息收集https://cn-sec.com/archives/3485020.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息