
Analisi di due metodi di bypass per la vulnerabilità shiro-cve-2020-17523 e relativo ambiente di vulnerabilità.
Apache Shiro è un framework di sicurezza Java potente e facile da usare, che si occupa di autenticazione, autorizzazione, password e gestione delle sessioni. Con l'API di facile comprensione di Shiro, puoi mettere in sicurezza rapidamente e facilmente qualsiasi applicazione, dalla più piccola applicazione mobile alla più grande applicazione web e aziendale.
Quando viene utilizzato insieme a Spring, in presenza di determinate regole di corrispondenza dei permessi, un attaccante può bypassare l'autenticazione costruendo pacchetti di richiesta HTTP speciali.
Versioni interessate: Apache Shiro < 1.7.1
shiro 1.7.0
https://github.com/jweny/shiro-cve-2020-17523 Gli ambienti vulnerabili per entrambe le tecniche sono già stati aggiornati.
Tecnica 1:
http://127.0.0.1:8080/admin/%20 o http://127.0.0.1:8080/admin/%20/
Utilizzando caratteri vuoti come lo spazio, è possibile bypassare l'autenticazione di Shiro.

Tecnica 2:
Dopo averne discusso con il ricercatore p0desta, si è scoperto che esiste un'altra modalità di sfruttamento, valida in uno scenario speciale.
http://127.0.0.1:8080/admin/%2e o http://127.0.0.1:8080/admin/%2e/
Tuttavia, nelle regole di corrispondenza dei percorsi di Spring, . (e anche /) rappresentano i separatori di percorso e non vengono trattati come caratteri normali. Pertanto, in condizioni predefinite, l'accesso a /admin/. restituisce 404.
Invece, nello scenario in cui è abilitata la corrispondenza dell'intero percorso, setAlwaysUseFullPath(true), la corrispondenza funziona correttamente.

In Shiro, l'acquisizione e la corrispondenza dell'URL avvengono in org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain
Diamo prima una rapida occhiata a questo metodo getChain:


Questo metodo controlla prima se requestURI termina con /; se sì, elimina l'ultimo /.
Successivamente, nel ciclo di corrispondenza dei percorsi, controlla se la regola di percorso pathPattern termina con /; se sì, la elimina anch'essa. Poi chiama il metodo pathMatches() per eseguire la corrispondenza dei percorsi.
Pertanto, in entrambe le tecniche di sfruttamento, non ha importanza se il percorso termina con /, perché viene eliminato non appena passa dal metodo getChain.
Concentriamoci sul metodo pathMatches():
Richiama Evaluate e calcola rispettivamente pathMatches("/admin/*","/admin/1") e pathMatches("/admin/*","/admin/ "): la prima corrisponde normalmente, la seconda fallisce.


Il debug inizia con una lunga serie di F7, fino ad arrivare a doMatch("/admin/*","/admin/ "). Come si può notare, i pathDirs restituiti da tokenizeToStringArray non contengono più il secondo livello di percorso. Questo fa sì che /admin/* e /admin non corrispondano.

Seguiamo il metodo tokenizeToStringArray e scopriamo che, quando viene richiamato, il parametro trimTokens è true.

E il metodo tokenizeToStringArray, quando il parametro trimTokens è true, applica trim(), causando l'eliminazione dello spazio. Quando si torna a getChain, l'ultimo / viene rimosso. Per questo i pathDirs restituiti da tokenizeToStringArray non hanno il secondo livello di percorso.

Riassumendo: nelle versioni di Shiro vulnerabili, poiché quando viene chiamato il metodo tokenizeToStringArray il parametro trimTokens è true di default, lo spazio viene elaborato da trim() e quindi eliminato. Quando si torna a getChain, l'ultimo / viene rimosso, quindi /admin e /admin/* non corrispondono, causando il bypass dell'autenticazione. Spring, invece, riceve il percorso di accesso /admin/%20 e, secondo la logica normale, restituisce la risposta; di conseguenza i permessi vengono bypassati.
Osservando /. e /./ della seconda tecnica, ti viene in mente un metodo familiare? Esatto: normalize().

Tradotto in parole semplici:
| Condizione | Esempio |
|---|---|
| I backslash vengono convertiti in slash | \ -> / |
| I doppi slash vengono convertiti in un singolo slash |
Quindi /admin/., dopo essere stato elaborato in /admin/./, diventa /admin/.

Dopo l'elaborazione da parte di org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain, poiché il percorso termina con /, l'ultimo / viene eliminato e il percorso diventa /admin. ``/admine/admin/*` non corrispondono, quindi l'autenticazione di Shiro viene bypassata.

Nel frattempo, la richiesta ricevuta da Spring è /admin/.. Se la corrispondenza dell'intero percorso non è abilitata, in Spring . e / fungono da separatori di percorso e non partecipano alla corrispondenza dei percorsi. Di conseguenza non viene trovato alcun mapping e viene restituito 404.

Se la corrispondenza dell'intero percorso è abilitata, viene confrontato l'intero URL, quindi Spring restituisce 200.
Ecco il codice per abilitare la corrispondenza dell'intero percorso:
@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;
}
}
Dall'analisi precedente emergono due cause del bypass dei permessi di Shiro:
tokenizeToStringArray non gestisce correttamente gli spazi./ non dovrebbe trovarsi prima della logica di corrispondenza dei percorsi nel ciclo.La correzione ufficiale è pertanto la seguente:
https://github.com/apache/shiro/commit/0842c27fa72d0da5de0c5723a66d402fe20903df
trimTokens di tokenizeToStringArray su false.
/: prima viene confrontato il percorso originale e, solo se la corrispondenza fallisce, si passa alla logica che elimina l'ultimo /.
Teoricamente trim() rimuove tutti gli whitespace all'inizio e alla fine della stringa; lo spazio è solo uno di questi. Tuttavia, nei test è emerso che, oltre allo spazio, altri whitespace come %08, %09, %0a, quando vengono elaborati da spring+tomcat, restituiscono tutti 400.
Pertanto, per la prima tecnica non è stato ancora trovato nessun altro payload utile oltre allo spazio.
https://github.com/apache/shiro/commit/0842c27fa72d0da5de0c5723a66d402fe20903df
| // -> / |
| Se termina con /. o /.., aggiunge / alla fine | /. -> /./ /.. -> /../ |
| Normalizzazione di /./ | /./ -> / |
| Salto di percorso | /aaa/../bbb -> /bbb |