深入理解Shiro反序列化原理( 三 )

前面代码逻辑还是差不多的,先从context中获取principal对象,然后检查是是否为空,如果为空则调用getRememberedIdentity创建然后设置到context中,否则直接返回,所以如果要触发反序列化这里必须要为空 。我们跟进resolvePrincipals方法中看一下 。
public PrincipalCollection resolvePrincipals() {PrincipalCollection principals = getPrincipals();if (CollectionUtils.isEmpty(principals)) {AuthenticationInfo info = getAuthenticationInfo();if (info != null) {principals = info.getPrincipals();}}if (CollectionUtils.isEmpty(principals)) {Subject subject = getSubject();if (subject != null) {principals = subject.getPrincipals();}}if (CollectionUtils.isEmpty(principals)) {Session session = resolveSession();if (session != null) {principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);}}return principals;}这个方法就和前面的resolveSession有点不太一样了,他第一次调用了getPrincipals如果为空还从其地方也获取了相关对象来构建principals,可以看到最后也获取了session对象 。如果前面已经设置了session对象,那么这里返回的就一定不会是null,最后就不会调用rememberMe导致反序列化 。所以我们在利用shiro反序列化时一定要删除cookie中的JSESSIONID字段 。
最后使用context创建对应环境的subject对象,这个对象是shiro框架对开发者使用的一个接口对象,在登录及认证授权时都是调用的该对象,由他内部再去调用securitymanager对象的操作 。
最后回到AbstractShiroFilter#doFilterInternal中,调用了Subject#execute(java.util.concurrent.Callable)方法,传入了updateSessionLastAccessTime和executeChain方法 。这里如果直接跟进这两个方法回错过一个细节,就是将subject对象设置打ThreadLocal中,但由于这个和shiro中的漏洞关系不大就不再跟进分析了 。
updateSessionLastAccessTime方法没什么用就不说了,下面跟进executeChain说一下shiro中的路径匹配 。

深入理解Shiro反序列化原理

文章插图
image
在这个方法里面就分两步,第一步根据request获取对应的过滤器,然后第二部执行过滤方法 。
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {FilterChain chain = origChain;FilterChainResolver resolver = getFilterChainResolver();if (resolver == null) {...return origChain;}FilterChain resolved = resolver.getChain(request, response, origChain);...return chain;}public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {FilterChainManager filterChainManager = getFilterChainManager();if (!filterChainManager.hasChains()) {return null;}String requestURI = getPathWithinApplication(request);for (String pathPattern : filterChainManager.getChainNames()) {if (pathMatches(pathPattern, requestURI)) {return filterChainManager.proxy(originalChain, pathPattern);}}return null;}首先获取FilterChainResolver对象,这个对象就是在WebEnvironment创建时初始化的,然后在ShiroFilter初始化时设置到该类的属性中 。然后根据请求URL匹配对应的过滤器,最后创建一个filterChain的静态代理类 。其中shiro权限绕过的原因主要就是由于路径匹配时匹配到了错误的过滤器或未匹配到shiro内置的过滤器,导致绕过shiro的过滤器检查,但其请求URL被tomcat过滤器处理后仍然能获取对应的资源 。
SHIRO-550源码分析上面分析了shiro框架的大概流程,在介绍DefaultSecurityManager#createSubject(SubjectContext)中创建Principal时就会对cookie中的rememberMe解析并反序列化 。下面就从这开始进行深入分析 。
首先进入org.apache.shiro.mgt.DefaultSecurityManager#getRememberedIdentity方法 。
protected PrincipalCollection getRememberedIdentity(SubjectContext subjectContext) {RememberMeManager rmm = getRememberMeManager();if (rmm != null) {try {return rmm.getRememberedPrincipals(subjectContext);} catch (Exception e) {......}}return null;}public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {PrincipalCollection principals = null;try {byte[] bytes = getRememberedSerializedIdentity(subjectContext);if (bytes != null && bytes.length > 0) {principals = convertBytesToPrincipals(bytes, subjectContext);}} catch (RuntimeException re) {principals = onRememberedPrincipalFailure(re, subjectContext);}return principals;}其中getRememberedSerializedIdentity方法主要是获取rememberMe的值并进行base64解密,然后convertBytesToPrincipals对base64解密后的值进行AES解密并反序列化 。注意这里的异常捕获,先提一下后面再回来分析 。我们继续跟进convertBytesToPrincipals方法 。
深入理解Shiro反序列化原理

文章插图


推荐阅读