flawopen.com/ssrf/Java

● CWE-918 · Высокий
Исследования · FlawOpen

Анализ уязвимости: Server-Side Request Forgery in Java

How java.net.URL and HttpClient allow internal infrastructure scanning, and how to enforce IP address boundaries in Java.

💡 Простыми словами (ELI5)

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

Ключевые понятия и термины

Web Application Security
Технический термин (Web Application Security): Core architecture component affected by CWE-918.
CWE-918
Технический термин (CWE-918): Standard Common Weakness Enumeration classification for ssrf-java.
Defense-in-Depth
Технический термин (Defense-in-Depth): Multi-layered engineering verification and runtime boundary isolation.

Пошаговый сценарий атаки

Step 1

Прием удаленного URL от пользователя

Сервис принимает внешний URL для загрузки аватарок, вебхуков или генерации документов.

Step 2

Указание адреса внутренней сети

Атакующий передает адрес сервиса метаданных облака (http://169.254.169.254/latest/meta-data/) или локальный адрес loopback.

Step 3

Неконтролируемое исходящее соединение

Сервер отправляет HTTP-запрос изнутри защищенного периметра без проверки принадлежности IP к приватным подсетям.

Step 4

Кража облачных учетных данных

Сервис метаданных доверяет запросу с локального интерфейса и отдает временные IAM-токены и секреты кластера.

Исходный код: Уязвимый vs Защищённый вариант

✕ УЯЗВИМАЯ РЕАЛИЗАЦИЯ
// VULNERABLE: Naive java.net.URL openStream()
import java.net.URL;
import java.io.InputStream;
import javax.servlet.http.*;

public class ProxyServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        String targetUrl = req.getParameter("url");
        // Attacker input: http://169.254.169.254/latest/meta-data/
        try {
            URL url = new URL(targetUrl);
            InputStream in = url.openStream();
            in.transferTo(resp.getOutputStream());
        } catch (Exception e) {
            resp.setStatus(500);
        }
    }
}
✓ БЕЗОПАСНЫЙ ИСПРАВЛЕННЫЙ ВАРИАНТ
// HARDENED: Resolve all IPs, assert non-private, and disable redirects
import java.net.*;
import java.net.http.*;
import java.time.Duration;
import javax.servlet.http.*;

public class ProxyServlet extends HttpServlet {
    private final HttpClient client = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(3))
        .followRedirects(HttpClient.Redirect.NEVER)
        .build();

    private boolean isSafeAddress(InetAddress addr) {
        return !addr.isLoopbackAddress() &&
               !addr.isSiteLocalAddress() &&
               !addr.isLinkLocalAddress() &&
               !addr.isAnyLocalAddress();
    }

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        String targetUrl = req.getParameter("url");
        try {
            URI uri = URI.create(targetUrl);
            if (!uri.getScheme().equalsIgnoreCase("http") && !uri.getScheme().equalsIgnoreCase("https")) {
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid scheme");
                return;
            }

            // 1. Resolve and validate all IP addresses
            InetAddress[] addresses = InetAddress.getAllByName(uri.getHost());
            for (InetAddress addr : addresses) {
                if (!isSafeAddress(addr)) {
                    resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Access to internal IP prohibited");
                    return;
                }
            }

            // 2. Fetch using hardened client
            HttpRequest request = HttpRequest.newBuilder(uri)
                .timeout(Duration.ofSeconds(4))
                .GET()
                .build();

            HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
            resp.getOutputStream().write(response.body());

        } catch (Exception e) {
            resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
        }
    }
}

Чек-лист по защите системы для инженеров

References