报表的 SQL 植入风险及规避方法

互联网时代带来方便的同时也带来了安全隐患,各类安全问题可说是防不胜防,特别是你们日益关心的我的信息等方面,貌似很难有什么安全和隐私可言。前端

而在报表做为经常使用的信息载体,更是直接面临各类安全挑战,例如:SQL 植入。mysql

什么是 SQL 植入?!sql

报表为啥会有 SQL 植入风险?!数据库

能不能经过报表工具提供的功能避免 SQL 植入?!api

SQL 植入的概念

首先,认识一下什么是 sql 植入?安全

Sql 植入也常被叫作 SQL 注入,具体的作法是经过把 SQL 命令插入到 Web 表单项或页面请求(Url)的查询字符串中提交,最终达到欺骗服务器执行恶意操做的目的。服务器

常见案例包括经过植入 SQL 骗过登陆验证。而以前不少影视网站泄露 VIP 会员密码的事件,不少就是经过 sql 植入到 WEB 表单爆出的,而且这类表单特别容易受到攻击。经过 SQL 植入,不只能够非法获取帐号信息,严重的还可以篡改、删除重要数据信息。架构

为何存在 SQL 植入呢?

知道了 sql 植入的概念,咱们也应该了解为何会出现 SQL 植入,问题出如今哪一个环节?oracle

1png

上图是一个最简单的三层架构的应用结构,包括业务展示层、 数据处理层、数据源。也能够理解成咱们常说的前端、后台、后台数据源。app

其中,数据源里有个叫数据库的东西,这是最多见的数据管理和存储方式,而关系型数据库那就更常见了,好比 oracle、db二、mysql 等。

开发的应用(数据处理层,也就是后台)为了和操做数据库(增删改查),必须和数据库之间有交流的接口,例如 jdbc 或 odbc,这对于技术人员或是 IT 销售人员也是耳熟能详的。

对于数据库的操做,尤为关系型的,广泛使用的就是 sql 语言。SQL 是一种高级的非过程化语言,只描述作什么而不须要告诉数据库怎么作。 SQL 脚本经过 api 以字符串方式传入数据库,数据库收到 sql 后只管执行而后将结果返回。对于数据库自己来讲,它是不知道传来的 sql 是合法仍是不合法的,而正是这种彻底的信任,致使出现了 sql 植入的风险。

归根结底,SQL 植入利用了应用程序的漏洞,若是编写数据处理的代码时没有充分考虑 sql 植入风险,攻击者将很容易将 sql 命令植入后台数据库引擎执行。而这些不是按照设计者或开发者的意图去执行的 sql 命令都会被视为恶意代码。

如何攻击?

了解了 sql 植入攻击的基本原理,咱们就来看看具体怎么攻击。多种多样的攻击方式中比较常见的包括:

一、特殊的输入参数

二、未处理特殊字符“–”、“#”

三、利用不合理的数据库配置

案例 一、特殊的输入参数

常见的如 union 或 or,这是 sql 内的关键字,一个用做多 sql 的归并,另外一个则经常使用在 where 条件中。

以 Union 为例,若是攻击者在一条正常可执行的 sql 后,经过猜表名的方式拼入“union select … from user”,那么 user 表信息就彻底暴露了。

Or 呢?可使得 where 条件变的恒真,以“骗过登陆验证”为例:

在程序中咱们通常拼 sql 为:strSQL = “SELECT * FROM users WHERE userID = ’” + userID + “’ and pw = ’”+ passWord +“’;”

若是此时 userID 传入 1 or 1=1 ,passWord 传入 ‘1’ or 1=1, 完整的 strSQL 就变成了:“SELECT * FROM users WHERE userID=1 OR 1=1 and pw =’ 1’ OR 1=1;”

很显然,where 条件变为恒真,也就成功骗过了验证,登陆进了系统。

案例 二、未处理特殊字符

以经常使用的注释符“–” 为例:通常的数据库均采用其做为注释符。另外,mysql 还支持“#”注释。

接下来看注释符怎么骗过登陆验证。

程序 sql 依然定义为:strSQL = “select * from users where userID=”+userID+“and password=”+psw

此时 userID 传入:’’ or 1=1 –

