当前位置: 首页 > news >正文

【Java代码审计 | 第十一篇】SSRF漏洞成因及防范

未经许可,不得转载。

文章目录

    • SSRF
    • 漏洞成因
    • Java中发送HTTP请求的函数
      • 1、HttpURLConnection
      • 2、HttpClient(Java 11+)
      • 3、第三方库
        • Request库漏洞示例
        • OkHttpClient漏洞示例
        • HttpClients漏洞示例
    • 漏洞代码示例
    • 防范
    • 标准代码

在这里插入图片描述

SSRF

SSRF(Server-Side Request Forgery,服务器端请求伪造) 是一种安全漏洞,攻击者可以利用该漏洞诱使服务器向内部或外部的任意系统发起请求。通过SSRF,攻击者可以绕过防火墙或访问限制,访问内部资源,甚至攻击内网中的其他服务。

常见的攻击场景包括:
1、访问内网中的敏感数据。
2、扫描内网端口和服务。
3、利用服务器作为跳板攻击其他系统。
4、访问云服务元数据(如AWS的元数据服务)。

漏洞成因

SSRF漏洞通常是由于应用程序在处理用户输入的URL时,未对其进行严格的验证和过滤,导致攻击者可以构造恶意URL,使服务器发起非预期的请求。

Java中发送HTTP请求的函数

在Java中,发送HTTP请求的常见方式有以下几种。

1、HttpURLConnection

这是Java标准库中的类,用于发送HTTP请求,示例代码如下:

import java.net.HttpURLConnection;  // 导入HttpURLConnection类
import java.net.URL;  // 导入URL类
import java.io.BufferedReader;  // 导入BufferedReader类
import java.io.InputStreamReader;  // 导入InputStreamReader类

public class HttpExample {
    public static void main(String[] args) {
        try {
            // 创建URL对象并指定要访问的地址
            URL url = new URL("https://example.com");
            // 打开与目标URL的连接
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            // 设置请求方法为GET
            conn.setRequestMethod("GET");

            // 创建BufferedReader读取响应内容
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String inputLine;
            StringBuilder content = new StringBuilder();

            // 逐行读取响应并拼接
            while ((inputLine = in.readLine()) != null) {
                content.append(inputLine);
            }
            // 关闭输入流
            in.close();
            // 断开连接
            conn.disconnect();

            // 打印网页内容
            System.out.println(content.toString());
        } catch (Exception e) {
            e.printStackTrace();  // 捕获异常并打印错误信息
        }
    }
}

2、HttpClient(Java 11+)

Java 11引入的新的HTTP客户端API,功能更强大。

3、第三方库

如Apache HttpClient、OkHttp等。

Request库漏洞示例
String url = request.getParameter("url"); // 从用户输入中获取URL
return Request.Get(url).execute().returnContent().toString(); // 直接使用用户输入的URL发起请求

代码直接从用户输入中获取URL,未进行任何验证或过滤。

OkHttpClient漏洞示例
String url = request.getParameter("url"); // 从用户输入中获取URL
OkHttpClient client = new OkHttpClient();
com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build();
client.newCall(ok_http).execute();  // 使用用户输入的URL发起请求

代码直接使用用户输入的URL构造请求,未对URL进行合法性检查。

HttpClients漏洞示例
String url = request.getParameter("url"); // 从用户输入中获取URL
CloseableHttpClient client = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = client.execute(httpGet); // 使用用户输入的URL发起请求

代码未对用户输入的URL进行任何验证或限制,直接用于发起HTTP请求。

漏洞代码示例

import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import javax.servlet.http.HttpServletRequest;

public class SSRFExample {
    public static void main(String[] args) {
        HttpServletRequest request = getRequest();
        // 获取请求中的 URL 参数
        String userInput = request.getParameter("url");
        if (userInput == null || userInput.isEmpty()) {
            System.out.println("Please provide a URL as a parameter.");
            return;
        }
        try {
            // 创建URL对象
            URL url = new URL(userInput);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET"); //GET方法请求
            // 读取响应内容
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String inputLine;
            StringBuilder content = new StringBuilder();
            while ((inputLine = in.readLine()) != null) {
                content.append(inputLine);
            }
            in.close();
            conn.disconnect();

            // 打印响应内容
            System.out.println(content.toString());
        } catch (Exception e) {
            e.printStackTrace();  // 捕获异常并打印错误信息
        }
    }
}

