为什么选择Nuclei
- 目前市面上有很多POC验证框架,比如:nuclei,xray,pocsuite3等等。
项目 |
开发语言 |
是否开源 |
开源协议 |
社区范围 |
nuclei |
Go |
开源 |
MIT |
有官方维护项目,贡献者:全球白帽子 |
xray |
Go |
闭源 |
无 |
有官方维护项目,贡献者:主要为国内白帽子 |
pocsuite3 |
Python3 |
开源 |
GPL |
官方维护但不公开,社区分散 |
- 首先如果工具考虑商用,只能选择nuclei,从开源协议上来讲MIT开源协议有着最宽松的政策,而且有着活跃的社区贡献,编写POC简单,但是使用Yaml这种方式编写POC注定处理不了复杂的逻辑,所以高级脚本引擎也在也在开发计划内,希望能尽快发布。
- 如果你的工具也是开源的可以选择pocsuite3,但是开源协议不能改,python语言开发能够处理更加复杂的漏洞逻辑,但是也增加了编写POC的成本,也需要python运行环境,而且有一个非常致命的问题是安装它的时候会被杀毒软件删除几个文件。
- xray的被动扫描,和爬虫引擎都是其他两个没有的,但是集成困难,只能调用命令行。
- 总的来说:谁不喜欢白嫖呢?
集成Nuclei
- 调用nuclei的引擎需要先设置基础的配置,默认配置在单元测试文件中可以复制。
defaultOpts := &types.Options{
RateLimit: 10,
BulkSize: 10,
TemplateThreads: 10,
HeadlessBulkSize: 10,
HeadlessTemplateThreads: 10,
Timeout: MyNucleiConfig.Timeout,
Retries: 1,
MaxHostError: 30,
InteractshURL: MyNucleiConfig.Iserver,
NoUpdateTemplates: true,
NoColor: true,
Validate: false,
UpdateTemplates: false,
Debug: false,
Verbose: false,
EnableProgressBar: false,
UpdateNuclei: false,
Silent: true,
Headless: false,
JSON: true,
JSONRequests: true,
CustomHeaders: []string{"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.64"},
VerboseVerbose: false,
}
- 因为我们是直接使用go代码去调用nuclei的POC验证引擎,所以我们不希望他去更新主程序模板和安装无头浏览器,也不想要打印过程,只需要拿到json结果就可以了,这里有些参数需要自定义的我新建了一个MyNucleiConfig结构方便设置。
- 直接调用和使用命令行调用还是有些不同的,使用命令行调用完了,进程结束结果输出到json文件,调用程序再去解析json结果,在一次执行完成后内存都释放掉了,但是直接集成在代码调用可以会出现内存占用过多而被系统OOM(out of memory)自我保护将进程kill掉,所以并发需要调节小一点。
- nuclei的结果输出需要一个回调函数将
output.ResultEvent
接出来直接序列化得到json数据,非常方便。
package MuniuNucleiScan
import (
"log"
"os"
"github.com/logrusorgru/aurora"
"github.com/projectdiscovery/nuclei/v2/pkg/catalog"
"github.com/projectdiscovery/nuclei/v2/pkg/core"
"github.com/projectdiscovery/nuclei/v2/pkg/core/inputs"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolinit"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
"github.com/projectdiscovery/nuclei/v2/pkg/types"
"go.uber.org/ratelimit"
)
type NucleiConfig struct {
Targets []string
TemplatePaths []string
Iserver string
Timeout int
}
func Nuclei(MyNucleiConfig NucleiConfig, outputWriter *OutputWriter) {
cache := hosterrorscache.New(30, hosterrorscache.DefaultMaxHostsCount)
defer cache.Close()
MuniuProgress := &ProgressClient{}
defaultOpts := &types.Options{
RateLimit: 10,
BulkSize: 10,
TemplateThreads: 10,
HeadlessBulkSize: 10,
HeadlessTemplateThreads: 10,
Timeout: MyNucleiConfig.Timeout,
Retries: 1,
MaxHostError: 30,
InteractshURL: MyNucleiConfig.Iserver,
NoUpdateTemplates: true,
NoColor: true,
Validate: false,
UpdateTemplates: false,
Debug: false,
Verbose: false,
EnableProgressBar: false,
UpdateNuclei: false,
Silent: true,
Headless: false,
JSON: true,
JSONRequests: true,
CustomHeaders: []string{"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.64"},
VerboseVerbose: false,
}
protocolstate.Init(defaultOpts)
protocolinit.Init(defaultOpts)
interactOpts := interactsh.NewDefaultOptions(outputWriter, nil, MuniuProgress)
interactOpts.ServerURL = MyNucleiConfig.Iserver
interactClient, err := interactsh.New(interactOpts)
if err != nil {
log.Fatalf("Could not create interact client: %s\\n", err)
}
BasePath, _ := os.Getwd()
catalog := catalog.New(BasePath)
defer interactClient.Close()
executerOpts := protocols.ExecuterOptions{
Output: outputWriter,
Options: defaultOpts,
Catalog: catalog,
ResumeCfg: types.NewResumeCfg(),
Progress: MuniuProgress,
RateLimiter: ratelimit.New(10),
Interactsh: interactClient,
HostErrorsCache: cache,
Colorizer: aurora.NewAurora(true),
}
engine := core.New(defaultOpts)
engine.SetExecuterOptions(executerOpts)
input := &inputs.SimpleInputProvider{
Inputs: MyNucleiConfig.Targets,
}
var template_list = make([]*templates.Template, 0)
template_list = LoadTemplates(MyNucleiConfig.TemplatePaths, executerOpts)
_ = engine.Execute(template_list, input)
}
- 模板载入也可以自定义重写
Parse
方法后可以对POC进行解密后加载等等。
调用Nuclei
- 在生产环境下,经常会有上千个目标,万一这一千个目标都有漏洞,而且每个目标不止命中一个POC,再极端的返回大响应,比如像springboot的env和dump这种响应,会把占用大量内存,所以需要将大于10kb的响应截断,不然又会被系统OOM(out of memory)。
res := make([]*output.ResultEvent, 0)
templatePaths := []string{
"thinkphp/thinkphp-5023-rce.nuclei.yaml",
}
targets := []string{"<http://172.19.0.2>"}
MyNucleiConfig := NucleiConfig{
Targets: targets,
TemplatePaths: templatePaths,
Timeout: lib.Config.ScanConfig.Timeout,
Iserver: lib.Config.ScanConfig.Iserver,
}
// 使用nuclei内置的输出流接收结果
outputWriter := NewOutputWriter()
outputWriter.WriteCallback = func(event *output.ResultEvent) {
if len(event.Response) > 10240 {
event.Response = event.Response[:10240]
}
res = append(res, event)
}
Nuclei(MyNucleiConfig, outputWriter)
s, _ := json.Marshal(res)
fmt.Println(string(s))