当前位置: 首页 > 工具软件 > PHP-Druid > 使用案例 >

SQL注入检测模块开源项目DRUID-SQL-WALL学习小结

巢德华
2023-12-01

 

作为sql注入原理、sql注入检测、防御系列学习的第三篇。本文主要关注了抽象语法树ast在sql注入检测上的应用开发、以及开源项目druid-sql-wall的学习,希望能给研究这一领域的朋友带来一点帮助,同时也希望能引发大家的共同讨论,共同学习、成长

上一篇文章中,我们学习了其他数据库 防火墙的一些基本知识

http://www.makaidong.com/littlehann/p/3505410.html

文章的接下来部分准备分为2部分进行学习:

    1. sql注入语法防御规则

    2. druid中sql注入防御模块sql-wall

相关学习资料

http://code.alibabatech.com/wiki/display/druid/wallfilter

http://code.alibabatech.com/wiki/display/druid/wallfilterconfig

http://code.alibabatech.com/wiki/display/druid/get+druid

其他数据库 防火墙位于前端应用开发层之后,前端的应用开发层可以是php、asp、java等,这些语言通过一些统一的访问接口(odbc、jdbc)对数据库系统发起访问请求

所以到了其他数据库 这一层的都是纯的sql请求,所以在其他数据库 这一层面要考虑的不是一些应用开发系统开发的oday、本地变量覆盖的漏洞,而应该明确我们所处的防御层面,我们要防御的是黑客针对其他数据库 发起的攻击。

1. 针对其他数据库 的缓冲区溢出攻击: 这个是实战中很少见,详见
2. 针对其他数据库 底层代码的极限领域的攻击,例如,这是在一个ctf中出现过的mysql attack topic:
<?php
    # goal: dump the info for the secret id
    require 'db.inc.php';

    $id = @(float)$_get['id'];
    die(var_dump($id));
    $secretid = 1;
    if($id == $secretid)
    {
        echo 'invalid id ('.$id.').';
    }
    else
    {
        $query = 'select * from users where id = \''.$id.'\';';
        $result = mysql_query($query);
        $row = mysql_fetch_assoc($result);
        
        echo "id: ".$row['id']."</br>";
        echo "name:".$row['name']."</br>";
    }
?>
http://localhost/php4fun/index.php?id=1.0000000000001
攻击者的目标的是要查出id为1的admin的数据,这里的绕过思路是利用了mysql的精度范围和php的精度范围不同,精度小的会忽略不能支持的位数。也就是说,浮点型的精度有上限和下限

3. 纯粹的拼接sql语法对数据进行注入攻击: 这是最常见的,我们接下来重点分析这方面内容

sql注入语法防御规则

    目前,druid的防御重点主要放在拼接型的sql注入攻击,即利用注入点在原始的sql语句综合 的中间或后面"插入"、"拼接"上攻击性的sql payload,从而达到提取非法数据等目的,缓冲区溢出和特殊情况的攻击druid暂时没有实现,将放到未来的版本中逐渐完善,下面根据温少的文档、并 配合druid的源代码进行学习进行具体规则的学习:

0x1 只允许执行增删改查基本语句
\druid\src\main\java\com\alibaba\druid\wall\wallconfig.java(druid的源码和整体架构放在文章的后半部分)
....
//是否允许非以上基本语句的其他语句,缺省关闭,通过这个选项就能够屏蔽ddl。
private boolean             nonebasestatementallow      = false;
....
这是最严格模式,但是也最缺乏灵活性,基本上是不能开启的,在正常的用户业务需求中,必不可少会用到除了crud(增删改查)之外的需求,开启这条规则会导致大量的误报,故druid默认关闭这个开关

0x2 不允许一次执行多条语句 每次只允许执行一条sql,一次执行多条sql,是被认为可能正被sql注入攻击。

1. sql server 6.0在其架构中引入了服务端游标,从而允许在同一连接句柄上执行包含多条语句的字符串。所有6.0之后的sql server 版本均支持该功能且允许执行下列语句:

select id from users;select name from users;

客户端连接到sql服务器开发并依次执行每条语句,其他数据库 服务器开发向客户端返回每条语句发送的结果集。

http://database .51cto.com/art/201007/213806.htm

2. mysql在4.1及之后的版本中也引入了该功能,但是php自身限制了这种用法。

<?php
     $con = mysql_connect("127.0.0.1", "root" , "111");
     mysql_select_db("php4fun_", $con);
     $sql = "update users set level=2;update users set pass=3;"; 
    $result = mysql_query($sql, $con); echo mysql_error(); 
    if($result) 
    { 
        $result_array = mysql_fetch_array($result); var_dump($result_array); 
    } 
?>

result: you have an error in your sql syntax; check the manual that corresponds to your mysql server version for the right syntax to use near 'select 1,2,3,4 from dual' at line 1 而如果使用的pdo方式操作其他数据库

<?php 
    $db = new pdo("mysql:host=localhost:3306;dbname=php4fun_", 'root', '111');
     $sql = "update users set level=2;update users set pass=3;";
     try 
    { 
        $db->query($sql); 
    } catch(pdoexception $e) {
         echo $e->getmessage(); die();
     } 
