В этой статье мы расскажем про категории OWASP Top Ten 2021 через призму срабатываний Java анализатора PVS-Studio. Так что, если у вас есть желание посмотреть на возможные паттерны уязвимостей в Java коде или узнать, что из себя представляют категории OWASP Top Ten, приятного чтения!

Введение

Мы — компания PVS-Studio, разрабатывающая одноимённый статический анализатор уже практически 18 лет. Работаем с языками C/C++, C# и Java. С недавнего времени активно занялись тем, чтобы Java анализатор мог называться SAST-решением. Ведь Java — один из популярнейших языков, на котором разрабатывают web-приложения.

SAST?

SAST (Static Application Security Testing) — это методология тестирования разрабатываемого приложения, которая основана на поиске потенциальных уязвимостей посредством анализа его исходного кода. Т.е. статический анализ, ориентированный на поиск ошибок безопасности. Это помогает предотвращать определённые проблемы безопасности ПО ещё до его выхода в релиз. Подробнее можете ознакомиться здесь.

И нас часто спрашивают: "Как вы выбираете диагностики, которые добавляете в анализатор? Чем руководствуетесь?" В контексте SAST-направления в Java для нас одним из главных ориентиров является OWASP Top Ten.

OWASP Top Ten — это проект организации OWASP, представляющий собой рейтинг сгруппированных web-уязвимостей. Формируется он раз в несколько лет на основе отчёта специалистов и консультантов по информационной безопасности, bug bounty компаний и самих разработчиков web-продуктов. В этой статье мы рассмотрим список, сформированный к 2021 году. Однако здесь хочу отметить, что в ближайшее время должен выйти OWASP Top Ten 2025. В нашем блоге обязательно будет обзор, так что подписывайтесь и следите.

А насколько SAST и OWASP Top Ten важны и актуальны? С каждым годом рост обнаруженных уязвимостей только растёт. Это можно наблюдать по количеству реальных зарегистрированных уязвимостей согласно статистике от CVE.

CVE?

CVE (Common Vulnerabilities and Exposures) — база данных общеизвестных уязвимостей информационной безопасности. Подробнее ознакомиться можно здесь.

Здесь я приведу краткую, но показательную выжимку их статистики по найденным уязвимостям с 2014 по 2024 годы:

2014

2015

2016

2017

2018

2019

2020

2021

2022

2023

2024

7948

6494

6457

14645

16512

17308

18375

20161

25059

28961

40077

С полной статистикой также можете ознакомиться по ссылке.

Вы можете видеть, что количество обнаруживаемых уязвимостей только растёт. И в их превентивном обнаружении может помочь максимально полное и всестороннее тестирование. Ведь у каждого метода тестирования своя зона ответственности. Если использовать не все, определённая часть проблем уйдёт в тень.

Именно всестороннее тестирование помогает придерживаться shift-left принципа. Если проблема была найдена во время статического или динамического анализа, а не во время ручного тестирования или уже в продакшне, затраты на её исправление будут гораздо меньше. В связи с этим потребность в SAST-инструментах может только расти.

Практически весь набор необходимых механизмов у нас был. Чтобы иметь возможность покрыть необходимые ситуации, Java команда активно работает над его улучшением.

Примечание. Ранее мы уже рассказывали о том, что делали для улучшения нашего SAST инструмента. Среди этого, например, технология анализа помеченных данных (статья N1, статья N2), пользовательское аннотирование кода, а также различные улучшения технологии анализа потока данных.

И по итогу наш анализатор мог покрыть 9/10 категорий OWASP Top Ten.

Категории OWASP Top Ten 2021

Опираться мы будем на позиции OWASP Top Ten по порядку. Мы расскажем вам про саму категорию и после покажем один или несколько примеров уязвимого кода, на который PVS-Studio выдаёт сообщение, чтобы было понятнее, как это может выглядеть на практике.

A01:2021 — Broken Access Control

Broken Access Control — категория, которая объединяет в себе уязвимости, приводящие к несанкционированному раскрытию информации, изменению или уничтожению всех данных или выполнению бизнес-функций за пределами полномочий пользователя.

