Shiro历史漏洞学习(一)

Shiro是一个强大且易用的Java安全框架,提供了身份验证、授权、密码学和会话管理等功能。本文将会梳理、总结和学习其相关经典漏洞。根据官方网站上的漏洞通报,Shiro 在历史上共通报了 17 个 CVE(以及最新披露的CVE),其中包含认证绕过、反序列化等漏洞类型,接下来我们来依次学习。

0x01 前置知识

功能特性

Apache Shiro 是一个 Java 安全框架,包括如下功能和特性:

  • Authentication:身份认证/登陆,验证用户是不是拥有相应的身份。在 Shiro 中,所有的操作都是基于当前正在执行的用户,这里称之为一个 Subject,在用户任意代码位置都可以轻易取到这个Subject。Shiro 支持数据源,称之为 Realms,可以利用其连接 LDAP\AD\JDBC 等安全数据源,并支持使用自定义的 Realms,并可以同时使用一个或多个 Realms 对一个用户进行认证,认证过程可以使用配置文件配置,无需修改代码。同时,Shiro 还支持 RememberMe,记住后下次访问就无需登录。
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限。同样基于 Subject、支持多种 Realms。Shiro 支持 Wildcard Permissions ,也就是使用通配符来对权限验证进行建模,使权限配置简单易读。Shiro 支持基于 Roles 和基于 Permissions 两种方式的验证,可以根据需要进行使用。并且支持缓存产品的使用。
  • Session Manager:会话管理,用户登陆后就是一次会话,在没有退出之前,它的所有信息都在会话中。Shiro 中的一切(包括会话和会话管理的所有方面)都是基于接口的,并使用 POJO 实现,因此可以使用任何与 JavaBeans 兼容的配置格式(如 JSON、YAML、Spring XML 或类似机制)轻松配置所有会话组件。Session 支持缓存集群的方式;还支持事件侦听器,允许在会话的生命周期内侦听生命周期事件,以执行相关逻辑。Shiro Sessions 保留发起会话的主机的 IP 地址,因此可以根据用户位置来执行不同逻辑。Shiro 对 Web 支持实现了 HttpSession 类及相关全部 API。也可以在 SSO 中使用。
  • Cryptography:加密,保护数据的安全性;Shiro 专注于使用公私钥对数据进行加密,以及对密码等数据进行不可逆的哈希。
  • Permissions:用户权限;Shiro 将所有的操作都抽象为 Permission,并默认使用 Wildcard Permissions 来进行匹配。Shiro 支持实例级别的权限控制校验,例如domain: action:instance
  • Caching:缓存,为了提高 Shiro 在业务中的性能表现。Shiro 的缓存支持基本上是一个封装的 API,由用户自行选择底层的缓存方式。缓存中有三个重要的接口 CacheManager/Cache/CacheManagerAware ,Shiro 提供了默认的 MemoryConstrainedCacheManager 等实现。

SecurityManager

org.apache.shiro.mgt.SecurityManager 是 shiro 的一个核心接口,接口负责了一个 Subject 也就是“用户”的全部安全操作:

  • 接口本身定义了 createSubject、login、logout 三个方法用来创建 Subject、登陆和退出。
  • 扩展了 org.apache.shiro.authc.Authenticator 接口,提供了 authenticate 方法用来进行认证。
  • 扩展了 org.apache.shiro.authz.Authorizer 接口,提供了对 Permission 和 Role 的校验方法。包括 has/is/check 相关命名的方法。
  • 扩展了 org.apache.shiro.session.mgt.SessionManager 接口,提供了 start、getSession 方法用来创建可获取会话。

Shiro 为 SecurityManager 提供了一个包含了上述所有功能的默认实现类 org.apache.shiro.mgt.DefaultSecurityManager,中间继承了很多中间类,并逐层实现了相关的方法,继承关系如下图。

DefaultSecurityManager 中包含以下属性:

  • subjectFactory:默认使用 DefaultSubjectFactory,用来创建具体 Subject 实现类。
  • subjectDAO:默认使用 DefaultSubjectDAO,用于将 Subject 中最近信息保存到 Session 里面。
  • rememberMeManager:用于提供 RememberMe 相关功能。
  • sessionManager:默认使用 DefaultSessionManager,Session 相关操作会委托给这个类。
  • authorizer:默认使用 ModularRealmAuthorizer,用来配置授权策略。
  • authenticator:默认使用 ModularRealmAuthenticator,用来配置认证策略。
  • realm:对认证和授权的配置,由用户自行配置,包括 CasRealm、JdbcRealm 等。
  • cacheManager:缓存管理,由用户自行配置,在认证和授权时先经过,用来提升认证授权速度。

DefaultSecurityManager 还有一个子类,就是 org.apache.shiro.web.mgt.DefaultWebSecurityManager,这个类在 shiro-web 包中,是 Shiro 为 HTTP/SOAP 等 http 协议连接提供的实现类,这个类默认创建配置了 org.apache.shiro.web.mgt.CookieRememberMeManager 用来提供 RememberMe 相关功能。

Subject

org.apache.shiro.subject.Subject 是一个接口,用来表示在 Shiro 中的一个用户。因为在太多组件中都使用了 User 的概念,所以 Shiro 故意避开了这个关键字,使用了 Subject

Subject 接口同样提供了认证(login/logout)、授权(访问控制 has/is/check 方法)以及获取会话的能力。在应用程序中如果想要获取一个当前的 Subject,通常使用 SecurityUtils.getSubject() 方法即可。

单从方法的命名和覆盖的功能来看,Subject 提供了与 SecurityManager 非常近似的方法,用来执行相关权限校验操作。而实际上,Subject 接口在 core 包中的实现类 org.apache.shiro.subject.support.DelegatingSubject 本质上也就是一个 SecurityManager 的代理类。

