作为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语法对数据进行注入攻击: 这是最常见的,我们接下来重点分析这方面内容
目前,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