0x00 什么是shiro
Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。使用Shiro的易於理解的API,您可以快速、輕松地獲得任何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。
0x01 環境搭建
- springboot
- shiro1.5.3
1、去https://start.spring.io/使用springboot腳手架搭建一個java8的springboot項目
2、用IDEA打開該項目,目錄結構與文件如下:

3、創建MyRealm、NameController、ShiroConfig三個class文件。
4、pom.xml增加對應的dependency
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
</project>
ShiroConfig.java代碼如下:
package com.example.demo;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
MyRealm myRealm(){
return new MyRealm();
}
@Bean
SecurityManager securityManager(){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm());
return manager;
}
@Bean
ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager());
bean.setLoginUrl("/login");
bean.setSuccessUrl("/index");
bean.setUnauthorizedUrl("/unauthorizedUrl");
Map<String,String> map= new LinkedHashMap<>();
map.put("/doLogin/","anon");
map.put("/test/*","authc");
bean.setFilterChainDefinitionMap(map);
return bean;
}
}
NameController代碼如下:
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NameController {
@GetMapping("/test/{name}")
public String test(@PathVariable String name){
return "s1111";
}
@GetMapping("/test/")
public String test2(){
return "test2";
}
}
MyRealm代碼如下:
package com.example.demo;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class MyRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}
0x02 poc測試


原本需要認證的test/{name}可以訪問到了

證明漏洞利用成功。
0x03 漏洞原理分析
根據shiro歷史上的認證繞過漏洞,本質問題就是springboot對url的處理和shiro的處理不一致導致的認證繞過。
其中shiro的url處理的問題都出在org/apache/shiro/web/util/WebUtils.java類下面,在return
public static String getPathWithinApplication(HttpServletRequest request) {
return normalize(removeSemicolon(getServletPath(request) + getPathInfo(request)));
}
斷點,
然后我們去springboot的處理url的地方進行斷點,org/springframework/web/util/UrlPathHelper.java。
在return uri;進行
private String decodeAndCleanUriString(HttpServletRequest request, String uri) {
uri = removeSemicolonContent(uri);
uri = decodeRequestString(request, uri);
uri = getSanitizedPath(uri);
return uri;
}
斷點。
訪問/test/%3bname,先來到shiro的url處理塊,

根據圖紙可知,獲取到的url在處理前已經進行了一次urldecode。然后在進入removeSemicolon操作,最后結果集在交給normalize操作。
根據計算器可知removeSemicolon會把url里;后面的內容給刪除(包括;)

跟進removeSemicolon看看。
實現代碼如下:
private static String removeSemicolon(String uri) {
int semicolonIndex = uri.indexOf(';');
return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
}
確實是把;后面的內容給刪除(包括;)了。
一路F8,最后果然把/test/賦值給requestURI變量了。

根據測試當初訪問/test/

可以看到,如果test目錄的話,是默認有權限訪問的,但是/test/后面的路由是需要驗證的。

接着我們來到springboot,看看springboot是怎么處理URL的問題。

uri取到的是/test/;name,可以看到springboot對url做了三個操作后才返回的,removeSemicolonContent,decodeRequestString,getSanitizedPath。
- removeSemicolonContent 是把(url未解碼前的uri里的;后面的內容給刪除)
- decodeRequestString把uri進行urldecode編碼
- getSanitizedPath 是把"//" 替換成 "/"
這是簡單對比下shiro的對url的操作順序:
- uri 進行urldecode
- uri 刪除;后面的內容,包括;
因為shiro的處理和springboot的處理順序不同,導致我們構造的poc在shiro側理解的是訪問的/test/,/test/我們本身就沒有限制權限,放過了這個原本需要認證權限的請求,而springboot側則是訪問的是/test/;name,然后springboot把;name當做一個字符串去尋找對應的路由,返回了對應的字符串。
0x04 總結
此漏洞主要是兩個組件之間對某個關鍵信息處理的邏輯未進行統一約定,可以理解成前后端對同一個信息處理的不同步導致的安全漏洞。
同樣的認證繞過在shiro歷史上出現過好幾次,具體可以去參考鏈接學習。