?> 

result: ok

3. oracle不支持多条语句,除非使用pl/sql \druid\src\main\java\com\alibaba\druid\wall\wallconfig.java .... private boolean multistatementallow = false; .... druid默认是禁止这种格式的sql语句综合 的,也即如果在传入的sql语句综合 中解析出了2条及以上的sqlstatement(一个sqlstatement抽象了一条sql语句综合 )就判断为注入攻击

0x3 不允许访问系统开发表
在之前的学习笔记

此文来自: 马开东博客 转载请注明出处 网址:

中,有总结过,从攻击者渠道的角度去理解,攻击者最终的目的是要获取信息 http://www.makaidong.com/littlehann/p/3495602.html 而"访问系统开发表"就是获取信息的渠道之一,故需要拦截之 但是druid对这种规则的判断更加细化,druid只拦截在子句中出现的连接系统开发表查询,举例说明: 1. select * from information_schema.columns; 这条语句druid认为是合法的,因为这条语句没有注入点的存在,sql语句综合 本身的唯一目的就是查询系统开发表,说明用户在进行正常的业务操作 2. select id
from admin
where id = 1
    and 5 = 6
union
select concat(0x5e252421, count(8), 0x2a5b7d2f)
from (select `column_name`, `data_type`, `character_set_name`
    from `information_schema`.`columns`
    where table_name = 0x73696e6765725f616c62756d
        and table_schema = 0x796971696c61695f757466
    ) t
这条语句druid认为是非法的注入攻击,因为sql在子句(可能是注入点的地方)采取了union拼接,进行了连接系统开发表的查询的操作 druid通过判断information_schema在ast层次结构中的位置,具体来说就是判断它的父节点是否为"sql表达式"(例如union select)、以及它的左节点是否为"from节点"。
即满足子句拼接的模式。以此来判断这条sql语句综合 是否有攻击性,在代码中的体现就是 druid\src\main\java\com\alibaba\druid\wall\spi\wallvisitorutils.java ..... boolean sametotopselectschema = false; if (parent instanceof sqlselectstatement) { sqlselectstatement selectstmt = (sqlselectstatement) parent; sqlselectquery query = selectstmt.getselect().getquery(); if (query instanceof sqlselectqueryblock) { sqlselectqueryblock queryblock = (sqlselectqueryblock) query; sqltablesource from = queryblock.getfrom(); while (from instanceof sqljointablesource) {   from = ((sqljointablesource) from).getleft(); } if (from instanceof sqlexprtablesource) { sqlexpr expr = ((sqlexprtablesource) from).getexpr(); if (expr instanceof sqlpropertyexpr) { sqlexpr schemaexpr = ((sqlpropertyexpr) expr).getowner(); if (schemaexpr instanceof sqlidentifierexpr) {   string schema = ((sqlidentifierexpr) schemaexpr).getname();   schema = form(schema);   if (schema.equalsignorecase(owner))   {   sametotopselectschema = true;   } }  } } } } if (!sametotopselectschema) { addviolation(visitor, errorcode.schema_deny, "deny schema : " + owner, x); } 而代码中的owner是从配置文件中读取的: string owner = ((sqlname) x).getsimlename(); owner = wallvisitorutils.form(owner); if (isintablesource(x) && !visitor.getprovider().checkdenyschema(owner)) { ... 配置文件被统一放在了: \druid\src\main\resources\meta-inf\druid\wall\mysql\deny-schema.txt information_schema mysql performance_schema 这样,druid就完成了对sql中的对系统开发敏感表的注入的智能检测

 

 

0x4 不允许访问系统开发对象
在sqlserver中有系统开发对象的概念。对敏感系统开发对象"sysobject"的检测也是同样的原理,即只检测子句的非法连接,并从配置文件中读取拦截列表,代码和对系统开发表的检测是类似的

 

 

0x5 不允许访问系统开发变量
系统开发敏感变量同样也是攻击者获取非法数据的一种渠道,druid采取智能判断的做法,举例说明:

1. select @@basedir;
这条语句druid不做拦截,因为这里没有注入点的存在,也就不可能是黑客的注入攻击,应该归类于业务的正常需要

2. select * from cnp_news where id='23' and len(@@version)>0 and '1'='1'
这条语句druid会做拦截,攻击者在子句中利用逻辑表达式进行非法的探测注入,目前druid的检测机制是"黑名单机制",把需要禁止的系统开发变量写在了配置文件中:
druid\src\main\resources\meta-inf\druid\wall\mysql\deny-variant.txt
basedir
version_compile_os
version
datadir
druid\src\main\java\com\alibaba\druid\wall\spi\wallvisitorutils.java
...
if (!checkvar(x.getparent(), x.getname()))
{
    boolean istop = wallvisitorutils.istopnonefromselect(this, x);
    if (!istop)
    {
    boolean allow = true;
    if (wallvisitorutils.iswhereorhaving(x) && isdeny(varname))
    {
        allow = false;
    }

    if (!allow)
    {
        violations.add(new illegalsqlobjectviolation(errorcode.variant_deny, "variable not allow : " + x.getname(), tosql(x)));
    }
    }
}
...

 

 

0x6 不允许访问系统开发函数
和"系统开发敏感表"、"系统开发敏感对象"、"系统开发敏感变量"一样,系统开发敏感函数也是攻击者用来获取非法信息的一种手段之一
druid中和禁用系统开发函数的配置文件:
druid\src\main\resources\meta-inf\druid\wall\mysql\deny-function.txt
version
load_file
database 
schema
user
system_user
session_user
benchmark
current_user
sleep
xmltype
receive_message

对于系统开发敏感函数的禁用,这里要注意一下,和系统开发表的防御思想类型,druid会智能地判断敏感函数在sql语句综合 中出现的位置,例如:
1. select load_file('\\etc\\passwd');
druid不会拦截这条语句,还是同样的道理,sql注入的关键在于注入点,这条语句没有注入点的存在,所以只能是用户正常的业务需求

2. select * from admin where id =(select 1 from (select sleep(0))a);   
druid会智能地检测出这个敏感函数出现在"where子句节点"中,而"where子句节点"经常被黑客用来当作一个sql注入点,故druid拦截之
代码如下:
druid\src\main\java\com\alibaba\druid\wall\spi\wallvisitorutils.java
public static void checkfunction(wallvisitor visitor, sqlmethodinvokeexpr x)
{
    final walltopstatementcontext topstatementcontext = walltopstatementcontextlocal.get();
    if (topstatementcontext != null && (topstatementcontext.fromsysschema || topstatementcontext.fromsystable))
    {
        return;
    }

    checkschema(visitor, x.getowner());

    if (!visitor.getconfig().isfunctioncheck())
    {
        return;
    }

    string methodname = x.getmethodname().tolowercase();

    wallcontext context = wallcontext.current();
    if (context != null)
    {
        context.incrementfunctioninvoke(methodname);
    }

    if (!visitor.getprovider().checkdenyfunction(methodname))
    {
        boolean istopnonefrom = istopnonefromselect(visitor, x);
        if (istopnonefrom)
        {
        return;
        }

        boolean isshow = x.getparent() instanceof mysqlshowgrantsstatement;
        if (isshow)
        {
        return;
        }

        if (iswhereorhaving(x))
        {
        addviolation(visitor, errorcode.function_deny, "deny function : " + methodname, x);
        }
    }
}   

 

 

0x7 不允许出现注释
正常执行的sql是不应该附带注释的,有注释的sql都会被认为是危险操作。druid是默认"禁止"单行注释和多行注释。这里所谓的"禁止"是值druid会在解析前自动地去除原始sql语句综合 中的注释。
例如攻击者常用的绕过方式:
1) sel/**/ect us/**/er() from dual;  (黑客常用来绕过基于正则前端waf)
2) select * from admin where no=4 and 1=2 /!40001+union/ select 1,concat(database (),0x5c,user(),0x5c,version()),3,4,5,6,7   
(mysql的comment dynamic execution bypass)     
http://www.freebuf.com/articles/web/22041.html