DelegatingSubject 中保存了一个 transient 修饰的 SecurityManager 成员变量,在使用具体的校验方法时,实际上委托 SecurityManager 进行处理,如下图:

DelegatingSubject 中不会保存和维持一个用户的“状态(角色/权限)”,恰恰相反,每次它都依赖于底层的实现组件 SecurityManager 进行检查和校验,因此通常会要求 SecurityManager 的实现类来提供一些缓存机制。所以本质上,Subject 也是一种“无状态”的实现。

Realm

Realm 翻译过来是“领域、王国”,这里可以将其理解以为一种“有界的范围”,实际上就是权限和角色的认定。

org.apache.shiro.realm.Realm 是 Shiro 中的一个接口,Shiro 通过 Realm 来访问指定应用的安全实体——用户、角色、权限等。一个 Realm 通常与一个数据源有 1 对 1 的对应关系,如关系型数据库、文件系统或者其他类似的资源。

因此,此接口的实现类,将使用特定于数据源的 API 来进行认证或授权,如 JDBC、文件IO、Hibernate/JPA 等等,官方将其解释为:特定于安全的 DAO 层。

在使用中,开发人员通常不会直接实现 Realm 接口,而是实现 Shiro 提供了一些相关功能的抽象类 AuthenticatingRealm/AuthorizingRealm,或者使用针对特定数据源提供的实现类如 JndiLdapRealm/JdbcRealm/PropertiesRealm/TextConfigurationRealm/IniRealm 等等。继承关系大概如下:

较多情况下,开发人员会自行实现 AuthorizingRealm 类,并重写 doGetAuthorizationInfo/doGetAuthenticationInfo 方法来自行实现自身的认证和授权逻辑。

总结
通过对以上三个组件的了解,一次认证及授权的校验流程就形成了:

  • 1.应用程序通过获取当前访问的 Subject(也就是用户),并调用其相应校验方法;
  • 2.Subject 将校验委托给 SecurityManager 进行判断;
  • 3.SecurityManager 会调用 Realm 来获取信息来判断用户对应的角色能否进行操作。

0x02 安全漏洞

CVE-2010-3863

漏洞信息
详情
漏洞编号 CVE-2010-3863 / CNVD-2010-2715
影响版本 shiro < 1.1.0 & JSecurity 0.9.x
漏洞描述 Shiro 在对请求路径与 shiro.ini 配置文件配置的 AntPath 进行对比前未进行路径标准化,导致使用时可能绕过权限校验
漏洞关键字 路径标准化
漏洞补丁 Commit-ab82949
相关链接 https://vulners.com/nessus/SHIRO_SLASHDOT_BYPASS.NASL https://marc.info/?l=bugtraq&m=128880520013694&w=2

漏洞分析

源码下载:这里选择了用IDEA进行远程调试jar包
先分析一下Shiro身份验证的流程:Shiro使用org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain 方法获取和调用要执行的 Filter,逻辑如下:

getPathWithinApplication()方法中调用 WebUtils.getPathWithinApplication()方法,用来获取请求路径。

获取 URI 路径的方法 getRequestUri() 获取 javax.servlet.include.request_uri 的值,并调用 decodeAndCleanUriString() 处理。

decodeAndCleanUriString() 是 URL Decode 及针对 JBoss/Jetty 等中间件在 url 处添加 ;jsessionid 之类的字符串的适配,对 ; 进行了截取。

处理之后的请求 URL 将会使用 AntPathMatcher#doMatch 进行匹配尝试。

流程梳理到这里就出现了一个重大的问题:在匹配之前,没有进行标准化路径处理,导致 URI 中如果出现一些特殊的字符,就可能绕过安全校验。比如如下配置:

在上面的配置中,为了一些有指定权限的需求的接口进行了配置,并为其他全部的 URL /** 设置了 anno 的权限。在这种配置下就会产生校验绕过的风险。

正常访问:/admin,会由于需要认证和权限被 Shiro 的 Filter 拦截并跳转至登录 URL。

漏洞修复

Shiro 在 Commit-ab82949 更新中添加了标准化路径函数。

/、//、/./、/../ 等进行了处理。

CVE-2014-0074

漏洞信息
详情
漏洞编号 CVE-2014-0074 / CNVD-2014-03861 / SHIRO-460
影响版本 shiro 1.x < 1.2.3
漏洞描述 当程序使用LDAP服务器并启用非身份验证绑定时,远程攻击者可借助空的用户名或密码利用该漏洞绕过身份验证。
漏洞补丁 Commit-f988846
相关链接 https://stackoverflow.com/questions/21391572/shiro-authenticates...in-ldap

https://www.openldap.org/doc/admin24/security.html |

漏洞分析

当使用了未经身份验证绑定的 LDAP 服务器时,允许远程攻击者通过空用户名或空密码绕过身份验,具体分析复现可见su18佬文章。

漏洞修复

Shiro 在 f988846 中针对此漏洞进行了修复,实际上,整个 1.2.3 版本的更新就是针对这个漏洞。

官方在 DefaultLdapContextFactoryJndiLdapContextFactory 中均加入了 validateAuthenticationInfo 方法用来校验 principal 和 credential 为空的情况。可以看到这里的逻辑是只有 principal 不为空的情况下,才会对 credential 进行校验。

并在 getLdapContext 方法创建 InitialLdapContext 前执行了校验,如果为空,将会抛出异常。

免责声明

免责声明:本博客的内容仅供合法、正当、健康的用途,切勿将其用于违反法律法规的行为。如因此导致任何法律责任或纠纷,本博客概不负责。谢谢您的理解与配合!

本文链接:

https://sanshiok.com/archive/28.html

# 最新文章