報表工具的 SQL 植入sql注入風險及規避方法

原文鏈接:http://c.raqsoft.com.cn/article/1561683907950?r=CGQ

 

 

互聯網時代帶來方便的同時也帶來了安全隱患,各種安全問題可說是防不勝防,特別是大家日益關心的個人信息等方面,貌似很難有什麼安全和隱私可言。

而在報表作爲常用的信息載體,更是直接面臨各種安全挑戰,例如:SQL 植入。

什麼是 SQL 植入?!

報表爲啥會有 SQL 植入風險?!

能不能通過報表工具提供的功能避免 SQL 植入?!

SQL 植入的概念

首先,認識一下什麼是 sql 植入?

Sql 植入也常被叫做 SQL 注入,具體的做法是通過把 SQL 命令插入到 Web 表單項或頁面請求(Url)的查詢字符串中提交,最終達到欺騙服務器執行惡意操作的目的。

常見案例包括通過植入 SQL 騙過登錄驗證。而之前很多影視網站泄露 VIP 會員密碼的事件,很多就是通過 sql 植入到 WEB 表單暴出的,並且這類表單特別容易受到攻擊。通過 SQL 植入,不僅可以非法獲取賬號信息,嚴重的還能夠篡改、刪除重要數據信息。

爲什麼存在 SQL 植入呢?

知道了 sql 植入的概念,我們也應該瞭解爲什麼會出現 SQL 植入,問題出現在哪個環節?

1png

上圖是一個最簡單的三層架構的應用結構,包括業務展現層、 數據處理層、數據源。也可以理解成我們常說的前端、後臺、後臺數據源。

其中,數據源裏有個叫數據庫的東西,這是最常見的數據管理和存儲方式,而關係型數據庫那就更常見了,比如 oracle、db2、mysql 等。

開發的應用(數據處理層,也就是後臺)爲了和操作數據庫(增刪改查),必須和數據庫之間有交流的接口,例如 jdbc 或 odbc,這對於技術人員或是 IT 銷售人員也是耳熟能詳的。

對於數據庫的操作,尤其關係型的,普遍使用的就是 sql 語言。SQL 是一種高級的非過程化語言,只描述做什麼而不需要告訴數據庫怎麼做。 SQL 腳本通過 api 以字符串方式傳入數據庫,數據庫收到 sql 後只管執行然後將結果返回。對於數據庫本身來說,它是不知道傳來的 sql 是合法還是不合法的,而正是這種完全的信任,導致出現了 sql 植入的風險。

歸根結底,SQL 植入利用了應用程序的漏洞,如果編寫數據處理的代碼時沒有充分考慮 sql 植入風險,攻擊者將很容易將 sql 命令植入後臺數據庫引擎執行。而這些不是按照設計者或開發者的意圖去執行的 sql 命令都會被視爲惡意代碼。

如何攻擊?

瞭解了 sql 植入攻擊的基本原理,我們就來看看具體怎麼攻擊。多種多樣的攻擊方式中比較常見的包括:

1、特殊的輸入參數

2、未處理特殊字符“–”、“#”

3、利用不合理的數據庫配置

案例 1、特殊的輸入參數

常見的如 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 條件變爲恆真,也就成功騙過了驗證,登錄進了系統。

案例 2、未處理特殊字符

以常用的註釋符“–” 爲例:一般的數據庫均採用其作爲註釋符。另外,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 數據庫,把“–”改爲“#”同樣可以實現注入。

案例 3、利用不合理的數據庫配置

常見的是權限配置不合理、過高,就會有 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 這些詞,如果有就可以認爲受到攻擊並拒絕執行。這樣做肯定會犧牲一點靈活性,例如有時傳進來的子句萬一真的會含有這些關鍵字,不過這種情況相對少見,在獲得了較好的安全性的同時,損失的靈活性可以接受。

下面我們就看一下,潤乾報表裏具體怎麼用這種方法?

兩種方式:

1、raqsoftConfig.xml 配置需要檢查的敏感詞列表

該方法是由產品提供的方法,配置好敏感詞列表後,報表計算時首先會對每個參數值逐一檢查,一旦發現檢查不通過的情況,報表不再繼續執行,並返回錯誤信息。

配置如

3png

屬性名:disallowedParmWordList,value 爲禁用敏感詞列表,多個之間用逗號分隔,英文字母不區分大小寫。

這個方式用起來很簡單,直接改配置,實施或維護人員就能簡單操作。

如訪問 Url:http://localhost:6868/demo/reportJsp/showReport.jsp?rpx=a.rpx&ar g2= 華北 union select * from users,其中紅色部分爲通過參數 arg2 植入的 sql

此時會報錯,報表數據集(sql)不會再執行。

4png

2、自定義參數檢查類

對應方法 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,這種安全漏洞一旦發生,後果非常嚴重。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章