跳转至

2 Java Web 常见漏洞

3457 个字 361 行代码 预计阅读时间 23 分钟

2.1 文件访问类漏洞

常见的 Java 文件操作相关的漏洞大致有如下类型:

  1. 任意目录遍历
  2. 任意文件、目录复制
  3. 任意文件读取 / 下载
  4. 任意文件、目录修改 / 重命名
  5. 任意文件、目录删除
  6. ......

产生这些漏洞的原因都是因为程序对文件或目录访问控制不严、程序内部逻辑错误导致的任意文件或目录恶意访问漏洞。

任意文件读写漏洞即因为没有验证请求的资源文件是否合法导致的,此类漏洞在 Java 中有着较高的几率出现,任意文件读取漏洞原理很简单,但一些知名的中间件:WeblogicTomcatResin又或者是主流 MVC 框架 :Spring MVCStruts2都存在此类漏洞。

任意文件读取

存在恶意文件读取漏洞代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.File" %>
<%@ page import="java.io.FileInputStream" %>

<pre>
<%
File file = new File(request.getServletContext().getRealPath("/")+ request.getParameter("name"));
FileInputStream in = new FileInputStream(file);
int tempbyte;

while ((tempbyte = in.read()) != -1) {
    out.write(tempbyte);
}

in.close();
%>
</pre>

输入http://localhost:8080/?name=index.jsp即可读取index.jsp文件内容。 alt text

若网站未对目录访问进行限制,攻击者可以通过路径穿越实现任意文件读取,例如http://localhost:8080/?name=../../../../../etc/passwd读取/etc/passwd文件内容。

任意文件写

Example
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.File" %>
<%@ page import="java.io.FileOutputStream" %>

<%
    File file = new File(request.getParameter("f"));
    FileOutputStream fos = new FileOutputStream(file);
    fos.write(request.getParameter("c").getBytes());
    fos.flush();
    fos.close();

    out.println(file.getAbsoluteFile() + "\t" + file.exists());
%>

输入http://localhost:8080/Tomcatdemo_war_exploded/?f=../../a.rar&c=aaa即可通过路径穿越实现任意文件写入 alt text

其他目录遍历、重命名等漏洞同理

任意文件 / 目录访问漏洞修复

限制读取目录或文件

在读取文件或者目录时严格控制用户传入参数,禁止或限制用户传入文件路径。

Example
<%@ page import="java.io.File" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
    // 定义限制用户遍历的文件目录常量
    private static final String IMAGE_DIR = "/data/images/";
%>
<%
    // 定义需要遍历的目录
    String dirStr = request.getParameter("dir");

    if (dirStr != null) {
        File dir = new File(dirStr);

        // 获取文件绝对路径,转换成标准的文件路径
        String fileDir = (dir.getAbsoluteFile().getCanonicalFile() + "/").replace("\\\\", "/").replaceAll("/+", "/");
        out.println("<h3>" + fileDir + "</h3>");
        // 检查当前用户传入的目录是否包含在系统限定的目录下
        if (fileDir.startsWith(IMAGE_DIR)) {
            File[] dirs = dir.listFiles();
            out.println("<pre>");
            for (File file : dirs) {
                out.println(file.getName());
            }
            out.println("</pre>");
        } else {
            out.println("目录不合法!");
        }
    }

%>

RASP 防御恶意文件访问攻击

RASP 可以使用 Agent 机制实现 Hook 任意的 Java API,因此可以轻易的捕获到 Java 程序读取的任意文件路径。RASP 可以将 Hook 到的文件路径和 Http 请求的参数进行关联分析,检测 Java 读取的文件路径是否会受到 Http 请求参数的控制,如果发现请求参数最终拼接到了文件路径中应当立即阻断文件访问行为,并记录攻击日志。

RASP 防御思路:

alt text

具体规则例如:

  1. 禁止文件名空字节访问(防止截断绕过)
  2. 禁止写入动态脚本文件(后缀名 jsp、php、aspx 等)
  3. 文件名和请求参数关联分析 (1)
  4. 文件名检测规则和黑名单 ( 禁止例如/etc/passwd等文件访问 )
  1. 如传入的某个参数最终和 Hook 到的文件路径完全一致,那么应当立即禁止文件访问请求,因为即便用户请求的不是恶意文件也肯定是一个存在任意文件读取漏洞的业务功能。

