Go语言代码审计实战

渗透技巧 2年前 (2021) admin
1,033 0 0


本文首发于奇安信攻防社区


本文主要是实战go语言编写的两个小程序的漏洞挖掘。

0x01程序一: PPGo

github地址 https://github.com/george518/PPGo_Job

Go语言代码审计实战

从这里直接下载就行了。
进入文件夹,设置好数据库(创建数据库,导入ppgo_job2.sql)和配置文件(conf/app.conf)
运行 ./run.sh start|stop

成功后

Go语言代码审计实战

表示成功。

开始挖:

先看登录代码

Go语言代码审计实战

登录代码没有发现什么问题。简单的比较从数据库取的账号密码匹配。
成功后设置auth响应。

先登录成功抓一个包:

  1. POST /login_in HTTP/1.1

  2. Host: maoge:8080

  3. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:94.0) Gecko/20100101 Firefox/94.0

  4. Accept: application/json, text/javascript, */*; q=0.01

  5. Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2

  6. Accept-Encoding: gzip, deflate

  7. Content-Type: application/x-www-form-urlencoded; charset=UTF-8

  8. X-Requested-With: XMLHttpRequest

  9. Content-Length: 30

  10. Origin: http://maoge:8080

  11. Connection: close

  12. Referer: http://maoge:8080/

  13. Cookie: Hm_lvt_8acef669ea66f479854ecd328d1f348f=1616241442,1618728317; Hm_lvt_1cd9bcbaae133f03a6eb19da6579aaba=1616723765,1616858080,1616942607; USER_ID_ANONYMOUS=b3acc94ef8cb416eae4a54f0208b0b87; MAIN_NAV_ACTIVE_TAB_INDEX=1; DETECTED_VERSION=2.3.0; ANALYSIS_PROJECT_ID=; PAGINATION_PAGE_SIZE=10; DATA_FILTER_SEARCH=other; sc=M38BEbHqxH5aXtFqEGynxePWh8EuUDGoiEhTw164lDN2Qxdr3XpWXAflnqESg3nn; Hm_lvt_1d2d61263f13e4b288c8da19ad3ff56d=1629280468; uid=MTYzMzk0NzkzOQ==|1633947939543498000|db23261bcc5dff7017756ad873f755dab61521e3; token=Mg==|1633947939543553000|b7794845537943b17f13c40ac378589fb2c15d38; beegosessionID=ffb9e2b7bfff94a8ddecc8dbf1768171

  14. username=admin&password=123456

返回内容

  1. ``HTTP/1.1 200 OK

  2. Content-Length: 46

  3. Content-Type: application/json; charset=utf-8

  4. Server: beegoServer:1.11.1

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

  6. Date: Thu, 25 Nov 2021 05:12:52 GMT

  7. Connection: close

  8. {

  9.  "message": "登录成功",

  10.  "status": 0

  11. }

返回了一个set cookie应该就是后面的鉴权凭证。

尝试渗透这个页面,页面内有服务器的账号密码,比较有价值。

Go语言代码审计实战

  1. GET /server/edit?id=5 HTTP/1.1

  2. Host: maoge:8080

  3. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:94.0) Gecko/20100101 Firefox/94.0

  4. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8

  5. Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2

  6. Accept-Encoding: gzip, deflate

  7. Connection: close

  8. Referer: http://maoge:8080/home

  9. Cookie: auth=1|c2ef80548b36081206a40745cffbca88

  10. Upgrade-Insecure-Requests: 1

这里直接走到鉴权的代码,架构采用的是beego,通常直接找到prepare,相当于AOP,里面就是鉴权的代码

Go语言代码审计实战

进入auth方法。

Go语言代码审计实战
Go语言代码审计实战

会对auth=1|c2ef80548b36081206a40745cffbca88 进行分隔。
1为 userid `,c2ef80548b36081206a40745cffbca88`鉴权字段。

跟如auth

  1. 最开始,self.userId 赋值为 0;

  2. 然后将auth里面的id传给变量 userId =1;

  3. 进入if userId>0 ; 获取用户信息根据 id。并且进行鉴权如果成功了设置self.userId 的值为1。

继续往下走

  1. 判断url是否在allowUrlnoAuth。如果都不在则无权限。明显不满足。

  2. 如果self.userId=0 且 actionName 和 controllerName 满足条件则未授权。否者在鉴权成功。明显 self.userId>0 则成功。
    看下如何绕过。
    如果把 userid 设置为 >0 甚至一个不存在的值并且比如 userid 设置为5。

试着走一下

  1. userId>0 满足走if语句

  2. 获取到 user 的信息为 nil

  3. 明显的url不满足 allowUrl 和 noAuth 的 if 。

  4. 走到下面的if 我们的 self.userId>0 不满足,则直接跳过,绕过鉴权。

所以只需要将 userid 改为 >1 即可。

Go语言代码审计实战

0x02程序二

程序二是一个文档管理工具。
文档搭建好以后进入登录页面。
Go语言代码审计实战
先看登录账号是否存在问题 。

  1.    if c.Ctx.Input.IsPost() {

  2.        account := c.GetString("account")

  3.        password := c.GetString("password")

  4.        captcha := c.GetString("code")

  5.        isRemember := c.GetString("is_remember")

  6.        // 如果开启了验证码

  7.        if v, ok := c.Option["ENABLED_CAPTCHA"]; ok && strings.EqualFold(v, "true") {

  8.            v, ok := c.GetSession(conf.CaptchaSessionName).(string)

  9.            if !ok || !strings.EqualFold(v, captcha) {

  10.                c.JsonResult(6001, i18n.Tr(c.Lang, "message.captcha_wrong"))

  11.            }

  12.        }

  13.        if account == "" || password == "" {

  14.            c.JsonResult(6002, i18n.Tr(c.Lang, "message.account_or_password_empty"))

  15.        }

  16.        member, err := models.NewMember().Login(account, password)

  17.        if err == nil {

  18.            member.LastLoginTime = time.Now()

  19.            _ = member.Update("last_login_time")

  20.            c.SetMember(*member)

  21.            if strings.EqualFold(isRemember, "yes") {

  22.                remember.MemberId = member.MemberId

  23.                remember.Account = member.Account

  24.                remember.Time = time.Now()

  25.                v, err := utils.Encode(remember)

  26.                if err == nil {

  27.                    c.SetSecureCookie(conf.GetAppKey(), "login", v, time.Now().Add(time.Hour*24*30).Unix())

  28.                }

  29.            }

进入验证代码

  1. func PasswordVerify(hashing string, pass string) (bool, error) {

  2.    data := trimSaltHash(hashing)

  3.    interation, _ := strconv.ParseInt(data["interation_string"], 10, 64)

  4.    has, err := hash(pass, data["salt_secret"], data["salt"], int64(interation))

  5.    if err != nil {

  6.        return false, err

  7.    }

  8.    if (data["salt_secret"] + delmiter + data["interation_string"] + delmiter + has + delmiter + data["salt"]) == hashing {

  9.        return true, nil

  10.    } else {

  11.        return false, nil

  12.    }

  13. }

没有看出来什么问题。获取密码并且加密与之对比。
如果登录成功了 那么设置sessionId。如果设置了remember参数,那么在cookie里面设置login参数。

  1. func (c *BaseController) SetMember(member models.Member) {

  2.    if member.MemberId <= 0 {

  3.        c.DelSession(conf.LoginSessionName)

  4.        c.DelSession("uid")

  5.        c.DestroySession()

  6.    } else {

  7.        c.SetSession(conf.LoginSessionName, member)

  8.        c.SetSession("uid", member.MemberId)

  9.    }

  10. }

进入到校验session的位置 采用的是beego框架,也直接进入Prepare进去。

  1. if member, ok := c.GetSession(conf.LoginSessionName).(models.Member); ok && member.MemberId > 0 {

  2.        c.Member = &member

  3.        c.Data["Member"] = c.Member

  4.    } else {

  5.        var remember CookieRemember

  6.         //如果Cookie中存在登录信息,从cookie中获取用户信息

  7.        if cookie, ok := c.GetSecureCookie(conf.GetAppKey(), "login"); ok {

  8.            if err := utils.Decode(cookie, &remember); err == nil {

  9.                if member, err := models.NewMember().Find(remember.MemberId); err == nil {

  10.                    c.Member = member

  11.                    c.Data["Member"] = member

  12.                    c.SetMember(*member)

  13.                }

  14.            }

  15.        }

  16.    }

如果session不正确那么走CookieRemember的分支,那么就从GetSecureCookie里面获取login的信息。那么先看看SecureCooki是如何设置的。
这个方法来自于beego的里面的设置cookie的方法,加密方式很简单,但是加入了随机时间戳。再来设置的密钥。

  1. func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) {

  2.    vs := base64.URLEncoding.EncodeToString([]byte(value))

  3.    timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)

  4.    h := hmac.New(sha256.New, []byte(Secret))

  5.    fmt.Fprintf(h, "%s%s", vs, timestamp)

  6.    sig := fmt.Sprintf("%02x", h.Sum(nil))

  7.    cookie := strings.Join([]string{vs, timestamp, sig}, "|")

  8.    ctx.Output.Cookie(name, cookie, others...)

  9. }

  10. app_key Secret ,mindoc

  11. func GetAppKey() string {

  12. return web.AppConfig.DefaultString("app_key", "mindoc")

  13. }

也就是说直接使用就可以构造出login,cookie。感觉很像shiro的RememberMe。

  1. SetSecureCookie("mindoc", "login", v, time.Now().Add(time.Hour*24*30).Unix())

然后直接在cookie里面加入login字段,既可绕过权限,成功进入后台。

0x03 总结

总的来说,go语言是一门相对简单的语言相比其他语言而言。go语言的框架有非常多并且很精巧,不过轮子都差不多,知一通百。

END



【版权说明】本作品著作权归郎里个郎里个郎所有,授权补天漏洞响应平台独家享有信息网络传播权,任何第三方未经授权,不得转载。



Go语言代码审计实战
郎里个郎里个郎

RIP攻防战队成员


敲黑板!转发≠学会,课代表给你们划重点了

复习列表





记一次文件上传的曲折经历


代码审计之eyouCMS最新版getshell漏洞


硬核黑客笔记 – 怒吼吧电磁波 (上)


从WEB弱口令到获取集权类设备权限的过程


一个域内特权提升技巧 | 文末双重福利


php无文件攻击浅析

Go语言代码审计实战


分享、点赞、在看,一键三连,yyds。
Go语言代码审计实战

点击阅读原文,加入社区,获取更多技术干货!

原文始发于微信公众号(补天平台):Go语言代码审计实战

版权声明:admin 发表于 2021年12月3日 上午10:00。
转载请注明:Go语言代码审计实战 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...