Java常见Web漏洞总结

约 41 分钟读完

常规漏洞

靶场

相关靶场:

https://github.com/bewhale/JavaSec

https://github.com/whgojp/JavaSecLab

https://github.com/j3ers3/Hello-Java-Sec

选择第二个

搭建数据库(使用docker):docker pull vitamojo/mysql8

docker run -d ` --name vitamojomysql8 ` -p 3306:3306 ` -v C:\Soft\docker\container\vitamojomysql8\conf:/etc/mysql/conf.d ` -v C:\Soft\docker\container\vitamojomysql8\data:/var/lib/mysql ` -v C:\Soft\docker\container\vitamojomysql8\logs:/var/log/mysql ` -e MYSQL_ROOT_PASSWORD=your_db_password ` -e MYSQL_DATABASE=myapp_db ` -e MYSQL_USER=your_db_password ` -e MYSQL_PASSWORD=your_db_password ` vitamojo/mysql8

# 停止容器 docker stop vitamojomysql8

# 启动容器 docker start vitamojomysql8

# 查看日志 docker logs vitamojomysql8

# 查看挂载的日志文件:Get-Content C:\Soft\docker\container\vitamojomysql8\logs\error.log -Wait

连接:docker exec -it vitamojomysql8 mysql -uroot -p

通过idea连接

image-20250804003813225

下载https://github.com/whgojp/JavaSecLab靶场的源码,按照里面提到的进行配置,注意端口的映射。

部署后直接启用,admin/admin登录

Sql注入