Java 恶意文件访问审计建议

在审计文件读取功能的时候要非常仔细,或许很容易就会有意想不到的收获!快速发现这类漏洞得方式其实也是非常简单的,在 IDEA 中的项目中重点搜下如下文件读取的类。

  1. JDK 原始的java.io.FileInputStreamjava.io.FileOutputStream
  2. JDK 原始的java.io.RandomAccessFile
  3. Apache Commons IO 提供的org.apache.commons.io.FileUtils
  4. JDK1.7 新增的基于 NIO 非阻塞异步读取文件的java.nio.channels.AsynchronousFileChannel类;
  5. JDK1.7 新增的基于 NIO 读取文件的java.nio.file.Files类。常用方法如 :Files.readAllBytesFiles.readAllLines
  6. java.io.File类的listlistFileslistRootsdelete方法;

除此之外,还可以搜索一下FileUtil/FileUtils很有可能用户会封装文件操作的工具类。

2.2 任意文件上传漏洞

Apache commons fileupload 文件上传

Apache commons-fileupload 是一个非常常用的文件上传解析库,Spring MVC、Struts2、Tomcat 等底层处理文件上传请求均使用了它。

code
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.commons.fileupload.FileItemIterator" %>
<%@ page import="org.apache.commons.fileupload.FileItemStream" %>
<%@ page import="org.apache.commons.fileupload.servlet.ServletFileUpload" %>
<%@ page import="org.apache.commons.fileupload.util.Streams" %>
<%@ page import="java.io.File" %>
<%@ page import="java.io.FileOutputStream" %>
<%
    if (ServletFileUpload.isMultipartContent(request)) {
        ServletFileUpload fileUpload       = new ServletFileUpload();
        FileItemIterator  fileItemIterator = fileUpload.getItemIterator(request);

        String dir       = request.getServletContext().getRealPath("/uploads/");
        File   uploadDir = new File(dir);

        if (!uploadDir.exists()) {
            uploadDir.mkdir();
        }

        while (fileItemIterator.hasNext()) {
            FileItemStream fileItemStream = fileItemIterator.next();
            String         fieldName      = fileItemStream.getFieldName();// 字段名称

            if (fileItemStream.isFormField()) {
                String fieldValue = Streams.asString(fileItemStream.openStream());// 字段值
                out.println(fieldName + "=" + fieldValue);
            } else {
                String fileName   = fileItemStream.getName();
                File   uploadFile = new File(uploadDir, fileName);
                out.println(fieldName + "=" + fileName);
                FileOutputStream fos = new FileOutputStream(uploadFile);

                // 写文件
                Streams.copy(fileItemStream.openStream(), fos, true);

                out.println("文件上传成功:" + uploadFile.getAbsolutePath());
            }
        }
    } else {
%>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>File upload</title>
</head>
<body>
<form action="" enctype="multipart/form-data" method="post">
    <p>
        用户名: <input name="username" type="text"/>
        文件: <input id="file" name="file" type="file"/>
    </p>
    <input name="submit" type="submit" value="Submit"/>
</form>
</body>
</html>
<%
    }
%>

Servlet 内置文件上传解析

Servlet3.0 新增了对文件上传请求解析的支持,javax.servlet.http.HttpServletRequest#getParts,使用 request.getParts(); 即可获取文件上传包解析后的结果。

JSP multipart-config:

