Go语言代码审计实战
字数 982 2025-08-07 08:21:50

Go语言代码审计实战教学文档

0x01 程序一:PPGo审计分析

1.1 程序概述

PPGo是一个基于Go语言的任务调度系统,使用Beego框架开发,GitHub地址:https://github.com/george518/PPGo_Job

1.2 环境搭建

  1. 下载源码
  2. 创建数据库并导入ppgo_job2.sql
  3. 配置conf/app.conf文件
  4. 运行./run.sh start|stop

1.3 登录功能分析

登录请求示例:

POST /login_in HTTP/1.1
Host: maoge:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 30

username=admin&password=123456

登录成功响应:

HTTP/1.1 200 OK
Set-Cookie: auth=1|c2ef80548b36081206a40745cffbca88; Expires=Thu, 02 Dec 2021 05:12:52 UTC; Max-Age=604800; Path=/

1.4 鉴权机制分析

Beego框架通过Prepare方法实现AOP鉴权,核心代码如下:

func (self *BaseController) auth() {
    self.userId = 0
    // 从cookie中获取auth值
    auth := self.Ctx.GetCookie("auth")
    if len(auth) > 0 {
        // 分隔auth值
        parts := strings.Split(auth, "|")
        userId, _ := strconv.Atoi(parts[0])
        if userId > 0 {
            // 获取用户信息
            user, err := models.UserGetById(userId)
            if err == nil && user != nil {
                // 验证auth值
                if parts[1] == self.authEncode(user) {
                    self.userId = userId
                }
            }
        }
    }
    
    // 检查URL权限
    if !self.checkAccess() {
        if self.userId == 0 {
            // 未授权处理
        } else {
            // 鉴权成功
        }
    }
}

1.5 鉴权绕过漏洞

漏洞原理:

  1. 当修改authcookie中的userid为>1的值时
  2. 系统会尝试查询该用户信息
  3. 查询结果为nil(用户不存在)
  4. self.userId仍保持>0的状态
  5. 由于URL不在allowUrlnoAuth列表中
  6. self.userId>0,导致绕过鉴权

利用方法:
修改cookie为auth=5|任意值即可绕过鉴权

0x02 程序二:文档管理系统审计分析

2.1 登录功能分析

登录核心代码:

if c.Ctx.Input.IsPost() {
    account := c.GetString("account")
    password := c.GetString("password")
    captcha := c.GetString("code")
    isRemember := c.GetString("is_remember")

    // 验证码检查
    if v, ok := c.Option["ENABLED_CAPTCHA"]; ok && strings.EqualFold(v, "true") {
        v, ok := c.GetSession(conf.CaptchaSessionName).(string)
        if !ok || !strings.EqualFold(v, captcha) {
            c.JsonResult(6001, i18n.Tr(c.Lang, "message.captcha_wrong"))
        }
    }

    member, err := models.NewMember().Login(account, password)
    if err == nil {
        member.LastLoginTime = time.Now()
        _ = member.Update("last_login_time")

        c.SetMember(*member)

        if strings.EqualFold(isRemember, "yes") {
            remember.MemberId = member.MemberId
            remember.Account = member.Account
            remember.Time = time.Now()
            v, err := utils.Encode(remember)
            if err == nil {
                c.SetSecureCookie(conf.GetAppKey(), "login", v, time.Now().Add(time.Hour*24*30).Unix())
            }
        }
    }
}

2.2 密码验证机制

func PasswordVerify(hashing string, pass string) (bool, error) {
    data := trimSaltHash(hashing)
    interation, _ := strconv.ParseInt(data["interation_string"], 10, 64)
    has, err := hash(pass, data["salt_secret"], data["salt"], int64(interation))
    if err != nil {
        return false, err
    }
    if (data["salt_secret"] + delmiter + data["interation_string"] + delmiter + has + delmiter + data["salt"]) == hashing {
        return true, nil
    } else {
        return false, nil
    }
}

2.3 会话管理机制

if member, ok := c.GetSession(conf.LoginSessionName).(models.Member); ok && member.MemberId > 0 {
    c.Member = &member
    c.Data["Member"] = c.Member
} else {
    var remember CookieRemember
    if cookie, ok := c.GetSecureCookie(conf.GetAppKey(), "login"); ok {
        if err := utils.Decode(cookie, &remember); err == nil {
            if member, err := models.NewMember().Find(remember.MemberId); err == nil {
                c.Member = member
                c.Data["Member"] = member
                c.SetMember(*member)
            }
        }
    }
}