Первый пример:

public class RedirectServlet extends HttpServlet {
  
  @Override
  protected void doGet(HttpServletRequest request, 
                       HttpServletResponse response) throws IOException {
    String query = request.getQueryString();
    if (query.contains("url")) {
      String url = request.getParameter("url");
      response.sendRedirect(url);               // <=
    }
  }
}

Срабатывание PVS-Studio: V5324 Possible open redirect vulnerability. Potentially tainted data in the 'url' variable is used in the URL. RedirectServlet.java 15, 14

Сервер принимает GET запрос, берёт из него переданный URL-адрес и без проверки перенаправляет по нему пользователя. Проблема в том, что злоумышленнику ничего не мешает создать фишинговую копию основного сайта и сделать скомпрометированную ссылку, например:

https://notevil.com/redirect?url=https://evil.com/registration

В этом случае наш сайт перенаправит пользователя на форму регистрации на сайте злоумышленника. И пользователь, думая, что находится на доверенном сайте, может ввести свои конфиденциальные данные. А они в свою очередь направятся прямиком злоумышленнику.

Чтобы этого не допустить, серверу перед перенаправлением стоит проверять ссылку. И если она не в числе доверенных — перенаправление должно быть запрещено:

private static final List<String> ALLOWED_DOMAINS = List.of(
  "https://allowed-host1.com",
  "https://allowed-host2.com",
  "https://allowed-host3.com"
);

public class RedirectServlet extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, 
                       HttpServletResponse response) throws IOException {
    String query = request.getQueryString();
    if (query.contains("url")) {
      String url = request.getParameter("url");
      if (ALLOWED_DOMAINS.contains(url)) {      // <=
        response.sendRedirect(url);              
      } else {
        //....
      }
    }
  }
}

Это лишь один вариант защиты. C более обширным списком вариантов защиты от этой проблемы, вы можете ознакомиться в специальной рубрике Cheat Sheet в блоге OWASP. В ней собраны рекомендации по предотвращению различных уязвимостей и проблем безопасности.

Рассмотрим пример другой ситуации:

private final Path uploadDir = Path.of("/var/www/uploads");

@Override
public void init() throws ServletException {
  try {
    Files.createDirectories(uploadDir);
    Files.setPosixFilePermissions(
            uploadDir,
            PosixFilePermissions.fromString("rwxrwxrwx"));  // <=
  } catch (IOException e) {
      throw new ServletException(e);
  }
}

@Override
protected void doPost(HttpServletRequest req,
                      HttpServletResponse resp) throws .... {
  Part filePart = req.getPart("file");
  Path target = uploadDir.resolve(Paths.get(filePart.getSubmittedFileName()));
  try (InputStream in = filePart.getInputStream()) {
    Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
  }
  resp.getWriter().println("uploaded to " + target);
}

Срабатывание PVS-Studio: V5318 Setting loose POSIX file permissions (rwxrwxrwx) is security-sensitive. PermissionsExample.java 13

Здесь каталогу, в который загружаются файлы, выставляются все возможные права группе OTHERS. В том числе права на выполнение файла. И если пользователь загрузит исполняемый файл (к примеру, shell.jsp), то при обращении к нему он выполнится (в случае, если мы работаем с Tomcat сервером):

https://example.com/uploads/shell.jsp

Так, небезопасная конфигурация прав доступа для специальной директории веб-приложения привела к RCE (удалённое выполнение кода). Чтобы подобных вещей не происходило, важно придерживаться принципа минимальных привилегий. А статический анализ вам поможет не проглядеть такие моменты.

A02:2021 — Cryptographic Failures

Категория Cryptographic Failures объединяет группу уязвимостей, связанных с некорректным шифрованием конфиденциальных данных: использование устаревших криптографических алгоритмов или хеш-функций, передача информации в незашифрованном виде и т.д.

Пример:

