FlagYard: OhMyPatch
Root cause
/patch applies a JSON Patch operation from any registered user straight to the user store, so one request replaces the role the server assigned with admin.
Chain
- Find the method
OPTIONS /register - Register
POST /register - Read the store
GET /users - Rewrite the role
PATCH /patch - Collect
GET /flag
Contents
01 Surface
Browsing the application turns up three endpoints. Trying for one more finds a fourth.
/registerfound by browsing/usersfound by browsing/found by browsing/flagfound by trying it
02 Registration
The logical first move is to register a user, so there is something to log in with. Going to /register directly returns 405 Method Not Allowed, so the question becomes which methods the endpoint does accept. An OPTIONS request answers it.
-
Direct visit 405 Method Not Allowed described, not captured
- 01 Options 200 OK Allow: POST
- 02 Post 500 INTERNAL SERVER ERROR no Content-Type
- 03 Post 400 BAD REQUEST Missing field: name
- 04 Post 200 OK token issued
Show the three attempts before it workedHide the three earlier attempts
/register accepts.Request
show 8 more headershide 8 headers
Response
show 4 headershide 4 headers
POST is allowed, so that is how a user gets registered.
Request
show 9 more headershide 9 headers
Response
show 5 headershide 5 headers
After much testing, the cause of the 500 turns out to be simple: the request did not set a Content-Type header. With it set, the server answers differently.
Request
show 4 more headershide 4 headers
Response
show 5 headershide 5 headers
A field is missing.
Adding all the missing fields gets the user registered, and the response carries an access token.
Request
show 11 more headershide 11 headers
Response
show 5 headershide 5 headers
03 The user store
With the token, a request to /users shows every registered user.
Request
show 6 more headershide 6 headers
Response
show 5 headershide 5 headers
Body reflowed to one record per line. In the last record, the bold fields are the two the server assigned.
The new account is the last record. Its name, age and department are the values sent in exchange 04. Its id and its role are not: the server chose those, and the role it chose is user.
04 The patch
The name of the challenge makes it clear that this has something to do with PATCH, and there is a /patch endpoint. That endpoint can be used to escalate the account to admin.
{"op":"replace","path":"/users/3/role","value":"admin"}
As sent in the manual request. All three values are the caller’s choice.
Request
Response
Not captured. I did not save the response to this request.
Set side by side, what the application relies on and what /patch does look like this.
Assumption
what the app relies onThe role is the server’s to assign.
Registration took five fields and none of them was a role. The record still came back with one, already set.
Bypass
why that does not hold/patch writes to the same record, by path.
The operation does not go through registration at all. It names the role field of a user record and replaces the value the server assigned.
05 Root cause and fix
An ordinary user’s token is enough to replace a role. /patch takes the operation as sent and applies it to the user store. The caller picks the path, and the path may point at role.
06 Result
Not captured After the patch, GET /flag returned the flag. I did not save that request or its response. The last step of the script makes the same request.
07 Automation
I also wrote a script that walks the same path in four steps: register, list the users, patch the role, request the flag. These are the lines that carry the payload.