2.4 SecureCookie实现分析

func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) {
    vs := base64.URLEncoding.EncodeToString([]byte(value))
    timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
    h := hmac.New(sha256.New, []byte(Secret))
    fmt.Fprintf(h, "%s%s", vs, timestamp)
    sig := fmt.Sprintf("%02x", h.Sum(nil))
    cookie := strings.Join([]string{vs, timestamp, sig}, "|")
    ctx.Output.Cookie(name, cookie, others...)
}

应用密钥获取:

func GetAppKey() string {
    return web.AppConfig.DefaultString("app_key", "mindoc")
}

2.5 鉴权绕过漏洞

漏洞原理:

  1. 系统使用固定密钥"mindoc"进行cookie加密
  2. 可以通过构造合法的"login" cookie绕过认证
  3. 类似于Shiro的RememberMe漏洞

利用方法:

  1. 构造合法的remember结构体
  2. 使用"mindoc"作为密钥生成cookie
  3. 在请求中添加伪造的"login" cookie

0x03 总结与防御建议

3.1 常见漏洞模式

  1. 鉴权逻辑缺陷:如PPGo中的userid验证不严
  2. 固定加密密钥:如文档系统中的"mindoc"
  3. 不安全的会话管理:依赖客户端可控的cookie值

3.2 防御建议

  1. 严格验证用户身份,确保用户存在且状态正常
  2. 使用强随机密钥,避免硬编码
  3. 实现完善的会话失效机制
  4. 对敏感操作进行二次验证
  5. 定期进行安全审计和代码审查

3.3 Go语言审计特点

  1. 框架众多但设计模式相似
  2. 通常通过中间件或Prepare方法实现鉴权
  3. 标准库安全性较高,但自定义实现可能存在风险
  4. 并发特性可能引入特殊的安全问题
Go语言代码审计实战教学文档 0x01 程序一:PPGo审计分析 1.1 程序概述 PPGo是一个基于Go语言的任务调度系统,使用Beego框架开发,GitHub地址:https://github.com/george518/PPGo_ Job 1.2 环境搭建 下载源码 创建数据库并导入 ppgo_job2.sql 配置 conf/app.conf 文件 运行 ./run.sh start|stop 1.3 登录功能分析 登录请求示例: 登录成功响应: 1.4 鉴权机制分析 Beego框架通过 Prepare 方法实现AOP鉴权,核心代码如下: 1.5 鉴权绕过漏洞 漏洞原理: 当修改 auth cookie中的userid为>1的值时 系统会尝试查询该用户信息 查询结果为nil(用户不存在) 但 self.userId 仍保持>0的状态 由于URL不在 allowUrl 和 noAuth 列表中 但 self.userId>0 ,导致绕过鉴权 利用方法: 修改cookie为 auth=5|任意值 即可绕过鉴权 0x02 程序二:文档管理系统审计分析 2.1 登录功能分析 登录核心代码: 2.2 密码验证机制 2.3 会话管理机制 2.4 SecureCookie实现分析 应用密钥获取: 2.5 鉴权绕过漏洞 漏洞原理: 系统使用固定密钥"mindoc"进行cookie加密 可以通过构造合法的"login" cookie绕过认证 类似于Shiro的RememberMe漏洞 利用方法: 构造合法的remember结构体 使用"mindoc"作为密钥生成cookie 在请求中添加伪造的"login" cookie 0x03 总结与防御建议 3.1 常见漏洞模式 鉴权逻辑缺陷:如PPGo中的userid验证不严 固定加密密钥:如文档系统中的"mindoc" 不安全的会话管理:依赖客户端可控的cookie值 3.2 防御建议 严格验证用户身份,确保用户存在且状态正常 使用强随机密钥,避免硬编码 实现完善的会话失效机制 对敏感操作进行二次验证 定期进行安全审计和代码审查 3.3 Go语言审计特点 框架众多但设计模式相似 通常通过中间件或Prepare方法实现鉴权 标准库安全性较高,但自定义实现可能存在风险 并发特性可能引入特殊的安全问题