在这个示例中,用户输入的URL直接用于发起HTTP请求,如果攻击者输入一个指向内网服务的URL(如http://169.254.169.254/latest/meta-data/),服务器会访问到敏感的内部资源并返回给客户端。

防范

1、严格验证用户输入的 URL,确保其符合预期格式和范围。

2、合理处理 302 跳转,对跳转地址进行校验,而非直接禁止。

3、限制协议类型,仅允许 http/https,禁止跨协议访问。

4、采用白名单机制,仅允许访问特定域名或 IP 地址:

  • 准确识别内网 IP,并正确解析 Host 头信息。
  • 禁止访问内网 IP 地址及私有 IP 段(如 127.0.0.1、192.168.x.x、10.x.x.x 等)。
  • 拒绝访问敏感 URL(如云服务元数据地址)。

5、配置 Web 端口白名单,防止端口扫描(可能对业务有一定限制)。

标准代码

private static final int CONNECT_TIMEOUT = 5000; // 连接超时时间(毫秒)

public static boolean checkSsrf(String url) {
    HttpURLConnection connection;
    String finalUrl = url;

    try {
        do {
            // 仅允许 http/https 协议,防止跨协议攻击
            if (!Pattern.matches("^https?://.+$", finalUrl)) {
                return false;
            }
            // 判断是否为内网 IP,避免 SSRF 访问内部服务
            if (isInnerIp(finalUrl)) {
                return false;
            }

            // 发起 HTTP 请求,不跟随跳转
            connection = (HttpURLConnection) new URL(finalUrl).openConnection();
            connection.setInstanceFollowRedirects(false); // 禁止自动跳转
            connection.setUseCaches(false); // 禁用缓存,确保每次请求都重新解析 DNS
            connection.setConnectTimeout(CONNECT_TIMEOUT); // 设置超时时间,防止长时间阻塞
            connection.connect(); // 触发 DNS 解析,尝试建立连接

            int statusCode = connection.getResponseCode();
            // 检查 3xx 状态码(重定向),但排除 304(缓存)和 306(保留未使用)
            if (statusCode >= 300 && statusCode <= 307 && statusCode != 304 && statusCode != 306) {
                String redirectedUrl = connection.getHeaderField("Location");
                if (redirectedUrl == null) {
                    break; // 若无重定向地址,则终止检查
                }
                finalUrl = redirectedUrl; // 继续检查跳转后的 URL
            } else {
                break; // 结束循环,URL 无需进一步检查
            }
        } while (connection.getResponseCode() != HttpURLConnection.HTTP_OK); // 仅当返回 200 时才终止检查

        connection.disconnect();
    } catch (Exception e) {
        return true; // 捕获异常,返回 true(默认安全策略)
    }
    return true;
}

private static boolean isInnerIp(String url) throws URISyntaxException, UnknownHostException {
    URI uri = new URI(url);
    String host = uri.getHost(); // 提取 Host 部分

    // 解析 Host 对应的 IP 地址,并标准化为 IPv4 格式
    InetAddress inetAddress = InetAddress.getByName(host);
    String ip = inetAddress.getHostAddress();

    // 定义内网 IP 段(私有地址范围)
    String[] privateSubnets = {"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8"};

    for (String subnet : privateSubnets) {
        SubnetUtils subnetUtils = new SubnetUtils(subnet); // 使用 commons-net 进行子网匹配
        if (subnetUtils.getInfo().isInRange(ip)) {
            return true; // IP 属于内网地址范围,返回 true
        }
    }
    return false;
}

说明:
1、return false → 发现安全问题时(如协议不合法、检测到内网 IP),返回 false,表示 URL 不安全。
2、return true → 没有发现明确安全问题,返回 true,允许执行。

相关文章:

  • git子仓库管理的两种方式
  • 【Python】整数除法不正确,少1的问题,以及有关浮点数转换的精度问题
  • 二:RV1126的VDEC视频解码模块的工作流程
  • AI革命编程学习:Python语法速通与高阶突破全实战(第二部分:AI辅助调试与高阶编程)
  • Flink-DataStreamAPI-生成水印
  • C++使用BFS求解最短路径
  • NS3学习——运行自定义拥塞控制算法步骤
  • 动态规划中固定倒数第二个数与倒数第一个数的区别与应用场景分析 —— 从最长等差数列问题到统计等差数列个数的填表策略对比
  • 关于前后端整合和打包成exe文件的个人的总结和思考
  • 数据集构建与训练前准备
  • VIC模型有哪些优势?适用哪些范围?基于QGIS的VIC模型建模;未来气候变化模型预测;基于R语言VIC参数率定和优化
  • 【Academy】Web 缓存欺骗 ------ Web cache deception
  • 如何实现wordpress搜索自字义字段内容
  • 分享最佳ChatGPT替代11个方案(2025)
  • 计算机组成原理(第六章 总线)
  • 关于在electron(Nodejs)中使用 Napi 的简单记录
  • 内容中台的核心架构是什么?
  • 【在校课堂笔记】Python 第 3 节课 总结
  • FlinkSQL源码笔记
  • ~(取反)在算法竞赛中的常见用法和注意事项
  • 百年前淮北乡村的风俗画卷——读郑重 《九十自述》
  • “世界茶树原产地”打通全产业链,茶文旅融合助力西双版纳高质量发展
  • 墨西哥城市长私人秘书及顾问遇袭身亡
  • 国家发改委:不断完善稳就业稳经济的政策工具箱,确保必要时能够及时出台实施
  • 林园:茅台一直是稀缺资源,股东比较有信仰,依旧看好白酒市场
  • 第1现场|俄媒称乌克兰网上出售北约对乌军培训手册