JSP 使用 request.getParts(); 必须配置 multipart-config,否则请求时会报错Unable to process parts as no multi-part configuration has been provided(由于没有提供 multi-part 配置,无法处理 parts

Example
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

<servlet>
        <servlet-name>file-upload-parts.jsp</servlet-name>
        <jsp-file>/modules/servlet/fileupload/file-upload-parts.jsp</jsp-file>
        <multipart-config>
            <max-file-size>1000000</max-file-size>
            <max-request-size>1000000</max-request-size>
            <file-size-threshold>1000000</file-size-threshold>
        </multipart-config>
    </servlet>

    <servlet-mapping>
        <servlet-name>file-upload-parts.jsp</servlet-name>
        <url-pattern>/modules/servlet/fileupload/file-upload-parts.jsp</url-pattern>
    </servlet-mapping>

</web-app>

Servlet @MultipartConfig

Servlet3.0 需要配置 @MultipartConfig 注解才能支持 multipart 解析

Example
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;

@MultipartConfig
@WebServlet(urlPatterns = "/FileUploadServlet")
public class FileUploadServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        PrintWriter out = resp.getWriter();

        out.println("<!DOCTYPE html>\n" +
                "<html lang=\"zh\">\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <title>File upload</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "<form action=\"\" enctype=\"multipart/form-data\" method=\"post\">\n" +
                "    <p>\n" +
                "        用户名: <input name=\"username\" type=\"text\"/>\n" +
                "        文件: <input id=\"file\" name=\"file\" type=\"file\"/>\n" +
                "    </p>\n" +
                "    <input name=\"submit\" type=\"submit\" value=\"Submit\"/>\n" +
                "</form>\n" +
                "</body>\n" +
                "</html>");

        out.flush();
        out.close();
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter out         = response.getWriter();
        String      contentType = request.getContentType();

        // 检测是否是multipart请求
        if (contentType != null && contentType.startsWith("multipart/")) {
            String dir       = request.getSession().getServletContext().getRealPath("/uploads/");
            File   uploadDir = new File(dir);

            if (!uploadDir.exists()) {
                uploadDir.mkdir();
            }

            Collection<Part> parts = request.getParts();

            for (Part part : parts) {
                String fileName = part.getSubmittedFileName();

                if (fileName != null) {
                    File uploadFile = new File(uploadDir, fileName);
                    out.println(part.getName() + ": " + uploadFile.getAbsolutePath());

                    FileUtils.write(uploadFile, IOUtils.toString(part.getInputStream(), "UTF-8"));
                } else {
                    out.println(part.getName() + ": " + IOUtils.toString(part.getInputStream()));
                }
            }
        }

        out.flush();
        out.close();
    }

}

Spring MVC 文件上传

Spring MVC 会自动解析 multipart/form-data 请求,将 multipart 中的对象封装到 MultipartRequest 对象中,所以在 Controller 中使用@RequestParam注解就可以映射 multipart 中的对象了,如:@RequestParam("file") MultipartFile file。

Example
import org.javaweb.utils.FileUtils;
import org.javaweb.utils.HttpServletResponseUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

import static org.javaweb.utils.HttpServletRequestUtils.getDocumentRoot;

@Controller
@RequestMapping("/FileUpload/")
public class FileUploadController {

    @RequestMapping("/upload.php")
    public void uploadPage(HttpServletResponse response) {
        HttpServletResponseUtils.responseHTML(response, "<!DOCTYPE html>\n" +
                "<html lang=\"en\">\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <title>File upload</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "<form action=\"/FileUpload/upload.do\" enctype=\"multipart/form-data\" method=\"post\">\n" +
                "    <p>\n" +
                "        用户名: <input name=\"username\" type=\"text\"/>\n" +
                "        文件: <input id=\"file\" name=\"file\" type=\"file\"/>\n" +
                "    </p>\n" +
                "    <input name=\"submit\" type=\"submit\" value=\"Submit\"/>\n" +
                "</form>\n" +
                "</body>\n" +
                "</html>");
    }

    @ResponseBody
    @RequestMapping("/upload.do")
    public Map<String, Object> upload(String username, @RequestParam("file") MultipartFile file, HttpServletRequest request) {
        // 文件名称
        String filePath   = "uploads/" + username + "/" + file.getOriginalFilename();
        File   uploadFile = new File(getDocumentRoot(request), filePath);

        // 上传目录
        File uploadDir = uploadFile.getParentFile();

        // 上传文件对象
        Map<String, Object> jsonMap = new LinkedHashMap<String, Object>();

        if (!uploadDir.exists()) {
            uploadDir.mkdirs();
        }

        try {
            FileUtils.copyInputStreamToFile(file.getInputStream(), uploadFile);

            jsonMap.put("url", filePath);
            jsonMap.put("msg", "上传成功!");
        } catch (IOException e) {
            jsonMap.put("msg", "上传失败,服务器异常!");
        }

        return jsonMap;
    }

}