完整 sql 则拼为:select * from users where userID=’’ or 1=1 – and password = …

“–”以后的脚本做为注释再也不执行,实际条件也成了恒真,成功骗过验证,侵入系统。

对于 mysql 数据库,把“–”改成“#”一样能够实现注入。

案例 三、利用不合理的数据库配置

常见的是权限配置不合理、太高,就会有 update 和 delele 甚至 drop 表的风险。

所以建议,“永远”不要使用管理员权限链接数据库,而应该为每一个应用使用单独的、权限有限的数据库链接。    

报表和 SQL 植入有啥关系?

因为大多数报表工具都会提供参数功能,根据用户输入的查询条件来筛选合适的数据,所以就给 SQL 植入提供了可乘之机。

2png

例如,若是但愿查询指定时间段的数据,能够把时间段做为参数传递给报表,报表在从数据库中取数时将这些参数拼接到取数 SQL 的 WHERE 条件上,就能够根据不一样参数取出不一样数据来进行呈现了。这种方式要求事先把查询条件作死,也就是固定了对应的条件字段。 好比下面这种传统作法:

sql:select * from t where date>=? and date<=?

这个 SQL 定义的数据集专用于按时间段查询,若是想用地区查询就不行了,须要再作一个 area=? 的查询条件或报表。显然很是麻烦……

若是报表工具只支持这种普通用法,天然不够灵活,可能就会限制报表工具的功能,因而“通用查询”这个大招儿就出现了。

报表工具提供了一种特殊的字符串型参数,容许应用其直接替换 SQL 的某一部分,好比 WHERE 子句。界面端则根据用户的输入拼出合法的 SQL 条件串,做为参数传递给报表替换现有 SQL 的 WHERE 子句,这样就能够在同一张报表上实现不一样形式的查询条件了。好比数据集的 SQL 能够写成:

SELECT … FROM T WHERE ${mac}

其中 ${mac} 就是未来会被参数 mac 替换的内容。按时间段查询时,能够把 mac 拼成 date>… AND data <=…,按地区查询时则拼成 area=…;固然还能够混合多条件查询拼成 data>… AND date<=… AND area=…;而无条件时则拼成一个永远为真的条件 1=1。

是的,这很是灵活了。

