Files
kenaito-conf/server/bug20261015.md
T
odboy 932db9fbdc fix: 修复多个安全缺陷和功能问题
- 移除加密相关代码(服务端和所有客户端)
- 修复 JWT 验证未检查签名算法的问题
- 修复重置密码使用不安全随机数的问题
- 实现 Logout 服务端失效机制
- 修复历史版本对比 diff 颜色标记错误
- 修复占位符解析循环引用风险
- 添加用户禁用状态检查
- 更新 bug 清单文档
2026-03-10 21:07:22 +08:00

6.8 KiB

Server 代码缺陷清单 — 2026-10-15

🔴 严重缺陷(安全/数据风险)

1. encryptIfRequired 是空实现 — 敏感数据未加密

文件: internal/handler/config.go

CryptoService 已完整实现了 AES 加解密,但 Add 和 Update 配置时调用的 encryptIfRequired 是空函数,所有敏感配置值以明文存储。

func (h *ConfigHandler) encryptIfRequired(config *model.AppConfig) {
    // simplified: check if contents contain sensitive keys
    // In production, parse the config and encrypt values with {Cipher} prefix
    // For now, skip encryption logic
}

2. 重置密码使用 math/rand — 可预测的伪随机数

文件: internal/handler/user.go

ResetPassword 用 math/rand 生成 8 位随机密码。math/rand 不是密码学安全的随机源,攻击者可以预测生成的密码。应使用 crypto/rand。

import "math/rand"

func randomString(n int) string {
    const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    b := make([]byte, n)
    for i := range b {
        b[i] = letters[rand.Intn(len(letters))]  // ← 不安全
    }
    return string(b)
}

3. JWT 验证未检查签名算法 — 存在 "none" 算法攻击风险

文件: internal/service/auth.go

没有检查 token.Method 是否为预期的 jwt.SigningMethodHS256,攻击者可以将算法改为 none 来伪造任意 token。

func (s *AuthService) ValidateToken(tokenString string) (*model.LoginUserInfo, error) {
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
        return []byte(s.jwtSecret), nil  // ← 未验证 token.Method
    })

4. 数据库真实凭据硬编码在配置文件中

文件: application.properties

外网 IP + 真实密码 + JWT 密钥 + 加密密钥全部明文提交到代码仓库。

database.host=103.236.98.135
database.password=?uWxt,CX2>+#*Ij@-xa+
jwt.secret=kenaito-conf-jwt-secret-2026
crypto.secretKey=k9#mP2$vL7qR4xN8

5. 加密注释声称 ECB 模式,实际使用 CBC + 零 IV

文件: internal/service/crypto.go

注释写的是 ECB,代码用的是 CBC(IV 全零)。如果 Java 端真的是 ECB,则两端加解密不兼容。且 CBC + 零 IV 等于 ECB 的安全性。

// ECB mode (compatible with Java version)
mode := cipher.NewCBCEncrypter(block, make([]byte, aes.BlockSize))

🟠 重要缺陷(功能/逻辑错误)

6. Logout 不做任何服务端失效处理

文件: internal/handler/auth.go

JWT 在过期前始终有效,登出后 token 仍可使用。

func (h *AuthHandler Logout(c *gin.Context) {
    c.JSON(http.StatusOK, model.WrapperResponse{Code: 0})
}

9. ValidateToken 不检查用户是否已被禁用

文件: internal/service/auth.go

用户被管理员禁用后,已签发的 JWT 在过期前仍然有效。

has, err := dao.Engine.ID(userId).Get(&user)
if err != nil || !has {
    return nil, errors.New("user not found")
}
// ← 没有检查 user.Enabled

10. 占位符解析存在循环引用风险

文件: internal/service/config_parse.go

没有检测循环引用,恶意或错误的配置(如 A=${B}, B=${A})会导致 CPU 空转 100 次迭代。

func (s *ConfigParseService) replacePlaceholder(...) string {
    maxIterations := 100
    for i := 0; i < maxIterations; i++ {
        // 循环引用时无限迭代
    }
}

11. FetchAllConfigs / FetchChangedConfigs 未强制要求 Token

文件: internal/handler/config_api.go

当 app.Token 为空时,任何请求都能拉取配置,无需认证。如果应用未配置 Token,则配置接口完全开放。

if app.Token != "" && app.Token != req.Token {
    c.JSON(http.StatusForbidden, ...)
    return
}
// Token 为空时直接放行

🟡 一般缺陷(代码质量/健壮性)

12. 数据库操作错误被大规模忽略

整个代码库中大量数据库操作结果被忽略,包括关键的 Insert、Update、Count 操作:

文件 示例
handler/app.go dao.Engine.Insert(&app) — 插入失败无感知
handler/config.go dao.Engine.Insert(&history) — 历史记录保存失败无感知
handler/user.go dao.Engine.Insert(&user) — 用户创建失败无感知
handler/config_api.go dao.Engine.Where(...).Find(&globalConfigs) — 查询失败返回空结果

13. Token 校验使用字符串直接比较 — 时序攻击

文件: internal/handler/config_api.go

应使用 crypto/subtle.ConstantTimeCompare 防止时序攻击。

if app.Token != "" && app.Token != req.Token {

14. Delete 用户是硬删除,与其他模块软删除不一致

文件: internal/handler/user.go

而 App、Config 等都是 enabled = false 软删除。硬删除用户后,关联的 user_permissions 记录和 created_by 引用会变成孤儿数据。

dao.Engine.ID(id).Delete(&model.User{})  // 物理删除

15. AppHandler.List 存在 N+1 查询

文件: internal/handler/app.go

加载 group names 时,每个 group 单独查询一次,应改为 WHERE id IN (...) 批量查询。

for gid := range groupIds {
    var group model.BusinessGroup
    if has, _ := dao.Engine.ID(gid).Get(&group); has {  // ← 每个 group 一次查询

16. ConfigHandler.List 中 app_code like ? 可能过度匹配

文件: internal/handler/config.go

如果 app.Code = "app",会匹配到 app、app-web、app-api 等所有以 app 开头的配置。

query = query.And("app_code like ?", app.Code+"%")

17. AppHandler.Tree 返回已禁用的应用

文件: internal/handler/app.go

没有过滤 enabled = 1,禁用的应用也会出现在树中。

func (h *AppHandler) Tree(c *gin.Context) {
    var apps []model.Application
    dao.Engine.Find(&apps)  // ← 没有 WHERE enabled = 1

18. Profile 模型缺少 CreatedAt/UpdatedAt 字段

文件: internal/model/profile.go

其他所有模型都有时间审计字段,Profile 没有,无法追踪环境的创建和修改时间。

19. DSN 构建逻辑重复

文件: internal/config/config.go 的 DSN() 方法 vs internal/dao/engine.go 的 Init() 函数

两处各自拼接 DSN 字符串,且 dao/engine.go 没有复用 config.DatabaseConfig.DSN() 方法,还额外加了 &loc=Local 参数。


总结

等级 数量 关键问题
🔴 严重 5 加密空实现、不安全随机数、JWT 算法未校验、凭据泄露、加密模式不一致
🟠 重要 6 回滚不加密、权限表缺失、登出无效、禁用用户仍可访问等
🟡 一般 8 错误忽略、N+1 查询、时序攻击、硬删除不一致等