public static void callExternalApi() throws Exception {
  SSLContext sslContext = SSLContext.getInstance("TLSv1");     // <=
  sslContext.init(null, null, new java.security.SecureRandom());

  HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
  URL url = new URL("https://external-api.example.com/data");
  
  HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
  try (BufferedReader in = new BufferedReader(
          new InputStreamReader(conn.getInputStream()))
  ) {
    String inputLine;
    while ((inputLine = in.readLine()) != null)
      System.out.println(inputLine);
  }
}

Срабатывание PVS-Studio: V5313 Do not use old versions of SSL/TLS protocols as it may cause security issues. Insecure protocols: TLSv1. SSLExample.java 11

В этом примере для создания соединения используется устаревшая версия протокола SSL/TLS. Такая небезопасная конфигурация способна привести к таким атакам, как Man-In-The-Middle, её подвиду — BEAST, и т.д. Если конкретнее, то злоумышленником возможен перехват и расшифровка/изменение данных, которые передаются между двумя сторонами.

Для того, чтобы обезопасить себя, лучше воспользоваться более новой и безопасной версией этого алгоритма — "TLSv1.2" или "TLSv1.3".

Подобные проблемы могут быть наследием legacy-части проекта, и, опять же, автоматизированный поиск подобных проблем посредствам SAST-инструментов — лучший способ не допустить подобного.

A03:2021 — Injection

Injection — уязвимости, которые возникают за счёт того, что ненадёжные данные при попадании в определённые критические места (к примеру, исполнение SQL-запроса) заставляют программу вести себя не так, как планировалось изначально. Как следствие, возможна порча конфиденциальных данных, их обнародование или остановка выполнения программы.

Подобных диагностик у нас предостаточно. Покажу на примере самой типовой — SQL-инъекции. Предыдущие примеры занимали собой не более одного метода. Давайте рассмотрим создание SQL-запроса на более приближенном к реальности примере — простом MVC-контроллере. Заодно и проверим, сможет ли PVS-Studio найти проблему в примере побольше и посложнее.

DemoController.java:

@RestController
public class DemoController {
  @Autowired
  private DemoService service;

  @RequestMapping("demo")
  public ResponseEntity<DemoObject> 
            demoEndpoint(@RequestParam(name="name") String name
  ) {
    return service.findByName(name)
                  .map(ResponseEntity::ok)
                  .orElse(ResponseEntity.notFound().build());
  }
}

В контроллер приходит запрос, из которого извлекается параметр name и передаётся в DemoService.

DemoService.java:

@Service
public class DemoService {
  @Autowired
  DemoRepository demoRepository;

  Optional<DemoObject> findByName(String name) {
    return demoRepository.findByName(name);
  }
}

DemoService обращается к репозиторию DemoRepository, передавая ему пришедший name в качестве параметра.

DemoRepository.java:

@Repository
public class DemoRepository {
  @Autowired
  private JdbcTemplate jdbcTemplate;

  Optional<DemoObject> findByName(String name) {
    var sql = "SELECT * FROM demoTable WHERE name = '" + name + "'";
    if (name.equals("demoCondition")) {
      sql = "SELECT * FROM demoTable WHERE name = demoName";
      return Optional.ofNullable(
              jdbcTemplate.queryForObject(sql, DemoObject.class));
    }
    return Optional.ofNullable(
            jdbcTemplate.queryForObject(sql, DemoObject.class));
  }
}

В репозитории мы формируем SQL-запрос для базы данных.

На этот код анализатор PVS-Studio выдаёт следующее срабатывание: V5309 Possible SQL injection. Potentially tainted data in the 'sql' variable is used to create SQL command. DemoRepository.java 23, DemoController.java 16

Само формирование запроса и является главной загвоздкой всей этой конструкции. Покажу нагляднее:

var sql = "SELECT * FROM demoTable WHERE name = '" + name + "'";

Запрос формируется через конкатенацию. И если значение у name будет следующим:

' or 1==1; drop table demoTable; --

То таблица demoTable будет удалена.

