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来展开,通过版本的更新也能从中学习到蜜罐开发的思路。

本文详细剖析了HFish项目源码,重点围绕Gin框架的路由、处理函数,配置文件的使用,以及RPC服务的判断。作者通过调试控制台和项目目录解读,揭示了项目的核心组件和工作流程,包括蜜罐实现、API接口和模板渲染等。
2106

被折叠的 条评论
为什么被折叠?



