Java常见Web漏洞总结
常规漏洞
靶场
相关靶场:
https://github.com/bewhale/JavaSec
选择第二个
搭建数据库(使用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连接

下载https://github.com/whgojp/JavaSecLab靶场的源码,按照里面提到的进行配置,注意端口的映射。
部署后直接启用,admin/admin登录
Sql注入
核心概念:SQL 注入
- 定义: SQL 注入是一种攻击技术,攻击者通过在应用程序的输入字段(如表单、URL 参数)中插入恶意的 SQL 代码片段,欺骗后端数据库执行非预期的、有害的 SQL 命令。
- 危害:
- 窃取、篡改或删除敏感数据(用户信息、密码、财务数据)。
- 绕过认证和授权。
- 执行数据库管理操作(如删除表、关闭数据库)。
- 在某些情况下,可能控制运行数据库服务器的操作系统。
- 根本原因: 应用程序将用户输入的数据和SQL 语句的结构(代码) 混合在一起,没有进行严格的区分和安全的处理。当用户输入被直接“拼接”到 SQL 语句中时,输入中的恶意 SQL 片段就会被数据库解释为可执行代码的一部分
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'='1且password任意,最终的 SQL 会变成:由于SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'anything''1'='1'恒为真,此查询会返回所有用户记录,攻击者可能借此绕过登录验证。
- 字符串拼接构造 SQL: 这是 JDBC 中最直接、最高危的引发方式。
- 防范措施:
- 使用
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等)是非常脆弱且不推荐的,容易被绕过。
- 使用
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);sortColumn或sortOrder参数(例如通过修改请求参数),传入如; DROP TABLE users; --或name; SELECT SLEEP(10); --,就会导致灾难性后果或探测性攻击。 - 动态 SQL (
<if>,<choose>,<foreach>) 中的${}: 在动态 SQL 标签内部错误地使用${}拼接用户输入也会引入风险。
- 在 XML Mapper 或注解中使用
- 防范措施:
- 优先使用
#{}: 对于所有用户输入或不可信来源的数据,必须使用#{}占位符。 - 谨慎使用
${}: 仅在绝对必要且参数值完全可控的情况下使用${}。典型的安全场景是动态指定列名或表名,但这些值不应直接来自用户输入,而应该:- 在代码层面对传入的列名/表名进行白名单校验(检查是否在预定义的合法值集合中)。
- 或者,在映射器方法内部进行校验。
- 避免在
ORDER BY等子句中直接使用用户输入: 如果必须支持动态排序,考虑在应用层将用户输入的排序字段映射到安全的列名枚举或常量。绝对不要将未经校验的用户输入直接放入${}用于ORDER BY或GROUP BY。 - 注意
like查询: 使用#{}时,通配符%和_需要作为参数值的一部分传入:避免<select id="searchUsers"> SELECT * FROM users WHERE username LIKE CONCAT('%', #{keyword}, '%') <!-- 安全 --> <!-- 或者 --> SELECT * FROM users WHERE username LIKE "%"#{keyword}"%" <!-- 安全 (MyBatis 特性) --> </select>LIKE '%${keyword}%'(危险!)。
- 优先使用
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 时,如果拼接用户输入,风险与直接使用 JDBCStatement完全一样。String sql = "SELECT * FROM users WHERE username = '" + username + "'"; // 极度危险! Query query = session.createNativeQuery(sql); javax.persistence.criteria.CriteriaQuery中的不安全构造: 虽然 Criteria API 是类型安全的,但如果不小心(通常比较难),在构建Predicate时直接拼接字符串也可能引入风险(罕见但理论存在)。
- HQL (Hibernate Query Language) / JPQL (Java Persistence Query Language) 中的字符串拼接: 虽然 HQL/JPQL 操作的是对象和属性,但它们最终会被翻译成 SQL。如果使用字符串拼接构造 HQL/JPQL,风险等同于 SQL 拼接。
- 防范措施:
- 使用参数化查询 (命名参数或位置参数): 这是 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 参数化:
- 避免在 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。 - 输入验证: 同样是纵深防御的一部分。
- 使用参数化查询 (命名参数或位置参数): 这是 Hibernate/JPA 防 SQL 注入的核心方法。
- 角色:
总结与关键防护原则
- 首要原则:参数化查询 (Parameterized Queries / Prepared Statements): 无论使用 JDBC、MyBatis、Hibernate 还是 JPA,将用户输入作为查询参数绑定(使用
?/:name/#{}),而不是直接拼接到 SQL/HQL/JPQL 语句中,是防御 SQL 注入最根本、最有效的手段。数据库引擎会区分代码和数据。 - 理解占位符语义:
- 安全 (
?,:name,#{},setParameter()): 表示参数绑定。 - 危险 (字符串拼接,
${}): 表示直接文本替换。
- 安全 (
- ORM 不是免死金牌: Hibernate/JPA 等 ORM 框架本身不自动防止 SQL 注入。开发者错误地拼接查询字符串或不安全地使用原生 SQL 时,风险依然存在。必须正确使用其参数化机制。
- 最小权限原则: 应用程序连接数据库的用户应只拥有完成其功能所必需的最小权限。避免使用具有 DBA 权限的账号。这样即使发生注入,损害也能被限制。
- 纵深防御:
- 输入验证与过滤: 在数据进入持久层之前,进行严格的格式、类型、长度、范围校验。使用白名单优于黑名单。
- 输出编码: 在 Web 层,对从数据库取出并显示给用户的数据进行适当的输出编码,防止 XSS 等二次攻击(虽然不直接防 SQL 注入,但提升整体安全)。
- 使用安全框架: 考虑集成 Spring Security 等框架,它们提供额外的安全层和工具。
- 定期安全审计与渗透测试: 使用自动化工具(如 OWASP ZAP, SQLMap)和人工审计检查应用漏洞。
- 依赖库更新: 保持 ORM 框架、数据库驱动等依赖库更新到最新版本,以修复已知安全漏洞。
- 日志与监控: 记录数据库操作日志,监控异常查询模式。
结论:
图片中指出的 JDBC、MyBatis、Hibernate、JPA 这四点,代表了 Java 中进行数据库交互的不同层次和技术。它们都存在 SQL 注入的风险,风险根源都在于开发者不安全地将用户输入拼接到了查询语句中。 防范的关键在于始终如一地、正确地使用参数化查询机制(PreparedStatement, MyBatis #{}, Hibernate/JPA setParameter()),并辅以输入验证、最小权限等纵深防御措施。理解每种技术中参数占位符的正确用法和安全边界至关重要。安全编码意识和实践是防御 SQL 注入的核心。
靶场定位后端源码

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



-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 文档中构造恶意的外部实体引用,可以导致:
- 读取服务器本地文件(如
/etc/passwd)- 发起 SSRF 攻击(服务器端请求伪造)
- 造成拒绝服务(DoS)
- 端口扫描内网服务
二、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 解析器,都应采取以下防护措施:
禁用 DTD 完全(最彻底方案):
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);禁用外部实体(如果必须允许 DTD):
factory.setFeature("http://xml.org/sax/features/external-general-entities", false); factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);启用安全处理模式:
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, "");四、代码审计要点
- 查找所有 XML 解析点:搜索上述12个类的使用情况
- 检查安全配置:确认是否设置了防护措施
- 关注输入来源:特别检查用户可控的 XML 输入
- 注意间接解析:如 WebService、SOAP、REST API 等可能隐式解析 XML
五、防御深度策略
- 输入过滤:对用户输入的 XML 进行严格校验
- 输出编码:对 XML 输出内容进行适当编码
- 最小权限:运行解析器的账户应具有最小权限
- 依赖更新:保持 XML 解析库最新版本
- 日志监控:记录异常解析行为
六、总结
理解 XXE 注入的关键在于:
- 识别所有 XML 解析点:Java 中有多种 XML 解析方式,都需要防护
- 控制外部实体处理:禁用或严格限制外部实体解析
- 纵深防御:从输入到输出多层防护
通过系统性地审计和加固这些 XML 处理点,可以有效防御 XXE 注入攻击。
对于靶场中

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配置文件