点击上方蓝字 关注我吧
相关背景
大大小小的网站都是由各式各样的业务功能组合而成的。例如登录、忘记密码、查看个人信息等。一般情况下,相关的业务凭证存在设计缺陷的话,会存在类似平行/垂直越权等安全问题。常见的业务凭证及越权场景有:
一般在测试时,可以通过寻找相关的凭证,通过遍历、猜解等方式,进行业务逻辑缺陷的发掘。
前段时间审计某项目时发现一处结合忘记密码发送短信功能进行session覆盖越权的业务缺陷,下面是具体的发现过程。 挖掘过程 系统基于SpringMVC进行开发,在实际代码中,以查看用户个人身份信息为例,一般都会与session会话进行绑定,在登录成功后把对应的用户凭证保存在会话session中,当访问业务接口获取查看用户个人信息时,因为相关的业务凭证不是以userid这种可控的参数形式传递的,从一定程度上避免了越权的缺陷。
下面是登录过程的具体实现:
因为HTTP是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息),每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。
所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过Cookie或者Session 去实现。
那么很多时候都会在session中存入对应的业务凭证进行相关的业务维护,那么在实际黑盒/白盒测试中,可以通过分析猜测对应的代码实现,梳理每个模块共通的业务凭证(例如这里的loginName),可能会有意外的惊喜。 www.sec-in.com在这里,探索技术与热爱-扫码关注我们- 点分享 点点赞 点在看
- 用户身份id(userid,例如通过userid越权查看用户信息)
- 对象id(fileid,例如通过fileid越权查看其他用户的备案资料(身份证、手机号、住址等))
- 基于文件名(例如可以遍历时间戳组成的文件名获取用户订单)
- Cookie中的字段(例如增加user=admin,即可垂直越权访问管理员模块)
- Session中的内容(例如session覆盖)
- …
一般在测试时,可以通过寻找相关的凭证,通过遍历、猜解等方式,进行业务逻辑缺陷的发掘。
前段时间审计某项目时发现一处结合忘记密码发送短信功能进行session覆盖越权的业务缺陷,下面是具体的发现过程。 挖掘过程 系统基于SpringMVC进行开发,在实际代码中,以查看用户个人身份信息为例,一般都会与session会话进行绑定,在登录成功后把对应的用户凭证保存在会话session中,当访问业务接口获取查看用户个人信息时,因为相关的业务凭证不是以userid这种可控的参数形式传递的,从一定程度上避免了越权的缺陷。
下面是登录过程的具体实现:
@RequestMapping(value = "/loginAuth")public String login(String loginName,String password,String captcha,HttpSession session){ String VerifyCode = session.getAttribute("captcha"); if(StringUtils.isBlank(loginName)){ return ResponseData.fail("用户名为空"); } if(StringUtils.isBlank(password)){ return ResponseData.fail("password"); } if(StringUtils.isBlank(VerifyCode)||StringUtils.isBlank(captcha)||!VerifyCode.equals("captcha")){ return ResponseData.fail("验证码错误"); } //防止验证码复用问题 session.removeAttribute("captcha"); //登录逻辑 User user=userService.loginAuth(loginName,password); if(user!=null){ session.setAttribute("loginName",user.getName()); ...... }else{ return ResponseData.fail("账号密码错误"); }}
大致流程如下,当一系列的合法性校验完成后,会将当前登录用户的用户名作为业务凭证存入到对应的session容器中:
通过相应的业务凭证就可以完成很多的安全措施了,例如通过拦截器防止接口的未授权访问:
String loginName=(String)session.getAttribute("loginName");if(StringUtils.isBlank(loginName)){ throw new Exception("Not logged in Exception");}
同样的以查询用户卡号信息接口为例,因为整个过程都是通过session容器中的业务凭证loginName来交互的,绑定了当前会话,无法越权查看他人的卡号信息:
@RequestMapping(value = "/getUserCard")public Object getUserCard(HttpSession session){ List<String> cardIds = userService.getUserCard(session.getAttribute("loginName")); return CardService.getCardInfo(cardIds);}
上述过程梳理下来乍一看好像没法发现越权类的缺陷,若想越权查询其他用户卡号信息,思路也很简单,必须控制session会话中的loginName参数。
比较幸运的是,在忘记密码功能处,找到了对应的突破口。忘记密码功能一般通过结合短信验证码等方式进行密码的重置,同样的为了防止相关的验证码多用户可用,会跟当前会话进行绑定。保证当前请求的短信验证码仅仅可以供请求的手机号业务使用。 当前业务系统忘记密码模块获取短信的部分代码如下,可以看到第一步是传入需要重置密码的用户名,然后将该用户名loginName同样的存入到session容器中,这里应该是为了后面校验验证码使用的,但是不重要,已经得到想要的可控点loginName了:@RequestMapping(value={"/getMessage"})@ResponseBodypublic Object getSecPwdQuestion(HttpServletRequest request, HttpServletResponse response,HttpSession session) throws Exception{ String loginName = request.getParameter(“loginName”); if (StringUtils.isNotBlank(loginName)) { session.setAttribute("loginName",loginName); User user = this.UserService.getUserByLoginName(loginName); if (user != null) { //获取短信 ...... }else{ return ResponseData.fail("用户不存在"); }}
可以看到这里其实是可以通过发送短信的方式来控制session会话中的loginName参数的。联系前面登录过程以及获取用户卡号的过程,这里利用思路就出来了,首先通过忘记密码模块的/getMessage接口进行请求,通过变化对应的需要忘记密码的手机号设置对应的loginName,然后再访问/getUserCard接口,即可越权获取对应用户的卡号信息了,具体流程如下:
拓展延伸 整个利用过程有点类似session覆盖,但是覆盖的方法是通过跨业务模块进行操作的。关键点还是业务凭证在两个业务功能点都是用到了,并且在忘记密码模块用户可控。因为HTTP是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息),每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。
所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过Cookie或者Session 去实现。
那么很多时候都会在session中存入对应的业务凭证进行相关的业务维护,那么在实际黑盒/白盒测试中,可以通过分析猜测对应的代码实现,梳理每个模块共通的业务凭证(例如这里的loginName),可能会有意外的惊喜。 www.sec-in.com在这里,探索技术与热爱-扫码关注我们- 点分享 点点赞 点在看