По пути от контроллера до самого запроса данные никак не очистили и не санитизировали. Анализатор это обнаружил и выдал сообщение. Чтобы обезопасить своё приложение, данные перед передачей в запрос необходимо либо очищать, либо экранировать.

Рекомендуемый способ защиты в контексте SQL-инъекции — параметризированные запросы:

Optional<DemoObject> findByName(String name) {
  String sql;
  if ("demoCondition".equals(name)) {
    sql = "SELECT * FROM demoTable WHERE name = demoName";
    return Optional.ofNullable(
      jdbcTemplate.queryForObject(
        sql,
        new BeanPropertyRowMapper<>(DemoObject.class)
      )
    );
  }

  sql = "SELECT * FROM demoTable WHERE name = ?";
  return Optional.ofNullable(
    jdbcTemplate.queryForObject(
      sql,
      new BeanPropertyRowMapper<>(DemoObject.class),
      name
    )
  );
}

A04:2021 — Insecure Design

Insecure Design — достаточно широкая категория, объединяющая в себе группу уязвимостей, которые возникают вследствие недостаточно безопасного архитектурного дизайна приложения.

Одну из подобных ситуаций мы уже рассмотрели, когда я рассказывал вам про категорию A01. К этой же категории относятся выставление слишком широких прав доступа группе OTHERS и диагностика V5318, которая подобные ситуации находит. Но, поскольку этот вариант уязвимости мы уже рассмотрели, давайте взглянем на ещё один:

@Override
protected void doGet(HttpServletRequest req,
                     HttpServletResponse res) throws IOException {
  String requestedSessionId = req.getRequestedSessionId();
  if (requestedSessionId != null && requestedSessionId.startsWith("ADMIN-")) {
    // ....
  }
  //....
}

Срабатывание PVS-Studio: V5316 The use of HttpServletRequest#getRequestedSessionId is discouraged as it may expose security risks. DemoServlet.java 15

Эта ситуация является одним из возможных паттернов реализации CWE-807: здесь при принятии решения в security-контексте полагаются на недостоверные данные.

CWE?

CWE (Common Weakness Enumeration) — поддерживаемая и развиваемая сообществом система классификации недостатков безопасности.

Классификация CWE так же, как и OWASP Top Ten, является для нас ориентиром при добавлении новых диагностик. И у нас на сайте вы можете ознакомиться с тем, как наши диагностики соотносятся со CWE идентификаторами.

Метод getRequestSessionId возвращает тот ID сессии, который задал пользователь, а не тот, который на самом деле сессии принадлежит.

С точки зрения архитектуры полагаться на ввод пользователя при принятии решения небезопасно. Рекомендуется использовать актуальный ID сессии. В этом случае исправление будет достаточно простым:

request.getSession().getId();

A05:2021 — Security Misconfiguration

Security Misconfiguration — категория, которая объединяет в себе уязвимости, связанные с небезопасной конфигурацией: включение лишних портов или служб, небезопасная настройка, использование данных извне для определения системных свойств и т.д.

Пример:

public class MisconfigurationExample extends HttpServlet {
  @Override
  protected void doPost(HttpServletRequest req,
                        HttpServletResponse resp) throws IOException {
    String key = req.getParameter("key");
    String value = req.getParameter("value");

    if (key == null || value == null || key.isBlank()) {
      resp.sendError(400, "Invalid key or value");
      return;
    }

    System.setProperty(key, value);
    resp.setContentType("text/plain");
    //....
  }
}

Срабатывание PVS-Studio: V5320 Potentially tainted data in the 'value' variable is used in configuration settings. This may cause security issues. MisconfigurationExample.java 22

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

  • изменение системных свойств может привести к утечке конфиденциальной информации или её порче. Самый простой пример — контролируемое пользователем свойство, в котором хранится адрес базы данных. При изменении имени БД можно получить доступ к данным, раскрытие которых не предусматривалось;

  • некорректно сконфигурированные системные свойства могут привести к падению программы или её нестабильной работе. К примеру, если пользователю доступно свойство с максимальным количеством потоков, которое может использовать программа.

