HFish源码浅析(二)
上一篇文章只说到了配置文件和读取配置文件相关的函数与结构,以及Run
函数,但是并没有对Run
函数进行深入,本篇文章就通过Run
函数和项目目录作为切入点,对HFish
的源码再作进一步的分析。
补充一下
这里补充一下上一篇遗漏的,我们通过查看项目的go.mod
文件内容,可以得知该项目使用的是Gin
框架,众所周知Gin
框架在易用性、可扩展性、开源社区方面都表现得非常优秀。
module HFish
go 1.12
require (
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/gin-gonic/gin v1.4.0
github.com/gliderlabs/ssh v0.2.2
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869
github.com/mattn/go-sqlite3 v1.11.0
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
)
这里针对这些依赖项做一下解读:
go-shlex
使用了两个不同开发者的shlex
库,它的功能主要是解析和操作命令行参数;mahonia
是一个字符编码转换库,例如用来处理不同字符编码gbk
或者utf-8
之间的转换;ssh
这个库估计是SSH
蜜罐使用到的,用于创建SSH
服务器或客户端应用程序;go-strftime
此依赖库主要用来格式化时间字符串;go-sqlite3
是用来与SQLite3
数据库进行交互;quotedprintable.v3
这个依赖库的主要功能也是关于编解码的操作;gomail.v2
这个应该是专门用来发送电子邮件,主要使用在邮箱的蜜罐功能上面
将调试控制台信息作为切入点分析HFish
程序开始运行的时候通过调试控制台可以看到下面的信息
HFish的路由以及相对应的处理函数
上图显示的路由方法,是Gin
框架的核心,通过项目的源码可以查看到对应的控制器(Controller)
和处理器(Handler)
,HFish
是使用Gin
框架来编写GET
、POST
等方法的控制器函数,来处理HTTP
响应。
接下来通过调试窗口查看栈调用信息:
下面是程序的流程图(针对Run
函数做了一定的简略操作)
以/get/fish/list
路由为例,找到处理它们这些路由方法的上一层函数LoadUrl
下面是LoadUrl
函数的源码:
func LoadUrl(r *gin.Engine) {
// 判断是否为 RPC 客户端
if is.Rpc() {
/* RPC 客户端 */
// API 接口
// WEB 上报蜜罐信息
apiStatus := conf.Get("api", "status")
// 判断 API 是否启用
if apiStatus == "1" {
r.Use(cors())
webUrl := conf.Get("api", "web_url")
deepUrl := conf.Get("api", "deep_url")
r.POST(webUrl, api.ReportWeb)
r.POST(deepUrl, api.ReportDeepWeb)
}
} else {
/* RPC 服务端 */
// 登录
r.GET("/login", login.Html)
r.POST("/login", login.Login)
r.GET("/logout", login.Logout)
// 仪表盘
r.GET("/", login.Jump, dashboard.Html)
r.GET("/dashboard", login.Jump, dashboard.Html)
r.GET("/get/dashboard/data", login.Jump, dashboard.GetFishData)
// 蜜罐列表
r.GET("/fish", login.Jump, fish.Html)
r.GET("/get/fish/list", login.Jump, fish.GetFishList)
r.GET("/get/fish/info", login.Jump, fish.GetFishInfo)
r.GET("/get/fish/typeList", login.Jump, fish.GetFishTypeInfo)
r.POST("/post/fish/del", login.Jump, fish.PostFishDel)
// 分布式集群
r.GET("/colony", login.Jump, colony.Html)
r.GET("/get/colony/list", login.Jump, colony.GetColony)
// 邮件群发
r.GET("/mail", login.Jump, mail.Html)
r.POST("/post/mail/sendEmail", login.Jump, mail.SendEmailToUsers)
// 设置
r.GET("/setting", login.Jump, setting.Html)
r.GET("/get/setting/info", login.Jump, setting.GetSettingInfo)
r.POST("/post/setting/update", login.Jump, setting.UpdateEmailInfo)
r.POST("/post/setting/updateAlertMail", login.Jump, setting.UpdateAlertMail)
r.POST("/post/setting/checkSetting", login.Jump, setting.UpdateStatusSetting)
// API 接口
// WEB 上报蜜罐信息
apiStatus := conf.Get("api", "status")
// 判断 API 是否启用
if apiStatus == "1" {
r.Use(cors())
webUrl := conf.Get("api", "web_url")
deepUrl := conf.Get("api", "deep_url")
r.POST(webUrl, api.ReportWeb)
r.POST(deepUrl, api.ReportDeepWeb)
r.GET("/api/v1/get/ip", api.GetIpList)
}
}
}
上一篇文章我们说过配置文件config.ini
,它最开头的一个节点是rpc
,这部分内容是集群部署才会使用到它。下面是该文件的部分摘抄内容
[rpc]
status = 0 # 模式 0关闭 1服务端 2客户端
addr = 127.0.0.1:7879 # RPC 服务端地址 or 客户端地址
name = Server # 状态1 服务端 名称 状态2 客户端 名称
Run
函数读取配置文件最关键的地方是在判断当前程序是否为Rpc
服务端或者客户端,下面代码截取自Run
函数
func Run() {
//省略代码...
// 启动 RPC
rpcStatus := conf.Get("rpc", "status")
// 判断 RPC 是否开启 1 RPC 服务端 2 RPC 客户端
if rpcStatus == "1" {
// 服务端监听地址
rpcAddr := conf.Get("rpc", "addr")
go server.Start(rpcAddr)
} else if rpcStatus == "2" {
// 客户端连接服务端
// 阻止进程,不启动 admin
rpcName := conf.Get("rpc", "name")
for {
// 这样写 提高IO读写性能
go client.Start(rpcName, ftpStatus, telnetStatus, "0", mysqlStatus, redisStatus, sshStatus, webStatus, deepStatus)
time.Sleep(time.Duration(1) * time.Minute)
}
}
// 启动 admin 管理后台
adminAddr := conf.Get("admin", "addr")
serverAdmin := &http.Server{
Addr: adminAddr,
Handler: RunAdmin(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
serverAdmin.ListenAndServe()
}
判断配置文件关于rpc
节点的status
字段,确认当前程序属于服务端节点还是客户端节点。
若值为“1”
,即表示当前程序为rpc
服务端,
若值为“2”
,即表示当前程序为rpc
客户端,下面程序会进入一个for
死循环,直至用户主动结束(按下ctrl+c)
或者程序发生错误。
HFish项目目录解读
通过项目目录来解读HFish
各个文件以及相关函数所对应的关系,下图是HFish_v0.2
完整的项目文件夹
admin文件夹
从admin
文件夹开始,从上到下逐个分析,admin
文件夹下都是管理后台的模板文件
在HFish/view/url.go
文件下的LoadUrl
函数有对应的模板渲染,下面摘取LoadUrl
函数的源代码:
func LoadUrl(r *gin.Engine) {
// 判断是否为 RPC 客户端
if is.Rpc() {
/* RPC 客户端 */
// API 接口
// WEB 上报蜜罐信息
apiStatus := conf.Get("api", "status")
// 判断 API 是否启用
if apiStatus == "1" {
r.Use(cors())
webUrl := conf.Get("api", "web_url")
deepUrl := conf.Get("api", "deep_url")
r.POST(webUrl, api.ReportWeb)
r.POST(deepUrl, api.ReportDeepWeb)
}
} else {
/* RPC 服务端 */
// 登录
r.GET("/login", login.Html)//对应上图的管理后台登陆页面
r.POST("/login", login.Login)
r.GET("/logout", login.Logout)
// 仪表盘
r.GET("/", login.Jump, dashboard.Html)
r.GET("/dashboard", login.Jump, dashboard.Html)//管理后台仪表板,需要登陆验证
r.GET("/get/dashboard/data", login.Jump, dashboard.GetFishData)
// 蜜罐列表
r.GET("/fish", login.Jump, fish.Html)//上钩列表
r.GET("/get/fish/list", login.Jump, fish.GetFishList)
r.GET("/get/fish/info", login.Jump, fish.GetFishInfo)
r.GET("/get/fish/typeList", login.Jump, fish.GetFishTypeInfo)
r.POST("/post/fish/del", login.Jump, fish.PostFishDel)
// 分布式集群
r.GET("/colony", login.Jump, colony.Html)//分布式集群页面
r.GET("/get/colony/list", login.Jump, colony.GetColony)
// 邮件群发
r.GET("/mail", login.Jump, mail.Html)//群发邮件通知页面
r.POST("/post/mail/sendEmail", login.Jump, mail.SendEmailToUsers)
// 设置
r.GET("/setting", login.Jump, setting.Html)//管理后台系统设置页面
r.GET("/get/setting/info", login.Jump, setting.GetSettingInfo)
r.POST("/post/setting/update", login.Jump, setting.UpdateEmailInfo)
r.POST("/post/setting/updateAlertMail", login.Jump, setting.UpdateAlertMail)
r.POST("/post/setting/checkSetting", login.Jump, setting.UpdateStatusSetting)
// API 接口
// WEB 上报蜜罐信息
apiStatus := conf.Get("api", "status")
// 判断 API 是否启用
if apiStatus == "1" {
r.Use(cors())
webUrl := conf.Get("api", "web_url")
deepUrl := conf.Get("api", "deep_url")
r.POST(webUrl, api.ReportWeb)
r.POST(deepUrl, api.ReportDeepWeb)
r.GET("/api/v1/get/ip", api.GetIpList)
}
}
}
core文件夹
和它的名称一样,是HFish
这个项目的核心文件,它的下面又分几个子文件夹,分别是:
dbUtil
子文件夹exec
子文件夹protocol
子文件夹report
子文件夹rpc
子文件夹
dbUtil
主要是操作数据库的一些常用工具函数,CURD
这些代码,为了避免本文内容篇幅过长,具体的代码细节这里就不展开了
exec
这个文件夹里面就一个Execute
函数,是一个调用系统名命令的接口函数,在这里主要是实现程序的help
参数
protocol
该子文件夹里包含的文件是HFish
各个蜜罐实现的关键。
关于它的httpx
子文件夹在这个版本并没有使用到,所以就不对它最具体的展开了
FTP蜜罐
展开ftp
这个文件夹,它对应的信息一下子就很清晰了
这里可以确定HFish
的ftp
蜜罐使用了一个叫graval
的第三方库,很明显它用于构建FTP
服务器。
下面调试一下它,关于攻击者触发蜜罐执行上报的流程
这里FTP
的蜜罐和SSH
不一样,它的上报函数是下面这两个,主要是通过判断是否为RPC
节点然后去确定使用哪一个:
在网站的后台管理也有上报的记录
mysql蜜罐
通过调试设置断点,确定这里是mysql
蜜罐的关键上报函数
这里对应蜜罐管理后台的返回信息
它大致的程序执行流程图是这样子的
Redis蜜罐
本地尝试连接redis
服务的时候会触发HFish
的蜜罐上报函数,它触发的调用顺序如下图所示
Redis
蜜罐的程序执行流程和MySql
类似,这里就不过多赘述了。
SSH蜜罐
再以SSH
蜜罐为例,goland
在kun_-hfish\core\protocol\ssh\ssh.go
文件下的Start
函数设置调试断点,通过调试可以看到,我输入的密码是123
从上面的代码可以得知,不管用户(这里角色应该是攻击者更恰当些)输入的是什么,程序都会将其记录日志,并且上报到蜜罐后台report.ReportSSH
函数就是实现上报功能的模块。
打开Admin
后台查看上报情况
telnet蜜罐
telnet
蜜罐的执行流程图如下所示
report
这个文件夹里的函数都是涉及到蜜罐的上报处理的,不管是集群部署还是单机部署都会调用到这里的上报函数
rpc
这部分是集群部署的时候会使用到,分为客户端和服务端的代码。
error文件夹
这里是使用gin
框架的特点来返回错误信息
一般是在用户查看管理后台仪表盘时执行统计信息
static文件夹
和大部分的web
框架一样,这个static
在gin
框架中主要是用于存放静态文件的,如下图所示,例如 JavaScript
、CSS
、图像等。
utils文件夹
顾名思义工具类,封装的都是项目里面常用的函数
例如打印控制台信息的字体颜色
又例如下面这个获取攻击者ip
的函数
还有下面这些记录日志的操作,等等
view文件夹
攻击者测试WEB
蜜罐后台登陆功能的时候
会触发ReportWeb
函数
这个文件夹主要是处理蜜罐管理后台各个页面的数据信息
web文件夹
该文件夹主要用来存放WEB
蜜罐和WEBDEEP
蜜罐的静态资源文件
HFish的模板渲染
在Gin
框架中使用LoadHTMLGlob
对多个模板进行统一渲染,HFish
中自然也使用到了。如下图:
上面代码分别是下面这三个函数使用到了模板渲染:
func RunWeb(template string, index string, static string, url string) http.Handler;
func RunDeep(template string, index string, static string, url string) http.Handler;
func RunAdmin() http.Handler;
未完待续…
下一篇文章打算针对HFish
最后开源的版本HFish0.6
来展开,通过版本的更新也能从中学习到蜜罐开发的思路。