Mercury Walkthrough
Training Mission
Before jumping into the full game, let's walk through a practice run using the Mercury service. This service is similar to what you'll find in the actual competition.
Recon & Analysis
First, SSH into your service container:
ssh -i team_key.pem rta_user@<your-service-ip>
Navigate to /app (or where the service is running) and find
vuln_http_server.py.
Read the code for understanding. Through that understanding, you will find the vulnerabilities. For
example, look at the login function:
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
conn = sqlite3.connect("database.db")
c = conn.cursor()
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
c.execute(query)
And the ping function:
@app.route("/ping", methods=["GET"])
def ping():
if "username" not in session:
return redirect(url_for("login"))
host = request.args.get("host")
response = os.popen(f"ping -c 1 {host}").read()
return response
Exploitation
Now that we understand the code, let's exploit it.
SQL Injection
Because the user's input isn't sanitized or parameterized and is trusted, the service is vulnerable to
SQL injection. We can test this by injecting any username of our choice, followed by the ',
which would prematurely close off the existing SQL statement. We can ensure it doesn't error out by
commenting the rest out.
Try logging in with the username: admin' --.
This modifies the query to
SELECT * FROM users WHERE username = 'admin' --' AND password = '{password}'. The database
treats everything after -- as a comment, so the effective query becomes
SELECT * FROM users WHERE username = 'admin', logging you in as admin without
a password.
Command Injection
The ping function takes the host parameter and directly concatenates it into a
shell command ping -c 1 {host}. Since this input is not validated, we can inject arbitrary
shell commands.
By using the semicolon ; operator, we can chain a second command after the ping.
Try pinging: 127.0.0.1; cat /home/rta_service/flag.txt
The server will execute ping -c 1 127.0.0.1, finish that command, and then execute
cat /home/rta_service/flag.txt, revealing the flag.
Patching
You must fix these bugs in your own service to prevent others from stealing flags from you.
Fixing the SQL Injection
NEVER use string formatting for SQL queries. Use Parameterized Queries.
- Bad:
c.execute(f"SELECT ... '{username}'") - Good:
c.execute("SELECT ... WHERE username = ?", (username,))
Fixing the Command Injection
Avoid os.popen or os.system with user input. Use the subprocess
module with a list of arguments, which prevents shell injection.
- Bad:
os.popen(f"ping -c 1 {host}") - Good:
import subprocess subprocess.run(["ping", "-c", "1", host])Note: This strictly treats
hostas an argument, not a command.