В целом, трудно представить ситуацию, где это решает больше проблем, чем создаёт. Но, если всё же необходимо использовать данные извне для формирования системных свойств, лучше использовать значения из "белого листа":

private static final List<String> ALLOWED_VALUES = List.of(
        "value1",
        "value2",
        "value3"
);

@Override
protected void doPost(HttpServletRequest req,
                      HttpServletResponse resp) throws IOException {
  //....
  if (    key == null || value == null || key.isBlank()
      || !ALLOWED_VALUES.contains(value)
  ) {
    resp.sendError(400, "Invalid key or value");
    return;
  }
  //....
}

А06:2021 — Vulnerable and Outdated Components

Выше я говорил, что у нас есть диагностики на 9 из 10 Top Ten категорий. И пока именно эта категория является той единственной, что нами не покрыта.

Vulnerable and Outdated Components — категория, которая содержит в себе лишь одну некорректную с точки зрения безопасности ситуацию: использование в проекте библиотеки или фреймворка версии, содержащей в себе обнаруженную уязвимость.

Сложность для нас состоит в том, чтобы определить, как лучше агрегировать информацию об уязвимых версиях библиотек. Как только подобная функциональность у нас появится, мы обязательно об этом расскажем. Так что обязательно следите за нашими обновлениями и новыми релизами. И в блоге информация обязательно об этом будет, так что подписывайтесь.

A07:2021 — Identification and Authentication Failures

Identification and Authentication failures — группа уязвимостей, связанных с ошибками в управлении сеансами или аутентификацией пользователей. Обычно подобные уязвимости способны привести к компрометации паролей, ключей безопасности или токенов сеанса, или позволяют злоумышленнику присвоить себе идентификационные данные.

Пример:

@Configuration
public class CorsConfig {
  @Bean
  public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
      @Override
      public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")          // <=
                .allowedMethods("*")
                .allowCredentials(true);
      }
    };
  }
}

Срабатывание PVS-Studio: V5325 Setting the value of the 'Access-Control-Allow-Origin' header to '*' is potentially insecure. CorsConfig.java 16

Здесь происходит небезопасная конфигурация CORS. Выставленное значение * заголовку Access-Control-Allow-Origin позволяет хосту абсолютно любого происхождения просматривать содержимое ответа кросс-доменных запросов, совершённых открытой в браузере страницей. Подробнее про это вы можете прочитать, перейдя по ссылке. Здесь лишь приведу возможные последствия небезопасной конфигурации:

  • раскрытие приватных данных при выходе из корпоративной сети во внешнее интернет-пространство;

  • злоумышленник может выдавать API определённого веб-приложения за свой, используя данные из браузера атакуемой жертвы (к примеру, cookies);

  • в случае, если сайт подвержен XSS, последствия атаки могут быть более серьёзными.

И раз уж затронули тему CORS, продемонстрирую ещё один небезопасный паттерн из этой категории:

public class NaiveCorsFilter implements Filter {
  @Override
  public void doFilter(ServletRequest request, 
                       ServletResponse response,
                       FilterChain chain) throws .... {

    HttpServletRequest req  = (HttpServletRequest) request;
    HttpServletResponse res = (HttpServletResponse) response;

    String origin = req.getParameter("origin");

    res.setHeader("Access-Control-Allow-Origin", 
                  origin);
    res.setHeader("Access-Control-Allow-Credentials", 
                  "true");
    res.setHeader("Access-Control-Allow-Methods", 
                  "GET,POST,PUT,DELETE");
    res.setHeader("Access-Control-Allow-Headers", 
                  "Content-Type,Authorization");

    chain.doFilter(request, response);
  }
}

Срабатывание PVS-Studio: V5323 Potentially tainted data in the 'origin' variable is used to define the 'Access-Control-Allow-Origin' header. NaiveCorsFilter.java 25, 22

Здесь CORS-заголовок формируется на основе данных, пришедших извне. Возможные негативные последствия такие же, как и в примере выше. И в обеих ситуациях лучшей практикой будет проверка на соответствие одному из разрешённых доменов:

