網際網路時代帶來友善的同時也帶來了安全隐患,各種安全問題可說是防不勝防,特别是大家日益關心的個人資訊等方面,貌似很難有什麼安全和隐私可言。
而在報表作為常用的資訊載體,更是直接面臨各種安全挑戰,例如:SQL 植入。
什麼是 SQL 植入?!
報表為啥會有 SQL 植入風險?!
能不能通過報表工具提供的功能避免 SQL 植入?!
SQL 植入的概念
首先,認識一下什麼是 sql 植入?
Sql 植入也常被叫做 SQL 注入,具體的做法是通過把 SQL 指令插入到 Web 表單項或頁面請求(Url)的查詢字元串中送出,最終達到欺騙伺服器執行惡意操作的目的。
常見案例包括通過植入 SQL 騙過登入驗證。而之前很多影視網站洩露 VIP 會員密碼的事件,很多就是通過 sql 植入到 WEB 表單爆出的,并且這類表單特别容易受到攻擊。通過 SQL 植入,不僅可以非法擷取賬号資訊,嚴重的還能夠篡改、删除重要資料資訊。
為什麼存在 SQL 植入呢?
知道了 sql 植入的概念,我們也應該了解為什麼會出現 SQL 植入,問題出現在哪個環節?

上圖是一個最簡單的三層架構的應用結構,包括業務展現層、 資料處理層、資料源。也可以了解成我們常說的前端、背景、背景資料源。
其中,資料源裡有個叫資料庫的東西,這是最常見的資料管理和存儲方式,而關系型資料庫那就更常見了,比如 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 植入提供了可乘之機。
例如,如果希望查詢指定時間段的資料,可以把時間段作為參數傳遞給報表,報表在從資料庫中取數時将這些參數拼接到取數 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 配置需要檢查的敏感詞清單該方法是由産品提供的方法,配置好敏感詞清單後,報表計算時首先會對每個參數值逐一檢查,一旦發現檢查不通過的情況,報表不再繼續執行,并傳回錯誤資訊。
配置如
屬性名:disallowedParamWordList,value 為禁用敏感詞清單,多個之間用逗号分隔,英文字母不區分大小寫。
這個方式用起來很簡單,直接改配置,實施或維護人員就能簡單操作。
如通路 Url:http://localhost:6868/demo/reportJsp/showReport.jsp?rpx=a.rpx&ar g2= 華北 union select * from users,其中紅色部分為通過參數 arg2 植入的 sql
此時會報錯,報表資料集(sql)不會再執行。
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();
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 設定參數值校驗的類路徑
自定義錯誤資訊的效果:
通路 URL:http://localhost:6868/demo/reportJsp/showReport.jsp?rpx=a.rpx&arg2= 華北 union select * from users
總結,作為應用開發,代碼中應盡量避免拼 sql 的方式,相應地可以考慮 prepardStatement,進而避免 sql 植入的風險。而作為報表工具,如果提供了 sql 子句替換的方案,一定要考慮 sql 植入的風險,畢竟報表開發人員不是 dba,這種安全漏洞一旦發生,後果非常嚴重。
更多內建部署安全相關問題請檢視:內建部署相關問題分類導航
* 報表安全的自我檢查
* Mac 環境中部署報表
* 潤乾報表權限管理機制
* 如何把潤乾報表嵌入 web 應用中
* 潤乾報表資料權限控制方案
* 潤乾報表叢集緩存同步功能介紹