文件上传 - 编码特性

QP 编码

QP 编码quoted-printable)是邮件协议中的一种内容编码方式,Quoted-printable是使用可打印的 ASCII 字符(如字母、数字与“=”)表示各种编码格式下的字符,以便能在 7-bit 数据通路上传输 8-bit 数据 , 或者更一般地说在非 8-bit clean 媒体上正确处理数据,这被定义为 MIME content transfer encoding

Example

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="jakarta.mail.internet.MimeUtility" %>
<%@ page import="java.io.*" %>
<%
String inputString = request.getParameter("qp");
if (inputString != null && !inputString.isEmpty()) {
    String encode = MimeUtility.encodeWord(inputString);
    try {
        String decode = MimeUtility.decodeWord(encode);
        out.println("QP编码后:" + encode + "<br/>");
        out.println("QP解码后:" + decode + "<br/>");
    } catch (Exception e) {
        e.printStackTrace();
    }

}
%>
测试结果

alt text

Apache commons fileupload 库从 1.3 开始支持了 RFC 2047 Header 值编码,从而支持解析使用 QP 编码后的文件名。

上传文件的时候选一个文件名经过 QP 编码后的文件,存储时会将文件名解码后存储,这样就可以绕过文件名检测。

Spring MVC 中同样支持 QP 编码,在 Spring 中有两种处理MultipartResolverorg.springframework.web.multipart.commons.CommonsMultipartResolverorg.springframework.web.multipart.support.StandardServletMultipartResolverCommonsMultipartResolver使用的是commons fileupload解析的所以支持 QP 编码。StandardMultipartHttpServletRequest比较特殊,Spring 4 没有处理 QP 编码,但是在 Spring 5 修改了实现,如果文件名是=?开始?=结尾的话会调用javax.mail库的MimeDelegate解析 QP 编码。

Spring 内置文件名编码特性

Spring 会对文件上传的名称做特殊的处理,org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#parseRequest内置了一种比较特殊的解析文件名的方式,如果传入的multipart请求无法直接使用filename=解析出文件名,Spring 还会使用content-disposition解析一次(使用filename*= (1) 解析文件名

  1. 参数“filename”和“filename*”的不同之处在于“filename*”使用 [RFC5987] 中定义的编码,允许使用 ISO-8859-1 字符集([ISO-8859-1])中不存在的字符。RFC 6266

Multipart 字段解析

Multipart 请求与普通的 GET/POST 参数传输有非常大的区别,因为 Multipart 请求需要后端 Web 应用解析该请求包,Web 容器也不会解析 Multipart 请求。WAF 可能会解析 Multipart 但是很多时候可以直接绕过,比如很多 WAF 无法处理一个数据量较大的 Multipart 请求或者解析 Multipart 时不标准导致绕过。

PHP 中默认会解析 Multipart 请求,也就是说我们除了可以以 GET/POST 方式传参,还可以使用发送 Multipart 请求,后端一样可以接受到 Multipart 中的参数。在 Java MVC 框架中 Spring MVCStruts2 等实现了和 PHP 类似的功能,当框架发现请求方式是 Multipart 时就会主动的解析并将解析结果封装到 HttpServletRequest 中。

RASP 防御恶意文件上传攻击

Apache commons fileupload 防御

