参考文章
CVE-2012-5055漏洞:cve.scap.org.cn/vuln/VHN-58…
CVE-2012-5055漏洞解析参考1:www.jianshu.com/p/c620d4bfd…
CVE-2012-5055漏洞解析参考2:github.com/spring-proj…
概述
在spring boot项目使用spring security时,想通过抛出的UsernameNotFoundException来提示用户账号不存在,需要注册。
但是抛出的UsernameNotFoundException却并未被捕捉,反而被后一个判断的异常BadCredentialsException捕捉。
通过debug和查看security源码发现,在AbstractUserDetailsAuthenticationProvider.java的authenticate中:
if (!this.hideUserNotFoundExceptions) {
throw ex;
}
throw new BadCredentialsException(……);
不禁为之疑惑,为何要出现这个参数来隐藏UsernameNotFoundException。
通过探求,认识到CVE-2012-5055漏洞,因此猜测这里设置这个参数也是为了防止黑客通过返回的异常信息来判断用户账户是否存在。
Bug场景
调用关系
代码演示
attemptAuthentication方法获取前端传递的用户登录信息并返回令牌,此时会去调用loadUserByUsername去验证数据库的用户信息
@Override
public Authentication attemptAuthentication(……) throws …… {
// 获取前端传递的用户登录信息
// ……
// 验证并返回令牌信息
return getAuthenticationManager().authenticate(……);
}
loadUserByUsername方法进行查询数据库并验证是否存在该用户,如果不存在就抛出UsernameNotFoundException
@Override
public UserDetails loadUserByUsername(String username) {
// 获取用户信息
User userDetails = userDetailsMapper.getUserDetails(username);
// 判断数据库中是否存在用户信息
if (userDetails == null) {
throw new UsernameNotFoundException("用户不存在,请进行注册");
}
return ……;
}
unsuccessfulAuthentication方法本该捕获到UsernameNotFoundException,但是却被BadCredentialsException捕获
@Override
protected void unsuccessfulAuthentication(……) throws IOException {
if (failed instanceof UsernameNotFoundException) {
// 通知前端用户不存在
} else if (failed instanceof BadCredentialsException) {
// BadCredentialsException
} else {
// ……
}
}
Bug分析
抛出的UsernameNotFoundException却捕获了另一个异常BadCredentialsException,猜测是security中途做了些处理,导致抛出了另一个异常。
关键在于异常从loadUserByUsername抛出后到被unsuccessfulAuthentication捕获异常都执行了那些内容。
通过debug跟随流程,找到了调用loadUserByUsername的方法DaoAuthenticationProvider.retrieveUser()方法
然后找到了调用retrieveUser的方法AbstractUserDetailsAuthenticationProvider.authenticate()
原来是该类隐藏了UsernameNotFoundException异常,并抛出了BadCredentialsException异常,所以导致捕获出现错误。
很奇怪为何会出现这么一个参数,要去隐藏用户未找到的信息,在DaoAuthenticationProvider中有这么一个参数
通过这个参数去了解了SEC-2056,发现了这个CVE-2012-5055漏洞(感觉也不算是个漏洞,只能说一种被攻击的可能手段吧)
Bug总结
Security的默认实现中,会隐藏UsernameNotFoundException异常。
原因是避免黑客通过比较账户校验时间长短来判断用户是否存在,甚至碰撞出密码来,设置对于用户不存在的登录信息进行随机的等待时间或者实行验证等手段来避免较快的返回失败信息,并通过隐藏UsernameNotFoundException来不让黑客通过响应信息推断用户的存在。
解决方案
方案一:将参数
hideUserNotFoundExceptions改为false
自行百度关键字:"hideUserNotFoundExceptions"即可。
方案二:不捕获
UsernameNotFoundException
如果网站安全性要求比较高,或者懒得改参数,可以选择不捕获这个异常,通过其他异常描述信息即可(比如提示用户“用户名或密码不对”,而不是直接提示用户名不对)。