这里druid采取的防御思路是"规范化",代码自动会将注释的部分删除,重新拼接sql语句综合 后,对"规范化"后的语句再进行注入检测,删除注释的代码逻辑在词法解析器中:
druid\src\main\java\com\alibaba\druid\sql\parser\lexer.java
..
protected boolean      skipcomment  = true;
..
public final void nexttoken()
{
      ....
      /*
                    解析'#'注释符
                    判断'#'解析出的节点是'单行注释'、或'多行注释'
                */
                case '#':
                    scansharp();
                    if ((token() == token.line_comment || token() == token.multi_line_comment) && skipcomment)
                    {
                        bufpos = 0;
                        continue;
                    }
                    return;
      ....
      /*
                        检测是否是'--'这种单行注释符
                 */
                 if (subnextchar == '-')
                 {
                        scancomment();
                        if ((token() == token.line_comment || token() == token.multi_line_comment) && skipcomment)
                        {
                            bufpos = 0;
                            continue;
                        }
                 }
      ...
      /*
                        判断当前节点是否是 /*  */  这种类型的多行注释
                 */
                 if (nextchar == '/' || nextchar == '*')
                 {
                        scancomment();
                        if ((token() == token.line_comment || token() == token.multi_line_comment) && skipcomment)
                        {
                            bufpos = 0;
                            continue;
                        }
                 }
...
在对sql的词法解析的开发过程 中,druid就会自动地对

 

此文来自: 马开东博客 转载请注明出处 网址:

各种形式的注释符进行删除,删除了注释后,druid再去解析sql语句综合 ,这个时候会出现两个情况: 1) 解析失败抛异常,说明原本的sql语句综合 很有可能是攻击型的sql语句综合 ,黑客使用了注释绕过或者注释执行技术 2) 解析正常,说明这是正常的sql语句综合 ,不排除有的程序猿会把一些简短的注释写在sql语句综合 中,但是这个注释的删除对原本的执行没有影响,所以也就判定为合理sql语句综合 oracle hints的语法是/* + */,druid能够区分注释和hints

 类似资料: