Перейти к основному содержимому

Spring Security — исключение, отклоненное запросом

· 7 мин. чтения

Задача: Наибольшая подстрока палиндром

Для заданной строки s, верните наибольшую подстроку палиндром входящую в s. Подстрока — это непрерывная непустая последовательность символов внутри строки. Стока является палиндромом, если она читается одинаково в обоих направлениях...

ANDROMEDA 42

1. Введение

Версии Spring Framework с 5.0 по 5.0.4, с 4.3 по 4.3.14 и другие более старые версии имели уязвимость безопасности при обходе каталога или пути в системах Windows.

Неправильная настройка статических ресурсов позволяет злоумышленникам получить доступ к файловой системе сервера. Например, обслуживание статических ресурсов с использованием протокола file: обеспечивает несанкционированный доступ к файловой системе в Windows .

Spring Framework признал уязвимость и устранил ее в более поздних выпусках.

Следовательно, это исправление защищает приложения от атак с обходом пути. Однако с этим исправлением некоторые из более ранних URL-адресов теперь вызывают исключение org.springframework.security.web.firewall.RequestRejectedException .

Наконец, в этом руководстве давайте узнаем об org.springframework.security.web.firewall.RequestRejectedException и StrictHttpFirewall в контексте атак обхода пути .

2. Уязвимости обхода пути

Уязвимость обхода пути или обхода каталога делает возможным несанкционированный доступ за пределы корневого каталога веб-документа. Например, изменение URL-адреса может привести к несанкционированному доступу к файлам за пределами корня документа.

Хотя самые последние и популярные веб-серверы компенсируют большинство этих атак, злоумышленники все еще могут использовать URL-кодирование специальных символов, таких как «./», «../», чтобы обойти защиту веб-сервера и получить незаконный доступ.

Кроме того, OWASP обсуждает уязвимости Path Traversal и способы их устранения.

3. Уязвимость Spring Framework

Теперь давайте попробуем воспроизвести эту уязвимость, прежде чем мы узнаем, как ее исправить.

Во-первых, давайте клонируем примеры Spring Framework MVC. Позже давайте изменим pom.xml и заменим существующую версию Spring Framework уязвимой версией. **

**

Клонируем репозиторий:

git clone git@github.com:spring-projects/spring-mvc-showcase.git

Внутри клонированного каталога отредактируйте pom.xml , включив 5.0.0.RELEASE в качестве версии Spring Framework:

<org.springframework-version>5.0.0.RELEASE</org.springframework-version>

Затем отредактируйте класс веб-конфигурации WebMvcConfig и измените метод addResourceHandlers , чтобы сопоставить ресурсы с локальным файловым каталогом с помощью файла:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/resources/**")
.addResourceLocations("file:./src/", "/resources/");
}

Позже соберите артефакт и запустите наше веб-приложение:

mvn jetty:run

Теперь, когда сервер запустится, вызовите URL:

curl 'http://localhost:8080/spring-mvc-showcase/resources/%255c%255c%252e%252e%255c/%252e%252e%255c/%252e%252e%255c/%252e%252e%255c/%252e%252e%255c/windows/system.ini'

%252e%252e%255c — это форма ..\ с двойным кодированием, а %255c%255c — форма \\ с двойным кодированием . ``

Небезопасно, что ответом будет содержимое системного файла Windows system.ini.

4. Интерфейс Spring Security HttpFirewall

Спецификация сервлета точно не определяет различие между servletPath и pathInfo. Следовательно, существует несоответствие между контейнерами сервлетов при переводе этих значений.

Например, в Tomcat 9 для URL-адреса http://localhost:8080/api/v1/users/1 URI /1 должен быть переменной пути.

С другой стороны, следующее возвращает /api/v1/users/1 :

request.getServletPath()

Однако приведенная ниже команда возвращает null :

request.getPathInfo()

Неспособность отличить переменные пути от URI может привести к потенциальным атакам, таким как атаки Path Traversal / Directory Traversal. Например, пользователь может использовать системные файлы на сервере, включив файлы \\, /../, . .\ в URL. К сожалению, только некоторые контейнеры сервлетов нормализуют эти URL-адреса.

Весенняя безопасность спешит на помощь. Spring Security последовательно ведет себя во всех контейнерах и нормализует такие вредоносные URL-адреса, используя интерфейс HttpFirewall . Этот интерфейс имеет две реализации:

4.1. Брандмауэр по умолчаниюHttp

Во-первых, не будем путаться в названии класса реализации. Другими словами, это не реализация HttpFirewall по умолчанию .

Брандмауэр пытается очистить или нормализовать URL-адреса и стандартизирует servletPath и pathInfo для контейнеров. Кроме того, мы можем переопределить поведение HttpFirewall по умолчанию, явно объявив @Bean :

@Bean
public HttpFirewall getHttpFirewall() {
return new DefaultHttpFirewall();
}

Однако StrictHttpFirewall обеспечивает надежную и безопасную реализацию и является рекомендуемой реализацией.

4.2. Строгий HTTP-брандмауэр

StrictHttpFirewall — это стандартная и более строгая реализация HttpFirewall. Напротив, в отличие от DefaultHttpFirewall , StrictHttpFirewall отклоняет любые ненормализованные URL-адреса, обеспечивая более строгую защиту. Кроме того, эта реализация защищает приложение от нескольких других атак, таких как межсайтовая трассировка (XST) и подделка глаголов HTTP . ``

Более того, эта реализация настраиваема и имеет разумные значения по умолчанию. Другими словами, мы можем отключить (не рекомендуется) несколько функций, таких как разрешение точек с запятой как части URI:

@Bean
public HttpFirewall getHttpFirewall() {
StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
strictHttpFirewall.setAllowSemicolon(true);
return strictHttpFirewall;
}

Короче говоря, StrictHttpFirewall отклоняет подозрительные запросы с помощью org.springframework.security.web.firewall.RequestRejectedException .

Наконец, давайте разработаем приложение управления пользователями с операциями CRUD для пользователей с использованием Spring REST и Spring Security и посмотрим на StrictHttpFirewall в действии. **

**

5. Зависимости

Давайте объявим зависимости Spring Security и Spring Web :

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.4</version>
</dependency>

6. Конфигурация безопасности Spring

Далее давайте защитим наше приложение с помощью базовой аутентификации, создав класс конфигурации, который расширяет WebSecurityConfigurerAdapter :

@Configuration
public class SpringSecurityHttpFirewallConfiguration extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/error").permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
}

По умолчанию Spring Security предоставляет пароль по умолчанию, который меняется при каждом перезапуске. Следовательно, давайте создадим имя пользователя и пароль по умолчанию в application.properties :

spring.security.user.name=user
spring.security.user.password=password

Отныне мы будем получать доступ к нашим защищенным API REST, используя эти учетные данные.

7. Создание защищенного REST API

Теперь давайте создадим наш REST API управления пользователями:

@PostMapping
public ResponseEntity<Response> createUser(@RequestBody User user) {
userService.saveUser(user);
Response response = new Response()
.withTimestamp(System.currentTimeMillis())
.withCode(HttpStatus.CREATED.value())
.withMessage("User created successfully");
URI location = URI.create("/users/" + user.getId());
return ResponseEntity.created(location).body(response);
}

@DeleteMapping("/{userId}")
public ResponseEntity<Response> deleteUser(@PathVariable("userId") String userId) {
userService.deleteUser(userId);
return ResponseEntity.ok(new Response(200,
"The user has been deleted successfully", System.currentTimeMillis()));
}

Теперь давайте создадим и запустим приложение:

mvn spring-boot:run

8. Тестирование API

Теперь давайте начнем с создания пользователя с помощью cURL :

curl -i --user user:password -d @request.json -H "Content-Type: application/json" 
-H "Accept: application/json" http://localhost:8080/api/v1/users

Вот запрос.json :

{
"id":"1",
"username":"navuluri",
"email":"bhaskara.navuluri@mail.com"
}

Следовательно, ответ:

HTTP/1.1 201
Location: /users/1
Content-Type: application/json
{
"code":201,
"message":"User created successfully",
"timestamp":1632808055618
}

Теперь давайте настроим наш StrictHttpFirewall для отклонения запросов от всех HTTP-методов:

@Bean
public HttpFirewall configureFirewall() {
StrictHttpFirewall strictHttpFirewall = new StrictHttpFirewall();
strictHttpFirewall
.setAllowedHttpMethods(Collections.emptyList());
return strictHttpFirewall;
}

Далее снова вызовем API. Поскольку мы настроили StrictHttpFirewall для ограничения всех HTTP-методов, на этот раз мы получаем ошибку.

В журналах у нас есть это исключение:

org.springframework.security.web.firewall.RequestRejectedException: 
The request was rejected because the HTTP method "POST" was not included
within the list of allowed HTTP methods []

Начиная с Spring Security v5.4 , мы можем использовать RequestRejectedHandler для настройки состояния HTTP при возникновении RequestRejectedException :

@Bean
public RequestRejectedHandler requestRejectedHandler() {
return new HttpStatusRequestRejectedHandler();
}

Обратите внимание, что код состояния HTTP по умолчанию при использовании HttpStatusRequestRejectedHandler равен 400. Однако мы можем настроить его, передав код состояния в конструкторе класса HttpStatusRequestRejectedHandler .

Теперь давайте перенастроим StrictHttpFirewall , чтобы разрешить \\ в методах URL и HTTP GET , POST , DELETE и OPTIONS :

strictHttpFirewall.setAllowBackSlash(true);
strictHttpFirewall.setAllowedHttpMethods(Arrays.asList("GET","POST","DELETE", "OPTIONS")

Затем вызовите API:

curl -i --user user:password -d @request.json -H "Content-Type: application/json" 
-H "Accept: application/json" http://localhost:8080/api<strong>\\</strong>v1/users

И вот у нас есть ответ:

{
"code":201,
"message":"User created successfully",
"timestamp":1632812660569
}

Наконец, давайте вернемся к исходной строгой функциональности StrictHttpFirewall , удалив объявление @Bean .

Далее попробуем вызвать наш API с подозрительными URL-адресами:

curl -i --user user:password -d @request.json -H "Content-Type: application/json" 
-H "Accept: application/json" http://localhost:8080/api/v1<strong>//</strong>users
curl -i --user user:password -d @request.json -H "Content-Type: application/json" 
-H "Accept: application/json" http://localhost:8080/api/v1<strong>\\</strong>users

Сразу же все вышеперечисленные запросы завершаются с ошибкой:

org.springframework.security.web.firewall.RequestRejectedException: 
The request was rejected because the URL contained a potentially malicious String "//"

9. Заключение

В этой статье объясняется защита Spring Security от вредоносных URL-адресов, которые могут вызывать атаки Path Traversal/Directory Traversal.

DefaultHttpFirewall пытается нормализовать вредоносные URL-адреса. Однако StrictHttpFirewall отклоняет запросы с RequestRejectedException . Наряду с атаками Path Traversal, StrictHttpFirewall защищает нас от нескольких других атак. Следовательно, настоятельно рекомендуется использовать StrictHttpFirewall вместе с его конфигурациями по умолчанию. **

**

Как всегда, полный исходный код доступен на Github .