private static final List<String> ALLOWED_HOSTS = List.of(
  "https://allowed-host1.com",
  "https://allowed-host2.com",
  "https://allowed-host3.com"
);

@Override
public void doFilter(ServletRequest request, 
                     ServletResponse response,
                     FilterChain chain) throws .... {
  HttpServletRequest req  = (HttpServletRequest) request;
  HttpServletResponse res = (HttpServletResponse) response;
  
  String origin = req.getParameter("origin");

  if (ALLOWED_HOSTS.contains(origin)) {
    res.setHeader("Access-Control-Allow-Origin", 
                  origin);
  } else {
    res.setHeader("Access-Control-Allow-Origin", 
                  ALLOWED_HOSTS.getFirst());
  }
  ....
}

A08:2021 — Software and Data Integrity Failures

Software and Data Integrity Failures — группа уязвимостей, приводящих к нарушению целостности программного обеспечения. Обновления без цифровой подписи, небезопасная десериализация, загрузка зависимостей из небезопасных репозиториев и т.д.

Пример:

public class UnsafeDeserializeServlet extends HttpServlet {
  @Override
  protected void doPost(HttpServletRequest req,
                        HttpServletResponse res) throws .... {

    try (InputStream in = req.getInputStream();
         ObjectInputStream ois = new ObjectInputStream(in)   // <=
    ) {
      Object obj = ois.readObject();       
      // ....
    } catch (ClassNotFoundException e) {
      throw new ServletException("Unknown class during deserialization", e);
    }
  }
}

Срабатывание PVS-Studio: V5333 Possible insecure deserialization vulnerability. Potentially tainted data in the 'in' variable is used to deserialize an object. UnsafeDeserializeServlet.java 19, 18

Здесь продемонстрирован пример небезопасной десериализации. Поток байтов берётся из запроса, и на основе их объект восстанавливается. Как вы можете видеть, никаких проверок не происходит. Ни что это за объект, ни разрешён ли он для десериализации. Это открывает пространство для RCE-уязвимости.

Десериализация данных пользователя через нативные Java-механизмы не лучшая практика. Тем не менее, если это всё же необходимо, один из вариантов обезопасить себя — проверить, объект какого класса нам приходит и есть ли он в списке тех, что допустимо десериализовывать:

class AllowedClass1 { }
class AllowedClass2 { }

class SecureObjectInputStream extends ObjectInputStream {
  List<String> allowedClasses = List.of(
    AllowedClass1.class.getName(),
    AllowedClass2.class.getName()
  );
  
  public SecureObjectInputStream(InputStream in) throws .... {
    super(in);
  }
  
  @Override
  protected Class<?> resolveClass(ObjectStreamClass osc) throws .... {
    if (!allowedClasses.contains(osc.getName())) {
      throw new InvalidClassException("Unauthorized deserialization", 
                                      osc.getName());
    }
    return super.resolveClass(osc);
  }
}

public class DeserializeServlet extends HttpServlet {
  @Override
  protected void doPost(HttpServletRequest req,
                        HttpServletResponse res) .... {
    try (InputStream in = req.getInputStream();
         ObjectInputStream ois = new SecureObjectInputStream(in)
    ) {
      Object obj = ois.readObject();
      // ....
    } catch (ClassNotFoundException e) {
      throw new ServletException("Unknown class during deserialization", e);
    }
  }
}

A09:2021 — Security Logging and Monitoring Failures

Security Logging and Monitoring Failures объединяет в себе все ситуации, которые могут:

  • помешать диагностировать проблему из-за некорректной системы логирования;

  • привести к раскрытию информации из логов третьим лицам;

  • привести к инъекции через систему логирования.

Пример:

public class AccessLogFilter extends OncePerRequestFilter {
  private static final Logger 
                       log = LoggerFactory.getLogger(AccessLogFilter.class);

