在實際的業務開發中,SQL交互每每是業務系統中不可或缺的一項。在Java中提供了相似Mybatis、Hibernate、SpringData JPA等來知足相關的數據庫交互須要。可是因爲種種緣由,開發人員在處理應用程序和數據庫交互時,使用字符串拼接的方式構造SQL語句,致使了SQL注入問題。那麼有時候面對大量的接口存在SQL注入,迭代困難的時候,過濾器/攔截器即是不少開發人員的首選,經過過濾相關的SQL關鍵字,避免SQL注入獲得進一步利用。前端
針對上述場景,不少時候須要加檢查過濾器設計是否嚴謹,檢查是否有漏網之魚,致使SQL注入漏洞被攻擊者進行利用。前段時間審計某項目時發現一處SQL注入致使的"越權",如下是相關的過程。java
系統基於SpringMVC進行開發,業務主要是與簡歷編輯相關。相關的問題接口主要在修改我的簡歷處。通常來講,這種修改我的信息的業務,除了修改內容之外,主要傳遞兩個關鍵信息:mysql
在進行接口業務請求時,將業務相關的關鍵參數userid聰當前用戶的身份憑證(通常是session)獲取,綁定我的用戶身份,而後從前端獲取須要修改的resumeId,最後在保存信息進行SQL交互時,從會話在獲取的userId再與resumeId進行二次綁定,保證userId對應的用戶僅能修改本身的簡歷。相似的SQL語句以下:sql
UPDATE user_resume SET content='test',user_name='test'{省略相關內容} where userId = $userId and resumeId=$resumeId;
以下是相關的代碼:shell
首先是Controller,在Controller層對用戶的輸入進行相關的封裝(這裏是簡歷的相關信息),經過自動綁定的處理方式,直接將用戶的輸入綁定到resume對象裏,而後經過當前登陸會話獲取當前用戶的userId,經過調用service的update方法進行簡歷內容的更新:數據庫
@ResponseBody @RequestMapping(value = "/updateResume", method = RequestMethod.POST) public PagedResult<ResponseRes> updateResume(Resume resume) { String userId = (String)session.getAttribute("userid"); PagedResult<ResponseRes> pageResult = this.resumeService .update(resume,userid); return pageResult; }
查看service層實現,調用的是resumeService的update方法:swift
public PagedResult<ResponseRes> updateResume(Resume resume,String userid) { String rusumeId = resume.getId(); if(resumeId!=null){ boolean updateStatus = resumeDao.update(resume, userid,resumeId); if(updateStatus){ //更新成功,封裝返回結果 ...... }else{ //更新失敗 ...... } }else{ } return updateResule; }
講封裝好的簡歷內容以及userId跟ResumeId傳入resumeDao的update方法進行處理,這裏使用的是mybatis框架進行處理,查看mapper的具體實現:安全
UPDATE user_resume SET ...... <if test='resume.address!=null'> ,address=${resume.address}, </if> ...... where userId=${userId} and resumeId=${resumeId}; </select>
因此整個簡歷更新的流程如上描述,由於在進行SQL交互時,經過update對簡歷表進行維護,經過userId和resumeId限定要更新的行。由於userId跟用戶會話進行綁定,因此用戶僅能更新本身對應的簡歷,防止了越權問題。session
可是這裏因爲在Mybatis中使用了$進行註解,存在SQL注入風險。那麼進一步檢查是否存在相關的防禦措施。這裏發現相關的過濾器措施,應該是系統注入太多了,因此開發統一經過過濾器對用戶輸入進行處理,檢查過濾器的具體實現。mybatis
一樣的過濾器仍是檢查以下幾點:
這裏由兩個過濾器組成,分別負責xss以及SQL注入的輸入檢測。由於都是直接檢測到惡意輸入就直接返回通用報錯頁面,因此這裏不存在順序問題致使的filter繞過。
其次是獲取數據的方式,這裏經過MultipartHttpServletRequest multiReq = multipartResolver.resolveMultipart(request);對當前文件上傳進行轉換,轉換成普通的request對象後再進行相關的輸入檢查,防止multipart提交致使的bypass:
private CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException { public static String[] MULTI_FILE_WHITE_LIST = new String[]{"/manager/uplode"}; HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String pathInfo = request.getPathInfo() == null ? "" : request.getPathInfo(); String url = request.getServletPath() + pathInfo; String contentType = request.getContentType();// 獲取請求的content-type //若是是文件上傳接口不須要進行轉換,須要在接口處直接進行輸入檢查 ... // 文件上傳請求 *特殊請求 if (multipartResolver.isMultipart(request)) { MultipartHttpServletRequest multiReq = multipartResolver.resolveMultipart(request); request = multiReq; } //SQL注入檢查 ......
此外針對更新簡歷接口,其提交的數據類型爲正常的key-value形式,因此這裏filter獲取數據的方式是覆蓋全面的。
最後是相關的過濾規則,這裏要結合數據庫類型進行判斷。系統使用的是mysql的數據庫,過濾規則以下:
String badSqlStr ="'|and|exec|execute|insert|select|delete|update|count|drop|*|%|chr|mid|master|truncate|char|declare|sitename|net user|xp_cmdshell|;|or|-|+|,|like'|and|exec|execute|insert|create|drop|table|from|grant|use|group_concat|column_name|information_schema.columns|table_schema|union|where|select|delete|update|sleep|order|by|count|*|chr|mid|master|truncate|char|declare|;|--|+|,|like|//|/|%|#";
過濾了大部分的SQL關鍵字,包括select這種SQL注入拖取內容必須的關鍵字,還有xp_cmdshell這類敏感的SQL函數。針對更新簡歷接口,其中的SQL注入乍一看應該是沒法進行獲取數據庫敏感信息等惡意操做了。
經過上述分析,更新簡歷接口的越權跟SQL注入問題貌似暫時獲得了緩解。實際上這裏能夠經過SQL注入,來「越權」修改他人的簡歷。
能夠查到相關update語句的條件爲where userId=${userId} and resumeId=${resumeId},其中userId從當前會話獲取,用戶不可控,resumeId爲前端傳遞,用戶可控。那麼也就是說,當前端傳遞的resumeId爲1 or userId=2
時,便可在更新當前用戶簡歷同時,更新userId=2的用戶的簡歷內容,達到越權的效果。
提交的參數內容以下(由於過濾器中並未過濾or關鍵字,因此該邏輯能夠繞過過濾器安全檢查):
address=xxxxx&{相關簡歷內容}&resumeId=10001 or userId=2
最終執行的SQL語句大體以下:
UPDATE user_resume SET content='test',user_name='test'{省略相關內容} where userId = $userId and resumeId=$resumeId or userId=2;
這裏結合數據庫表進一步說明具體效果,爲了方便說明,將數據庫內容簡化成以下結構,userId表明用戶身份,resumeId表明對應的簡歷,最後content表明簡歷的內容:
正常狀況下,假設登陸了userId爲1的帳戶,嘗試更新其resumeId爲10000的簡歷,對應執行的SQL語句爲(這裏把內容test修改成test111):
UPDATE user_resume SET content='test111' where userId = 1 and resumeId=10000;
執行後對應的user_resume表內容變化以下:
根據前面的分析,由於resumeId存在SQL注入且用戶可控,此時嘗試提交resumeId=1 or userId=2,嘗試越權將userId=2的用戶簡歷內容修改成Access-Control-Bypass,對應執行的SQL語句以下:
UPDATE user_resume SET content='Access-Control-Bypass' where userId = 1 AND resumeId=1 OR userId = 2;
成功利用SQL注入"越權"修改他人用戶的簡歷信息:
針對SQL注入的修復,經過過濾器/攔截器對用戶的輸入進行檢查並非最佳的選擇
。在知足特定的場景狀況下,可能會致使例如上述的「越權」問題。
與此同時,不少開發人員存在誤區,認爲使用JPA、HQL進行SQL交互後便不會存在SQL問題了,SQL注入的本質是在處理應用程序和數據庫交互時,使用字符串拼接的方式構造SQL語句,同時相關的敏感參數可控
,只要知足即存在SQL注入風險,只是使用了HQL後,因爲其特色沒法直接執行原生SQL,及寫文件,執行命令等操做,也不支持跨庫查表等敏感操做。可是依舊能夠結合實際的場景,嘗試達到上述的「越權」危害。