一次SQL注入致使的"越權"

原文來自SecIN社區—做者:tkswifty

相關背景

  在實際的業務開發中,SQL交互每每是業務系統中不可或缺的一項。在Java中提供了相似Mybatis、Hibernate、SpringData JPA等來知足相關的數據庫交互須要。可是因爲種種緣由,開發人員在處理應用程序和數據庫交互時,使用字符串拼接的方式構造SQL語句,致使了SQL注入問題。那麼有時候面對大量的接口存在SQL注入,迭代困難的時候,過濾器/攔截器即是不少開發人員的首選,經過過濾相關的SQL關鍵字,避免SQL注入獲得進一步利用。前端

  針對上述場景,不少時候須要加檢查過濾器設計是否嚴謹,檢查是否有漏網之魚,致使SQL注入漏洞被攻擊者進行利用。前段時間審計某項目時發現一處SQL注入致使的"越權",如下是相關的過程。java

挖掘過程

  系統基於SpringMVC進行開發,業務主要是與簡歷編輯相關。相關的問題接口主要在修改我的簡歷處。通常來講,這種修改我的信息的業務,除了修改內容之外,主要傳遞兩個關鍵信息:mysql

  • 當前用戶的身份憑證userId
  • 當前用戶的業務編號(這裏是簡歷),resumeId

  在進行接口業務請求時,將業務相關的關鍵參數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表明簡歷的內容:

image.png

  正常狀況下,假設登陸了userId爲1的帳戶,嘗試更新其resumeId爲10000的簡歷,對應執行的SQL語句爲(這裏把內容test修改成test111):

UPDATE user_resume SET content='test111' where userId = 1 and resumeId=10000;

  執行後對應的user_resume表內容變化以下:

image.png

  根據前面的分析,由於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注入"越權"修改他人用戶的簡歷信息:

image.png

  針對SQL注入的修復,經過過濾器/攔截器對用戶的輸入進行檢查並非最佳的選擇。在知足特定的場景狀況下,可能會致使例如上述的「越權」問題。

  與此同時,不少開發人員存在誤區,認爲使用JPA、HQL進行SQL交互後便不會存在SQL問題了,SQL注入的本質是在處理應用程序和數據庫交互時,使用字符串拼接的方式構造SQL語句,同時相關的敏感參數可控,只要知足即存在SQL注入風險,只是使用了HQL後,因爲其特色沒法直接執行原生SQL,及寫文件,執行命令等操做,也不支持跨庫查表等敏感操做。可是依舊能夠結合實際的場景,嘗試達到上述的「越權」危害。

相關文章
相關標籤/搜索