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.Authenticato
r 接口,提供了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 版本的更新就是针对这个漏洞。
官方在 DefaultLdapContextFactory
和 JndiLdapContextFactory
中均加入了 validateAuthenticationInfo
方法用来校验 principal 和 credential 为空的情况。可以看到这里的逻辑是只有 principal 不为空的情况下,才会对 credential 进行校验。
并在 getLdapContext
方法创建 InitialLdapContext
前执行了校验,如果为空,将会抛出异常。
免责声明
免责声明:本博客的内容仅供合法、正当、健康的用途,切勿将其用于违反法律法规的行为。如因此导致任何法律责任或纠纷,本博客概不负责。谢谢您的理解与配合!