Sploitus

Exploit for shiro

kitploit · 2026-09-04

Exploit Code

MARKDOWN172 lines
## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-JWENY-SHIRO-CVE-2020-17523
# Apache Shiro два способа обхода аутентификации (CVE-2020-17523)

## 0x01 Описание уязвимости

Apache Shiro — это мощная и простая в использовании платформа безопасности Java, выполняющая аутентификацию, авторизацию, управление паролями и сессиями. Благодаря понятному API Shiro вы можете быстро и легко создавать любые приложения — от самых маленьких мобильных до крупнейших корпоративных.

При совместном использовании с Spring и определённых правилах сопоставления прав доступа злоумышленник может обойти аутентификацию, создав специальный HTTP-запрос.

Затронутые версии: Apache Shiro < 1.7.1

## 0x02 Настройка среды для уязвимости

shiro 1.7.0

https://github.com/jweny/shiro-cve-2020-17523 Среда для обоих методов уже обновлена.

## 0x03 Тестирование PoC

**Метод 1:**

http://127.0.0.1:8080/admin/%20 или http://127.0.0.1:8080/admin/%20/

Использование пробелов и других пустых символов позволяет обойти аутентификацию Shiro.

![image-20210205120522547](https://assets.kitploit.com/production/public/readmes/27762/311475c836078400db76ad3276634313ecb94472008ebb81532d069d28c38045.png)

**Метод 2:**

После общения с коллегой p0desta был обнаружен ещё один метод использования в особых сценариях.

http://127.0.0.1:8080/admin/%2e или http://127.0.0.1:8080/admin/%2e/

Однако `.` (а также `/`) в правилах сопоставления путей Spring являются разделителями путей и не сопоставляются как обычные символы. Поэтому по умолчанию доступ к `/admin/.` возвращает 404.

Но если включён полный путь (`setAlwaysUseFullPath(true)`), сопоставление работает корректно.

![image-20210205102100797](https://assets.kitploit.com/production/public/readmes/27762/342713c29e6d8130ae6c0754a98db6a05c7aaa559890f277026e2e18f3d981b4.png)

## 0x04 Анализ уязвимости

В Shiro получение и сопоставление URL происходит в `org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain`.

Кратко рассмотрим метод `getChain`:

![carbon \(2\)](https://assets.kitploit.com/production/public/readmes/27762/ee3896af70b48af6f295318700209acb66e9e7eba0b1d2f6d2fc8d3aa1d99dc2.png)

![image-20210205103341569](https://assets.kitploit.com/production/public/readmes/27762/77d66f994129e771a3b0e7ae9a20d83398a9d30363c062842354d155ab843034.png)

Этот метод сначала проверяет, заканчивается ли requestURI на `/`, и если да, удаляет последний `/`.

Затем в цикле сопоставления путей сначала проверяется, заканчивается ли правило пути pathPattern на `/`, и если да, он также удаляется. После этого вызывается метод `pathMatches()` для сопоставления пути.

**Поэтому для обоих методов не имеет значения, заканчивается ли путь на`/`, так как после прохождения `getChain` он будет удалён.**

### 4.1 Анализ обхода с пробелом

Обратим внимание на метод `pathMatches()`:

Вызовем Evaluate и вычислим `pathMatches("/admin/*","/admin/1")` и `pathMatches("/admin/*","/admin/ ")`. Первый соответствует нормально, второй — нет.

![image-20210203134044268](https://assets.kitploit.com/production/public/readmes/27762/f525565f2e5ee3804954f3a9944533b91ff6e23a6e4e1ff8e1b7c6fe343d7fc4.png)

![image-20210203134119174](https://assets.kitploit.com/production/public/readmes/27762/11fb37c90ba8988f9e9f1d532386300123a3cdedf6ba28def7f7281997dd7ae0.png)

Начинаем отладку. После долгого F7 доходим до `doMatch("/admin/*","/admin/ ")`. Видно, что `tokenizeToStringArray` вернул pathDirs уже без второго уровня пути. Это приводит к несоответствию `/admin/*` и `/admin`.

![image-20210203150854085](https://assets.kitploit.com/production/public/readmes/27762/e6f58d0420dbab4b24ed2ae10bd78899ac79b22956dbb39390dbef6cdf8bd8d5.png)

Следуем за методом `tokenizeToStringArray`. Обнаруживаем, что при вызове `tokenizeToStringArray` параметр `trimTokens` равен true.

![image-20210203150959413](https://assets.kitploit.com/production/public/readmes/27762/78cfe84cc975c811e3463eb8efa6478083a7864f41f0a6b227056ba3bea58961.png)

В методе `tokenizeToStringArray` при значении `trimTokens` true выполняется обработка `trim()`, что удаляет пробелы. После возврата в `getChain` последний `/` удаляется. Поэтому pathDirs, возвращённые `tokenizeToStringArray`, не содержат второго уровня пути.

![image-20210203151053344](https://assets.kitploit.com/production/public/readmes/27762/21eb1fa2992d29be243b4252c7f3b6023b365ab2395de41dab13bc3c0a797cb8.png)

Резюме: в уязвимой версии Shiro при вызове `tokenizeToStringArray` по умолчанию `trimTokens` равен true, и пробелы удаляются через `trim()`. После возврата в `getChain` последний `/` удаляется, поэтому `/admin` не соответствует `/admin/*`, что приводит к обходу аутентификации. В то же время Spring получает путь доступа `/admin/%20` и обрабатывает его как обычно, что позволяет обойти авторизацию.

### 4.2 Анализ обхода с /./

Глядя на второй метод с `/.` и `/./`, вспоминаете ли вы знакомый метод? Да, это `normalize()`.

![carbon \(3\)](https://assets.kitploit.com/production/public/readmes/27762/fd15cc48370ea932e2b09717e3392e7cabe8824eea026ac1f2b4f4bb52282b55.png)

Кратко переведём:

Условие| Пример  
---|---  
Обработка обратной косой черты в прямую| \ -> /  
Двойной слеш обрабатывается как один  
  
Таким образом, `/admin/.` сначала обрабатывается в `/admin/./`, затем становится `/admin/`.

![image-20210205113301788](https://assets.kitploit.com/production/public/readmes/27762/8c62eab37ca96ac938504765ec2a4e7d285184def5ac697055126d9c33983977.png)

После обработки в `org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain` последний `/` удаляется, и получается `/admin`. `/admin` не соответствует `/admin/*`, поэтому аутентификация Shiro обходится.

![image-20210205113518970](https://assets.kitploit.com/production/public/readmes/27762/c360564f613bcd3528fc6db6ac773028b628c7ecff6ea39b388884d05dc70c40.png)

В то же время Spring получает запрос `/admin/.`. **Если полный путь не включён, в Spring`.` и `/` являются разделителями путей и не участвуют в сопоставлении.** Поэтому маппинг не находится и возвращается 404.

![image-20210205114350972](https://assets.kitploit.com/production/public/readmes/27762/9d005eac9e590e79dcb9ec69d0482e98a83cf5dab87fa3e4884387403f44cb52.png)

Если включён полный путь, Spring сопоставляет весь URL и возвращает 200.

Здесь приведён код для включения полного пути:

root@kitploit:~
    
    
    @SpringBootApplication
    public class SpringbootShiroApplication extends SpringBootServletInitializer implements BeanPostProcessor {
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
            return builder.sources(SpringbootShiroApplication.class);
        }
    
        public static void main(String[] args) {
    
            SpringApplication.run(SpringbootShiroApplication.class, args);
        }
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName)
                throws BeansException {
            if (bean instanceof RequestMappingHandlerMapping) {
                ((RequestMappingHandlerMapping) bean).setAlwaysUseFullPath(true);
            }
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName)
                throws BeansException {
            return bean;
        }
    }
    

## 0x05 Официальное исправление

На основе анализа можно выделить две причины обхода авторизации в Shiro:

  1. Функция `tokenizeToStringArray` некорректно обрабатывает пробелы.
  2. Логика удаления последнего `/` не должна выполняться до цикла сопоставления путей.



Официальное исправление:

https://github.com/apache/shiro/commit/0842c27fa72d0da5de0c5723a66d402fe20903df

  1. Установить параметр `trimTokens` в значение false в `tokenizeToStringArray`. ![image-20210203154342100](https://assets.kitploit.com/production/public/readmes/27762/a883cd43c8934b6810fa041ff974710e6df4c4d797099b7726f6bbf81bc114e2.png)
  2. Изменить порядок удаления последнего `/`. Сначала сопоставлять исходный путь, и только если сопоставление не удалось, удалять последний `/`. ![image-20210205115522098](https://assets.kitploit.com/production/public/readmes/27762/b610d66ea4794159157f5496f13da790d45969b32569f9694e0d8b425b61c6c1.png)



## 0x06 О trim

Теоретически `trim()` удаляет все пробельные символы в начале и конце строки; пробел — лишь один из них. Однако в тестах другие пробельные символы, такие как `%08`, `%09`, `%0a`, при обработке Spring+Tomcat возвращают 400.

Таким образом, для первого метода, кроме пробела, других полезных payload пока не обнаружено.

## 0x07 Ссылки

https://github.com/apache/shiro/commit/0842c27fa72d0da5de0c5723a66d402fe20903df

https://www.anquanke.com/post/id/216096

https://www.cnblogs.com/syp172654682/p/9257282.html