安全研发启蒙课:低成本实现的被动扫描工具
背景
之前有师傅提过,怎么在关闭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)
},
))
运行脚本测试下yak test.yak
浏览器设置代理并访问百度,如图收到请求
发现回调函数是同步运行的,回调函数内的代码运行会导致网页请求卡住。在介绍被动扫描原理时说过,这里应该是镜像一份流量发送给被动扫描插件,所以这里需要做一些优化,目标是不影响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插件、端口扫描插件等,师傅们可以按需调用相应插件。
更新通知!
往期推荐
Web Fuzzer 高级进阶:支持前端 AES-ECB 加密Web 安全测试实战
CVE-2020-2551 深入 IIOP 检测与 Non-Java(Yak) 利用