核心概念:SQL 注入

  • 定义: SQL 注入是一种攻击技术,攻击者通过在应用程序的输入字段(如表单、URL 参数)中插入恶意的 SQL 代码片段,欺骗后端数据库执行非预期的、有害的 SQL 命令。
  • 危害:
    • 窃取、篡改或删除敏感数据(用户信息、密码、财务数据)。
    • 绕过认证和授权。
    • 执行数据库管理操作(如删除表、关闭数据库)。
    • 在某些情况下,可能控制运行数据库服务器的操作系统。
  • 根本原因: 应用程序将用户输入的数据SQL 语句的结构(代码) 混合在一起,没有进行严格的区分和安全的处理。当用户输入被直接“拼接”到 SQL 语句中时,输入中的恶意 SQL 片段就会被数据库解释为可执行代码的一部分

  1. JDBC (Java Database Connectivity)

    • 角色: Java 访问数据库的标准 API,是其他 ORM 框架(如 MyBatis, Hibernate)的底层基础。
    • 如何引发 SQL 注入:
      • 字符串拼接构造 SQL: 这是 JDBC 中最直接、最高危的引发方式。
        String username = request.getParameter("username"); // 用户输入,如 ' OR '1'='1
        String password = request.getParameter("password"); // 用户输入
        String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
        Statement stmt = connection.createStatement();
        ResultSet rs = stmt.executeQuery(sql); // 危险!
        如果用户输入 username' OR '1'='1password 任意,最终的 SQL 会变成:
        SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'anything'
        由于 '1'='1' 恒为真,此查询会返回所有用户记录,攻击者可能借此绕过登录验证。
    • 防范措施:
      • 使用 PreparedStatement (参数化查询): 这是 JDBC 防 SQL 注入的黄金标准。
        String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
        PreparedStatement pstmt = connection.prepareStatement(sql);
        pstmt.setString(1, username); // 安全!数据库会将参数值视为纯数据,而非代码
        pstmt.setString(2, password);
        ResultSet rs = pstmt.executeQuery();
        PreparedStatement 预编译 SQL 语句模板(? 是占位符),数据库引擎会预先理解语句结构。调用 setXxx() 方法传入参数值时,数据库会将这些值严格地作为数据绑定到对应的占位符上,不会将它们解释为 SQL 语法的一部分。即使参数值中包含 SQL 关键字或特殊字符(如单引号),它们也会被安全地转义或处理。
      • 避免使用 Statement 除非有非常特殊且安全的理由,否则绝对不要用字符串拼接构造 SQL 语句并通过 Statement 执行。
      • 输入验证与过滤: 在业务层面,对输入进行严格的类型、格式、长度、范围检查(例如,用户名只能是字母数字)。虽然不能替代参数化查询,但作为纵深防御的一部分很有价值。注意: 仅依赖黑名单过滤(如移除 ', ;, DROP 等)是非常脆弱且不推荐的,容易被绕过。
  2. MyBatis

    • 角色: 一个半自动化的持久层框架,开发者需要编写 SQL,但 MyBatis 简化了参数设置和结果映射。
    • 如何引发 SQL 注入:
      • 在 XML Mapper 或注解中使用 ${} 进行字符串替换: MyBatis 提供了两种参数占位符:
        • #{} (安全): 类似于 JDBC 的 PreparedStatement 参数化查询,是首选
        • ${} (危险): 直接在 SQL 语句中进行字符串替换(相当于拼接)。
      • 错误示例:
        <!-- XML Mapper -->
        <select id="findUser" resultType="User">
            SELECT * FROM users
            ORDER BY ${sortColumn} ${sortOrder} -- 动态排序字段和顺序
        </select>
        // 注解方式 (同样危险)
        @Select("SELECT * FROM users ORDER BY ${sortColumn} ${sortOrder}")
        List<User> findUsersSorted(@Param("sortColumn") String sortColumn, @Param("sortOrder") String sortOrder);
        如果攻击者能控制 sortColumnsortOrder 参数(例如通过修改请求参数),传入如 ; DROP TABLE users; --name; SELECT SLEEP(10); --,就会导致灾难性后果或探测性攻击。
      • 动态 SQL (<if>, <choose>, <foreach>) 中的 ${} 在动态 SQL 标签内部错误地使用 ${} 拼接用户输入也会引入风险。
    • 防范措施:
      • 优先使用 #{} 对于所有用户输入不可信来源的数据,必须使用 #{} 占位符。
      • 谨慎使用 ${} 仅在绝对必要且参数值完全可控的情况下使用 ${}。典型的安全场景是动态指定列名表名,但这些值不应直接来自用户输入,而应该:
        • 在代码层面对传入的列名/表名进行白名单校验(检查是否在预定义的合法值集合中)。
        • 或者,在映射器方法内部进行校验。
      • 避免在 ORDER BY 等子句中直接使用用户输入: 如果必须支持动态排序,考虑在应用层将用户输入的排序字段映射到安全的列名枚举或常量。绝对不要将未经校验的用户输入直接放入 ${} 用于 ORDER BYGROUP BY
      • 注意 like 查询: 使用 #{} 时,通配符 %_ 需要作为参数值的一部分传入:
        <select id="searchUsers">
            SELECT * FROM users WHERE username LIKE CONCAT('%', #{keyword}, '%') <!-- 安全 -->
            <!-- 或者 -->
            SELECT * FROM users WHERE username LIKE "%"#{keyword}"%" <!-- 安全 (MyBatis 特性) -->
        </select>
        避免 LIKE '%${keyword}%' (危险!)。
  3. Hibernate & JPA (Java Persistence API)

    • 角色:
      • Hibernate: 一个功能强大的全自动 ORM (对象关系映射) 框架实现。
      • JPA: Java EE/Jakarta EE 的 ORM 标准规范。Hibernate 是 JPA 最流行的实现之一。其他实现还有 EclipseLink, OpenJPA 等。JPA 定义了标准 API,而 Hibernate 提供了这些 API 的实现以及额外的特性。
    • 如何引发 SQL 注入:
      • HQL (Hibernate Query Language) / JPQL (Java Persistence Query Language) 中的字符串拼接: 虽然 HQL/JPQL 操作的是对象和属性,但它们最终会被翻译成 SQL。如果使用字符串拼接构造 HQL/JPQL,风险等同于 SQL 拼接。
        String username = request.getParameter("username");
        // 危险!字符串拼接 HQL
        String hql = "FROM User WHERE username = '" + username + "'";
        Query<User> query = session.createQuery(hql, User.class);
        List<User> users = query.getResultList();
        同样,恶意输入 ' OR '1'='1 会导致查询所有用户。
      • 原生 SQL 查询 (createNativeQuery / @NamedNativeQuery) 中的字符串拼接: 当直接使用原生 SQL 时,如果拼接用户输入,风险与直接使用 JDBC Statement 完全一样。
        String sql = "SELECT * FROM users WHERE username = '" + username + "'"; // 极度危险!
        Query query = session.createNativeQuery(sql);
      • javax.persistence.criteria.CriteriaQuery 中的不安全构造: 虽然 Criteria API 是类型安全的,但如果不小心(通常比较难),在构建 Predicate 时直接拼接字符串也可能引入风险(罕见但理论存在)。
    • 防范措施:
      • 使用参数化查询 (命名参数或位置参数): 这是 Hibernate/JPA 防 SQL 注入的核心方法。
        • HQL/JPQL 参数化:
          String hql = "FROM User WHERE username = :uname"; // 命名参数
          Query<User> query = session.createQuery(hql, User.class);
          query.setParameter("uname", username); // 安全
          String jpql = "SELECT u FROM User u WHERE u.username = ?1"; // 位置参数
          TypedQuery<User> query = entityManager.createQuery(jpql, User.class);
          query.setParameter(1, username); // 安全
        • 原生 SQL 查询参数化: 必须对原生 SQL 查询也使用参数化!
          String sql = "SELECT * FROM users WHERE username = :uname";
          NativeQuery query = session.createNativeQuery(sql);
          query.setParameter("uname", username); // 安全
          String sql = "SELECT * FROM users WHERE username = ?";
          Query query = entityManager.createNativeQuery(sql);
          query.setParameter(1, username); // 安全
      • 避免在 HQL/JPQL 或原生 SQL 中拼接字符串: 绝对禁止。
      • 优先使用 Criteria API 或 QueryDSL: 这些类型安全的查询构建器通过编程接口构造查询,几乎完全消除了 SQL 注入的风险,因为它们在构造过程中会进行类型检查,并且底层使用参数化绑定。
        // JPA Criteria API 示例 (安全)
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<User> cq = cb.createQuery(User.class);
        Root<User> root = cq.from(User.class);
        cq.select(root).where(cb.equal(root.get("username"), username));
        TypedQuery<User> query = entityManager.createQuery(cq);
        List<User> users = query.getResultList();
      • 谨慎使用原生 SQL (createNativeQuery): 仅在绝对需要时使用(如复杂报表、数据库特定功能),并且必须使用参数化。优先考虑 HQL/JPQL 或 Criteria API。
      • 输入验证: 同样是纵深防御的一部分。

总结与关键防护原则

  1. 首要原则:参数化查询 (Parameterized Queries / Prepared Statements): 无论使用 JDBC、MyBatis、Hibernate 还是 JPA,将用户输入作为查询参数绑定(使用 ? / :name / #{}),而不是直接拼接到 SQL/HQL/JPQL 语句中,是防御 SQL 注入最根本、最有效的手段。数据库引擎会区分代码和数据。
  2. 理解占位符语义:
    • 安全 (?, :name, #{}, setParameter()): 表示参数绑定。
    • 危险 (字符串拼接, ${}): 表示直接文本替换。
  3. ORM 不是免死金牌: Hibernate/JPA 等 ORM 框架本身不自动防止 SQL 注入。开发者错误地拼接查询字符串不安全地使用原生 SQL 时,风险依然存在。必须正确使用其参数化机制。
  4. 最小权限原则: 应用程序连接数据库的用户应只拥有完成其功能所必需的最小权限。避免使用具有 DBA 权限的账号。这样即使发生注入,损害也能被限制。
  5. 纵深防御:
    • 输入验证与过滤: 在数据进入持久层之前,进行严格的格式、类型、长度、范围校验。使用白名单优于黑名单。
    • 输出编码: 在 Web 层,对从数据库取出并显示给用户的数据进行适当的输出编码,防止 XSS 等二次攻击(虽然不直接防 SQL 注入,但提升整体安全)。
    • 使用安全框架: 考虑集成 Spring Security 等框架,它们提供额外的安全层和工具。
    • 定期安全审计与渗透测试: 使用自动化工具(如 OWASP ZAP, SQLMap)和人工审计检查应用漏洞。
    • 依赖库更新: 保持 ORM 框架、数据库驱动等依赖库更新到最新版本,以修复已知安全漏洞。
    • 日志与监控: 记录数据库操作日志,监控异常查询模式。

结论:

图片中指出的 JDBC、MyBatis、Hibernate、JPA 这四点,代表了 Java 中进行数据库交互的不同层次和技术。它们都存在 SQL 注入的风险,风险根源都在于开发者不安全地将用户输入拼接到了查询语句中。 防范的关键在于始终如一地、正确地使用参数化查询机制PreparedStatement, MyBatis #{}, Hibernate/JPA setParameter()),并辅以输入验证、最小权限等纵深防御措施。理解每种技术中参数占位符的正确用法和安全边界至关重要。安全编码意识和实践是防御 SQL 注入的核心。

靶场定位后端源码

image-20250804142259508

打开浏览器开发者模式抓取Run的数据包,

image-20250804142347590

http://127.0.0.1:8001/sqli/jdbc/vul1?username=test&password=1%27%20and%20updatexml(1%2Cconcat(0x7e%2C(SELECT%20user())%2C0x7e)%2C1)%20AND%20%271%27%3D%271&type=add&_=1754288530545

image-20250804142559882

image-20250804142649825

-JDBC

1、采用Statement方法拼接SQL语句

2、PrepareStatement会对SQL语句进行预编译,但如果直接采取拼接的方式构造SQL,此时进行预编译也无用。

3、JDBCTemplate是Spring对JDBC的封装,如果使用拼接语句便会产生注入

4、自定义过滤(黑白名单)

安全写法:SQL语句占位符(?) + PrepareStatement预编译

-MyBatis

MyBatis支持两种参数符号,一种是#,另一种是$,#使用预编译,$使用拼接SQL。

1、order by注入:由于使用#{}会将对象转成字符串,形成order by "user" desc造成错误,因此很多研发会采用${}来解决,从而造成注入.

2、like 注入:模糊搜索时,直接使用'%#{q}%' 会报错,部分研发图方便直接改成'%${q}%'从而造成注入.

3、in注入:in之后多个id查询时使用#同样会报错,从而造成注入.

-Hibernate

1、setParameter:预编译

2、username=:username 预编译

-JPA

1、username=:username 预编译

总结

黑盒:正常发现和利用即可

白盒:

1、确定数据库通讯技术

2、确定类型后找调用写法

3、确定写法是否安全(预编译)

XXE注入

Java XXE 注入全面解析与防御指南

一、XXE 注入核心原理

XXE (XML External Entity) 注入是一种利用 XML 解析器处理外部实体时产生的安全漏洞。攻击者通过在 XML 文档中构造恶意的外部实体引用,可以导致:

  1. 读取服务器本地文件(如 /etc/passwd
  2. 发起 SSRF 攻击(服务器端请求伪造)
  3. 造成拒绝服务(DoS)
  4. 端口扫描内网服务

二、Java 中易受攻击的 XML 解析类

以下是 Java 中 12 种常见的 XML 处理类及其风险点:

1. XMLReader

XMLReader reader = XMLReaderFactory.createXMLReader();
// 危险:未禁用外部实体
reader.parse(new InputSource(new StringReader(xml)));

2. SAXParser

SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
// 危险:未禁用外部实体
parser.parse(new InputSource(new StringReader(xml)), handler);

3. DocumentBuilder

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
// 危险:未禁用外部实体
Document doc = db.parse(new InputSource(new StringReader(xml)));

4. XMLStreamReader

XMLInputFactory factory = XMLInputFactory.newInstance();
// 危险:未禁用外部实体
XMLStreamReader reader = factory.createXMLStreamReader(new StringReader(xml));

5. SAXReader (DOM4J)

SAXReader reader = new SAXReader();
// 危险:未禁用外部实体
Document document = reader.read(new StringReader(xml));

6. SAXBuilder (JDOM)

SAXBuilder builder = new SAXBuilder();
// 危险:未禁用外部实体
Document doc = builder.build(new StringReader(xml));

7. SAXSource

SAXSource source = new SAXSource(new InputSource(new StringReader(xml)));
// 需要确保底层解析器已禁用外部实体

8. TransformerFactory

TransformerFactory tf = TransformerFactory.newInstance();
// 危险:未禁用外部实体
Transformer transformer = tf.newTransformer();
transformer.transform(new StreamSource(new StringReader(xml)), new StreamResult(output));

9. SAXTransformerFactory

SAXTransformerFactory stf = (SAXTransformerFactory)TransformerFactory.newInstance();
// 危险:未禁用外部实体

10. SchemaFactory

SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
// 危险:未禁用外部实体
Schema schema = factory.newSchema(new StreamSource(new StringReader(xsd)));

11. Unmarshaller (JAXB)

JAXBContext context = JAXBContext.newInstance(SomeClass.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
// 危险:未禁用外部实体
SomeClass obj = (SomeClass) unmarshaller.unmarshal(new StringReader(xml));

12. XPathExpression

XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
// 危险:未禁用外部实体
XPathExpression expr = xpath.compile("//someElement/text()");
String result = expr.evaluate(new InputSource(new StringReader(xml)));

三、安全防护措施

通用防护方案

对于所有 XML 解析器,都应采取以下防护措施:

  1. 禁用 DTD 完全(最彻底方案):

    factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
  2. 禁用外部实体(如果必须允许 DTD):

    factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
    factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
  3. 启用安全处理模式

    factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);

各解析器具体配置

1. DocumentBuilderFactory

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);

2. SAXParserFactory

SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

3. XMLInputFactory (StAX)

XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);

4. DOM4J (SAXReader)

SAXReader reader = new SAXReader();
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
reader.setFeature("http://xml.org/sax/features/external-general-entities", false);

5. JDOM (SAXBuilder)

SAXBuilder builder = new SAXBuilder();
builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
builder.setFeature("http://xml.org/sax/features/external-general-entities", false);

6. TransformerFactory

TransformerFactory tf = TransformerFactory.newInstance();
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");

7. JAXB (Unmarshaller)

JAXBContext context = JAXBContext.newInstance(SomeClass.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
unmarshaller.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");

四、代码审计要点

  1. 查找所有 XML 解析点:搜索上述12个类的使用情况
  2. 检查安全配置:确认是否设置了防护措施
  3. 关注输入来源:特别检查用户可控的 XML 输入
  4. 注意间接解析:如 WebService、SOAP、REST API 等可能隐式解析 XML

五、防御深度策略

  1. 输入过滤:对用户输入的 XML 进行严格校验
  2. 输出编码:对 XML 输出内容进行适当编码
  3. 最小权限:运行解析器的账户应具有最小权限
  4. 依赖更新:保持 XML 解析库最新版本
  5. 日志监控:记录异常解析行为

六、总结

理解 XXE 注入的关键在于:

  1. 识别所有 XML 解析点:Java 中有多种 XML 解析方式,都需要防护
  2. 控制外部实体处理:禁用或严格限制外部实体解析
  3. 纵深防御:从输入到输出多层防护

通过系统性地审计和加固这些 XML 处理点,可以有效防御 XXE 注入攻击。

对于靶场中

image-20250804144943606

public String vul1(String payload) { try { XMLReader xmlReader = XMLReaderFactory.createXMLReader(); StringWriter stringWriter = new StringWriter(); xmlReader.setContentHandler(new DefaultHandler() { public void characters(char[] ch, int start, int length) { for (int i = start; i < start + length; i++) { if (ch[i] == '\n') { stringWriter.write("
"); } else { stringWriter.write(ch[i]); } } } }); xmlReader.parse(new InputSource(new StringReader(payload))); return stringWriter.toString(); } catch (Exception e) { return e.getMessage(); } }

代码功能解析

  • 方法通过XMLReaderFactory.createXMLReader()创建一个 XML 解析器(XMLReader)。
  • 设置ContentHandler为自定义的DefaultHandler子类,重写characters方法:遍历解析到的字符,将换行符\n替换为 HTML 的<br/>标签,其他字符直接写入StringWriter
  • 最终通过xmlReader.parse(new InputSource(new StringReader(payload)))解析输入的payload(XML 字符串),并返回处理后的结果。

XMLReaderFactory.createXMLReader()创建的 XML 解析器,默认配置下可能允许解析外部实体(具体取决于底层使用的 XML 解析器实现,如 Xerces 等)。 代码中没有显式禁用外部实体解析(如未设置FEATURE_SECURE_PROCESSING特性,或未禁止ENTITY_EXPANSION_LIMIT等),导致攻击者可以构造包含外部实体的恶意 XML payload。

假设攻击者传入如下恶意payload

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE root [ <!ENTITY xxe SYSTEM "file:///c:/windows/system32/drivers/etc/hosts"> <!-- 引用本地文件 --> ]>

<root>&xxe;</root>

靶场中点击run后是针对于linux环境,修改payload http://127.0.0.1:8001/xxe/vul1?payload=%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%3C%21DOCTYPE%20root%20%5B%3C%21ENTITY%20xxe%20SYSTEM%20%22file%3A%2F%2F%2FC%3A%2FWindows%2Fwin.ini%22%3E%5D%3E%3Croot%3E%26xxe%3B%3C%2Froot%3E

就可以看到windows的win.ini配置文件

← CC2链分析 CC4链 →