yaklang
- 关注
背景
之前有师傅提过,怎么在关闭Yakit时让Yak引擎在后台运行,只使用被动扫描功能,这样就可以节省前端的性能损耗。
这个需求用Yaklang就完全可以实现,实际上Yakit的大部分功能也是通过Yaklang编写的,下面就详细介绍下如何使用Yaklang编写一个被动扫描脚本。
被动扫描原理
被动扫描的流程如图,MITM指中间人,在被动扫描过程中起到类似HTTP代理的作用。在浏览器中将代理设置为MITM Server地址,那浏览器的所有Web请求都将请求到MITM Server,然后MITM去请求目标网站,再将网站响应返回给浏览器,完成一次代理请求。
MITM在代理浏览器请求目标网站,等待目标网站响应时,镜像了一份请求,发给Hook模块,Hook模块加载被动扫描插件,将请求参数传入扫描插件,对本次请求的网站做扫描。用户只需要通过浏览器访问目标网站即可实现漏洞扫描。
概览整个流程,MITM Server的作用只是获取到浏览器的请求,发送给Hook,那不使用这种HTTP代理的方式获取浏览器请求,抓网卡的HTTP流量,发送给Hook行不行?不行,因为HTTPS的请求是加密的,网卡抓到的流量是加密后的请求,无法解析。而MITM作为一个HTTP代理,既可以作为服务端与客户端交互(所以需要安装证书,解决HTTPS请求的问题),也可以作为客户端与目标网站交互,可以获取到整个流程的所有明文数据。
函数介绍
了解了被动扫描原理后,再看下Yaklang提供了那些库函数可以实现上述流程,如图是hook库和mitm库
mitm.Start()
可以让我们启动一个mitm server,通过mitm.callback()
设置回调,所有的流量都会经过这个回调函数,这样我们就可以获取所有请求流量,然后调用mitm插件做漏洞扫描。
hook.NewMixPluginCaller()
方法会创建一个manager
,暂且将这个manager
理解为插件管理对象吧,刚创建的manager是空的,需要通过LoadPlugin方法通过插件名加载插件。
下一步就是获取到所有mitm插件。yakit的插件仓库的本地插件都存在本地数据库中,所以我们可以调用db.YieldYakScriptAll()
方法获取所有插件。例
println("所有MITM插件") yakScriptsChan = db.YieldYakScriptAll() for yakscript = range yakScriptsChan{ if yakscript.Type == "mitm"{ println(yakscript.ScriptName) } } // 所有MITM插件 // 参数发现 // Dog // 被动指纹检测 // 基础 XSS 检测 // 敏感信息获取 // 基础 SQL 注入检测:No Protection // ThinkPHP RCE 被动扫描 // SSRF HTTP Public // Spring Actuator 敏感信息泄漏 // Shiro 指纹识别 + 弱密码检测 // ......
编写脚本
有了上述库函数的支持,我们就可以开始编写脚本了
首先创建一个manager
// 创建manager manager, err = hook.NewMixPluginCaller() if err != nil { log.Info("build mix plugin caller failed: %s", err) die(err) }
获取所有插件,并加载到manager
// 从数据库获取所有插件 yakScriptsChan = db.YieldYakScriptAll() // 筛选mitm插件 for yakscript = range yakScriptsChan{ if yakscript.Type == "mitm"{ manager.LoadPlugin(yakscript.ScriptName) } }
启动MITM Server,这里使用的默认证书,也就是yakit的证书,免得再装证书了。
// 启动MITM Server mitm.Start(8083,mitm.useDefaultCA(true),mitm.callback( func(isHttps,url,req,rsp){ log.info("检测到请求: %s",url) }, ))
镜像流量
发现回调函数是同步运行的,回调函数内的代码运行会导致网页请求卡住。在介绍被动扫描原理时说过,这里应该是镜像一份流量发送给被动扫描插件,所以这里需要做一些优化,目标是不影响mitm server做代理。
channel = make(chan []var) for i = 0; i < 30; i+=1{ go fn(){ for c = range channel{ isHttps,url,req,rsp = c log.info("检测到请求: %s",url) } }() } // 启动MITM Server mitm.Start(port,mitm.useDefaultCA(true),mitm.callback( func(isHttps,url,req,rsp){ channel <- [isHttps,url,req,rsp] }, ))
调用MITM插件
manager.MirrorHTTPFlowEx(isScanPort,isHttps,url,req,rsp,body)
方法可以调用所有已经加载的MITM插件,设置第一个参数可以启用端口扫描,剩余参数和MITM插件的参数一样。
// 因为回调函数的req和rsp类型是go的原生类型*http.Request和*http.Response类型 req,err = http.dump(req) if err!= nil{ log.error(err) return } rsp,err = http.dump(rsp) if err!= nil{ log.error(err) return } body,err = str.ExtractBodyFromHTTPResponseRaw(rsp) if err!= nil{ log.error(err) return } manager.MirrorHTTPFlowEx(false,isHttps,url,req,rsp,body)
优化日志信息
调用插件时发现有些MITM插件的输出使用yakit_ouput
方法,不能在控制台输出,实际上yakit_ouput
也是通过hook,将参数传给Feedback
方法输出信息。manager提供了一个SetFeedback
方法,通过自定义Feedback
就可以自定义输出信息,如下:
manager.SetFeedback(func(i){ msg = json.loads(i.Message) data = msg.content.data level = msg.content.level switch msg.content.level{ case "info": log.info(data) case "error": log.error(data) default: log.info("收到信息,不支持的信息类型: [%s] %s",level,data) } })
最终脚本
yakit.AutoInitYakit() // 设置日志级别 loglevel("info") // 参数 port = cli.Int("port", cli.setRequired(true),cli.setDefault(8083)) // 创建manager manager, err = hook.NewMixPluginCaller() if err != nil { log.Info("build mix plugin caller failed: %s", err) die(err) } // 设置feedback manager.SetFeedback(func(i){ msg = json.loads(i.Message) data = msg.content.data level = msg.content.level switch msg.content.level{ case "info": log.info(data) case "error": log.error(data) default: log.info("收到信息,不支持的信息类型: [%s] %s",level,data) } }) // 加载插件 yakScriptsChan = db.YieldYakScriptAll() for yakscript = range yakScriptsChan{ if yakscript.Type == "mitm"{ manager.LoadPlugin(yakscript.ScriptName) } } // 启动MITM Server channel = make(chan []var) for i = 0; i < 30; i+=1{ go fn(){ for c = range channel{ isHttps,url,req,rsp = c req,err = http.dump(req) if err!= nil{ log.error(err) return } rsp,err = http.dump(rsp) if err!= nil{ log.error(err) return } body,err = str.ExtractBodyFromHTTPResponseRaw(rsp) if err!= nil{ log.error(err) return } manager.MirrorHTTPFlowEx(false,isHttps,url,req,rsp,body) } }() } // 启动MITM Server mitm.Start(port,mitm.useDefaultCA(true),mitm.callback( func(isHttps,url,req,rsp){ channel <- [isHttps,url,req,rsp] }, ))
测试脚本
运行脚本,默认代理开在8083端口。跑出来的漏洞可以在Yakit的风险与漏洞中看,如图
总结
这个简陋的脚本只是一个演示,除了可以调用mitm插件,manager.MirrorHTTPFlowEx
还可以调用Nuclei插件、Yak插件、端口扫描插件等,师傅们可以按需调用相应插件。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)