一次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,及写文件,执行命令等操做,也不支持跨库查表等敏感操做。可是依旧能够结合实际的场景,尝试达到上述的“越权”危害。

相关文章
相关标签/搜索