Apache commons-fileupload 底层处理解析Multipart的类是org.apache.commons.fileupload.FileUploadBase.FileItemIteratorImpl.FileItemStreamImpl ,只需HookFileItemStreamImpl 类的构造方法就可以获取到Multipart 的字段或者文件名称,RASP只需要检测传入的pName 参数值cmd.jsp` 是否是一个合法的文件名称就可以实现文件上传校验了。

Tip

Tomcat 封装了Apache commons fileupload库,并修改了 fileupload 类的包名,如:org.apache.tomcat.util.http.fileupload.FileUploadBase.FileItemIteratorImpl.FileItemStreamImpl#FileItemStreamImpl,所以应当把这个类也放入检测范围内。

javax.servlet.http.Part 防御

javax.servlet.http.Part是一个接口,不同的容器实现可能不同,RASP 可以对javax.servlet.http.Part接口的getInputStream方法进行 Hook,然后调用getNamegetSubmittedFileName就可以获取到字段名称、文件名等信息。

为了能够适配高版本Jakarta APIJakarta EE8修改了javax.servlet.http.Part API 包名为:jakarta.servlet.http.Part

Spring MVC 文件名内置编码支持

RASP 为了更好的防御文件上传类请求,需要支持 RFC 2047 QP 编码,还需要支持对 Spring MVC 内置的文件名编码处理处理。

2.3 SQL 注入漏洞

攻击方式参考 SQL 注入

SQL 注入防御

为了避免 SQL 注入攻击的产生,需要严格检查请求参数的合法性或使用预编译,请参考 Java 基础中的 JDBC SQL 注入防御方案

RASP 防御 SQL 注入

Java 中,所有的数据库读写操作都需要使用 JDBC 驱动来实现,JDBC 规范中定义了数据库查询的接口,不同的数据库厂商通过实现 JDBC 定义的接口来实现数据库的连接、查询等操作。所以我们可以使用 RASP Hook JDBC 数据库查询的接口类:java.sql.Connectionjava.sql.Statement

例如 Mysql 的驱动包的实现数据库连接的实现类是:com.mysql.jdbc.ConnectionImpl,该类实现了com.mysql.jdbc.MySQLConnection接口,而com.mysql.jdbc.MySQLConnection类是java.sql.Connection的子类,也就是说com.mysql.jdbc.ConnectionImpl接口必须实现java.sql.Connection定义的数据库连接和查询方法。

alt text
RASP 防御 SQL 注入示意

2.4 XSS 漏洞

XSS 攻击

XSS 攻击方式参考 XSS

XSS 防御

XSS 最为常见的处理方式是转义特殊字符,后端程序在接受任何用户输入的参数时都应当优先考虑是否会存在 XSS 攻击。

htmlspecialchars

PHP 中通常会使用 htmlspecialchars 函数会将一些可能有攻击威胁的字符串转义为 html 实体编码,这样可以有效的避免 XSS 攻击。

字符 替换后
& (& 符号 ) &amp;
" ( 双引号 ) &quot;
' ( 单引号 ) &#039;或者&apos;
< ( 小于 ) &lt;
> ( 大于 ) &gt;

Java 中虽然没有内置,但我们可以手动实现其功能,例如全局的 XSS 过滤器

RASP XSS 攻击防御

RASP 可以实现类似于全局 XSSFilter 的请求参数过滤功能,比较稳定的一种方式是 Hook javax.servlet.ServletRequest接口的实现类的getParameter/getParameterValues/getParameterMap等核心方法,在该方法 return 之后插入 RASP 的检测代码。这种实现方案虽然麻烦,但是可以避免触发 Http 请求参数解析问题(Web 应用无法获取getInputStream和乱码等问题

反射型的 XSS 防御比较简单,过滤或拦截掉<>input参数就不再具有攻击性了。

但是 POST 请求的 XSS 参数就没有那么容易过滤了,为了兼顾业务,不能简单的使用htmlSpecialChars的方式直接转义特殊字符,因为很多时候应用程序是必须支持 HTML 标签的(如:<img>、<h1>RASP 在防御 XSS 攻击的时候应当尽可能的保证用户的正常业务不受影响,否则可能导致用户无法业务流程阻塞或崩溃。

为了支持一些常用的 HTML 标签和 HTML 标签属性,RASP 可以通过词法解析的方式,将传入的字符串参数值解析成 HTML 片段,然后分析其中的标签和属性是否合法即可。

2.5 反序列化漏洞

反序列化攻击

参考反序列化

反序列化防御

升级 JDK 版本

JDK6u141JDK7u131JDK 8u121开始引入了 JEP 290JEP 290: Filter Incoming Serialization Data 限制了 RMI 类反序列化,添加了安全过滤机制,在一定程度上阻止了反序列化攻击。

ObjectInputStream在序列化对象时是会调用java.io.ObjectInputStream#filterCheck ->sun.rmi.registry.RegistryImpl#registryFilter,检测合法性:

当攻击者向一个实现了JEP 290的服务端 JDK 发送反序列化对象时会攻击失败并抛出:java.io.InvalidClassException: filter status: REJECTED异常。

JDK9 ObjectInputStream可以设置ObjectInputFilter,可实现自定义对象过滤器,如下:

ObjectInputStream ois = new ObjectInputStream(bis);
ois.setObjectInputFilter(new ObjectInputFilter() {
   @Override
   public Status checkInput(FilterInfo filterInfo) {
      // 序列化类名称
      String className = filterInfo.serialClass().getName();

      // 类名检测逻辑
      return ALLOWED;
   }
});

除此之外,还可以添加 JVM 启动参数:-Djdk.serialFilter过滤危险的类,参考:JDK approach to address deserialization Vulnerability

重写 ObjectInputStream resolveClass

https://github.com/ikkisoft/SerialKiller 是一个非常简单的反序列化攻击检测工具,利用的是继承ObjectInputStream重写resolveClass方法。

重写 ObjectInputStream 类方法虽然灵活,但是必须修改每一个需要反序列化输入流的实现类,比较繁琐。

RASP 防御反序列化攻击

RASP 可以利用动态编辑类字节码的优势,直接编辑ObjectInputStream类的resolveClass/resolveProxyClass方法字节码,动态插入 RASP 类代码,从而实现检测反序列化脚本攻击。

package java.io;

public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants {

    // .. 省略其他代码

    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
            // 插入RASP检测代码,检测ObjectStreamClass反序列化的类名是否合法
    }

    protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
            // 插入RASP检测代码,检测动态代理类接口类名是否合法
    }

}

使用 RASP 检测反序列化攻击,可以不用受制于请求协议、服务、框架等,检测规则可实时更新,从而程度上实现反序列化攻击防御。

2.6 本地命令执行

本地命令执行攻击

参考 Java 基础:命令执行

RASP 防御

Java 底层执行系统命令的 API java.lang.UNIXProcess/ProcessImpl#forkAndExec方法,forkAndExec是一个 native 方法,如果想要 Hook 该方法需要使用 Agent 机制中的Can-Set-Native-Method-Prefix,为forkAndExec设置一个别名,如:__RASP__forkAndExec,然后重写__RASP__forkAndExec方法逻辑,即可实现对原forkAndExec方法 Hook

alt text
Java 本地命令执行 API
Hook forkAndExec
@RASPMethodHook(
      className = "java.lang.ProcessImpl", methodName = CONSTRUCTOR_INIT,
      methodArgsDesc = ".*", methodDescRegexp = true
)
public static class ProcessImplHook extends RASPMethodAdvice {

   @Override
   public RASPHookResult<?> onMethodEnter() {
      try {
         String[] commands = null;

         // JDK9+的API参数不一样!
         if (getArg(0) instanceof String[]) {
            commands = getArg(0);
         } else if (getArg(0) instanceof byte[]) {
            commands = new String[]{new String((byte[]) getArg(0))};
         }

         // 检测执行的命令合法性
         return LocalCommandHookHandler.processCommand(commands, getThisObject(), this);
      } catch (Exception e) {
         RASPLogger.log(AGENT_NAME + "处理ProcessImpl异常:" + e, e);
      }

      return new RASPHookResult<?>(RETURN);
   }

}

请求参数关联分析

获取到本地命令执行的参数后需要与 Http 请求的参数进行关联分析,检测当前执行的系统命令是否与请求参数相关,如果确认当前执行的系统命令来源于 Http 请求参数,那么 RASP 会立即阻止命令执行并阻断 Http 请求。

限制执行本地系统命令

因为本地命令执行的危害性极大,所以在默认情况下可以直接禁止本地命令执行,如果业务的确有必要开启那么可以对相应的业务 URL 做白名单。限制的方式可分为两种类型:

  1. 完全限制本地命令执行,禁止在 Java 中执行任何命令;
  2. 允许程序内部的本地命令执行,只在有 Http 请求的时候才禁止执行命令;

这两种类型的禁止方案为可选方案,可在 RASP 的云端实时配置。

Others

其余漏洞介绍篇幅过长,不适合在此展开,请参考以下链接:


最后更新: 2024年8月16日 12:44:21
创建日期: 2024年7月23日 18:59:51

评论