  @Override
  protected void doFilterInternal(HttpServletRequest request,
                                  HttpServletResponse response,
                                  FilterChain filterChain) throws .... {

    String ip     = request.getRemoteAddr();
    String method = request.getMethod();
    String uri    = request.getRequestURI();
    String qs     = request.getQueryString();

    log.info("Access {} {}{}?{} from {}", 
             method, uri, "?", 
             qs,   // <= 
             ip);

    filterChain.doFilter(request, response);
  }
}

Срабатывание PVS-Studio: V5319 Possible log injection. Potentially tainted data in the 'qs' variable is written into logs. AccessLogFilter.java 33, 29

Здесь пример, который показывает, как можно через обычные запросы засорять логи и внедрять в них ложную информацию. Сам запрос может содержать символы, которые в URL-кодировке представляют собой символ переноса строки \n.

Если изначальный URL-запрос был следующим:

/products?category=books%0AERROR:+Inventory+breach+detected

То строка qs будет равна следующему:

category=books\nERROR: Inventory breach detected

Как следствие, сообщение в логе будет выглядеть так:

INFO  Access GET /products?category=books from 10.1.1.5
ERROR: Inventory breach detected

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

Для того, чтобы не допустить подобной ситуации, необходимо чистить информацию, которая поступает извне и попадает в логи. Очистку данных можно реализовать самостоятельно, а можно воспользоваться готовыми библиотечными методами. К примеру, методами класса org.apache.commons.text.StringEscapeUtils.

A10:2021 — Server-Side Request Forgery (SSRF)

Server-Side Request Forgery (SSRF) — уязвимость, при которой сервер осуществляет подключение к удалённому ресурсу без его предварительной проверки. Цели у подобной атаки могут быть разными:

  • раскрытие информации о внутренней инфраструктуре атакуемого ресурса или организации;

  • кража конфиденциальных данных;

  • осуществление вредоносных запросов от имени атакуемого сервера.

Пример:

@RestController
public class PreviewController {
  @GetMapping("/fetch")
  public String fetchUrl(@RequestParam("url") String url) throws Exception {
    URL external = new URL(url);
    HttpURLConnection conn = (HttpURLConnection) external.openConnection();
    conn.setConnectTimeout(2000);
    conn.setReadTimeout(2000);

    BufferedReader br = new BufferedReader(new InputStreamReader(
                                               conn.getInputStream()
                                           ));
    StringBuilder sb = new StringBuilder();
    String line;

    while ((line = br.readLine()) != null) {
      sb.append(line).append('\n');
    }
    br.close();

    return sb.toString();
  }
}

Срабатывание PVS-Studio: V5334 Possible server-side request forgery. Potentially tainted data in the 'external' variable is used to access a remote resource. PreviewController.java 17, 15

Из запроса получаем URL, к которому без предварительной проверки сразу осуществляется подключение. Здесь возможно раскрытие внутренней информации, если адрес будет, к примеру, следующим: http://127.0.0.1:8080/admin.

Возможны и другие примеры, где уязвимость осуществляется с другими целями. Общим для них остаётся вариант защиты — проверка пришедшего извне адреса:

private static final List<String> ALLOWED_URLS = List.of(
  "https://allowed-domain1.com",
  "https://allowed-domain2.com"
);
@GetMapping("/fetch")
public String fetchUrl(@RequestParam("url") String url) throws Exception {
  if (!ALLOWED_URLS.contains(url)) {
    //....
    return "Invalid url";
  }
  ....
}

Итог

Мы кратко рассмотрели, что из себя представляют категории OWASP Top Ten на простых примерах. И параллельно с этим одним глазком взглянули на SAST возможности нашего Java анализатора.

Безусловно, работа над анализатором продолжается, и количество/качество диагностик улучшается с каждым релизом. Лучший вариант рассмотреть возможности нашего анализатора — попробовать самому.

Если у вас есть мысли после прочтения этой статьи, которыми вы хотите поделиться, добро пожаловать в комментарии. С радостью с вами пообщаемся.

А на этом у нас всё. Следите за нашим блогом, и до скорых встреч.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Vladislav Bogdanov. OWASP Top Ten 2021 explained with simple Java examples and SAST insights.

Комментарии (0)