But,带来了灵活性的同时也带来了 sql 植入的风险……可能的植入风险及基于 sql 的规避方法请研读此文章: 报表工具的 SQL 植入风险 ](http://c.raqsoft.com.cn/article/1535513599294)。(报表与 sql 植入的关系,也引自此文。)

报表工具规避 SQL 植入

上面的内容以及所连接的文章介绍了经过 sql 自己的一些限制来防止 sql 注入,那么报表工具能不能也提供一些方法呢?好比著名的润乾报表。

答案确定是有的。

目前,报表工具通常都会提供敏感词检查,当传进来的替换子句中包含某些特定词时将被拒绝,好比不多有人会用 select,from union 这些 SQL 关键字做为字段名,那么,咱们能够首先判断一下替换子句中是否包含有 select,from 这些词,若是有就能够认为受到攻击并拒绝执行。这样作确定会牺牲一点灵活性,例若有时传进来的子句万一真的会含有这些关键字,不过这种状况相对少见,在得到了较好的安全性的同时,损失的灵活性能够接受。

下面咱们就看一下,润乾报表里具体怎么用这种方法?

两种方式:

一、raqsoftConfig.xml 配置须要检查的敏感词列表

该方法是由产品提供的方法,配置好敏感词列表后,报表计算时首先会对每一个参数值逐一检查,一旦发现检查不经过的状况,报表再也不继续执行,并返回错误信息。

配置如

3png

属性名:disallowedParamWordList,value 为禁用敏感词列表,多个之间用逗号分隔,英文字母不区分大小写。

这个方式用起来很简单,直接改配置,实施或维护人员就能简单操做。

如访问 Url:http://localhost:6868/demo/reportJsp/showReport.jsp?rpx=a.rpx&ar g2= 华北 union select * from users,其中红色部分为经过参数 arg2 植入的 sql

此时会报错,报表数据集(sql)不会再执行。

 

二、自定义参数检查类

对应方法 1,更为灵活。

(1)对全部参数值检查,代替方法 1

(2)针对某些指定的参数进行检查

(3)错误信息可自定义

public class … implements IParamChecker {

    // 校验不经过返回 false,提供 paramName 以便用户只检查某种形式的参数

    public boolean check(String s, String s1) {

        //s 为报表–参数内,定义的参数名;s1 为报表接收到的对应 s 的参数值

        return false;

    }

    // 检验不经过时,可获取详细信息

    public String getCause() {

        return “错误信息”;

    }

}

接口为 IParamChecker,两个方法:check()为检查实现,getCause() 负责返回错误信息。

下面是介绍使用方法的简单代码示例:

(1)实现自定义类

A、对全部参数值检查

public class ResistSQLInject implements IParamChecker {

    private String cause = "";

    private List wordList = new ArrayList();

    public boolean check(String paramName, String inputValue) {

        if(inputValue == null || inputValue.length() == 0){// 若是参数值为空,则无需检查

            return true;

        }

        if(wordList == null){ // 若是检测关键字列表是空,则不做检查

            return true;

        }

for(int i = 0; i < wordList.size(); i++){

            inputValue = inputValue.toLowerCase();// 这里作,是为了避免区分大小写

            if(inputValue.indexOf(wordList.get(i).toLowerCase())>= 0){       

                return false;

            }

        }

        return true;

    }  

    public String getCause() {

        String tmp = this.cause;

        this.cause = "";

        return tmp;

    }

}

B、针对某些参数检查

 private String cause = "";

    private List wordList = new ArrayList();

    /*

     * @paramName 验证的参数名

     * @inputValue 验证的参数值

     */

    public boolean check(String paramName, String inputValue) {

        //wordList.add(“select”);

        if(wordList == null){ // 若是检测关键字列表是空,则不做检查

            return true;

        }

        if(paramName==“userID”){

            if(inputValue == null || inputValue.length() == 0){// 若是参数值为空,则无需检查

                return true;

            }

            for(int i = 0; i < wordList.size(); i++){

                inputValue = inputValue.toLowerCase();// 这里作,是为了避免区分大小写

                if(inputValue.indexOf(wordList.get(i).toLowerCase())>= 0){

                    StringBuffer sb = new StringBuffer();

                    sb.append(“校验未经过,”).append(paramName).append(“参数中含有如下词汇:”).append(wordList.get(i))

                            .append(“\n 位置:”).append(inputValue.indexOf(wordList.get(i).toLowerCase()));

                    this.cause = sb.toString();

                    return false;

                }

            }

        }

        return true;

    }

C、自定义错误信息

public boolean check(String paramName, String inputValue) {

        //wordList.add(“select”);

        if(wordList == null){ // 若是检测关键字列表是空,则不做检查

            return true;

        }

        if(inputValue == null || inputValue.length() == 0){// 若是参数值为空,则无需检查

            return true;

        }

        for(int i = 0; i < wordList.size(); i++){

            inputValue = inputValue.toLowerCase();// 这里作,是为了避免区分大小写

            if(inputValue.indexOf(wordList.get(i).toLowerCase())>= 0){

StringBuffer sb = new StringBuffer();

                sb.append(“参数:”).append(paramName).append(“检查未经过,”).append(“含有如下敏感词汇:”).append(wordList.get(i))

                        .append(“。\n 谨记:\n”).append(“道路千万条 \n 规范第一条 \n 数据不规范 \n 亲人两行泪”);

                this.cause = sb.toString();

                return false;

            }

        }

        return true;

    }

(2)配置自定义类

xml(raqsoftConfig.xml):

paramCheckClass 设置参数值校验的类路径

 

5png

自定义错误信息的效果:

访问 URL:http://localhost:6868/demo/reportJsp/showReport.jsp?rpx=a.rpx&arg2= 华北 union select * from users

6png

总结,做为应用开发,代码中应尽可能避免拼 sql 的方式,相应地能够考虑 prepardStatement,从而避免 sql 植入的风险。而做为报表工具,若是提供了 sql 子句替换的方案,必定要考虑 sql 植入的风险,毕竟报表开发人员不是 dba,这种安全漏洞一旦发生,后果很是严重。

相关文章
相关标签/搜索