审计流程
Servlet
生命周期
Servlet
生命周期可被定义为从创建直到毁灭的整个过程。以下是Servlet
遵循的过程:
Servlet
初始化后调用 init () 方法。
Servlet
调用 service() 方法来处理客户端的请求。
Servlet
销毁前调用 destroy() 方法。
最后,Servlet
是由JVM
的垃圾回收器进行垃圾回收的。
简单介绍一下使用到的几种方法。
init
方法被设计成只调用一次。它在第一次创建Servlet
时被调用,在后续每次用户请求时不再调用。因此,它是用于一次性初始化。
service()
方法是执行实际任务的主要方法。Servlet
容器(即Web
服务器)调用service()
方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。
每次服务器接收到一个Servlet
请求时,服务器会产生一个新的线程并调用服务。service()
方法检查HTTP
请求类型(GET、POST、PUT、DELETE
等),并在适当的时候调用doGet、doPost、doPut,doDelete
等方法
destroy()
方法只会被调用一次,在Servlet
生命周期结束时被调用。destroy()
方法可以让您的Servlet
关闭数据库连接、停止后台线程、把Cookie
列表或点击计数器写入到磁盘,并执行其他类似的清理活动。
Servlet审计
找Controller
文件,找mapping
路由。
…
Springboot审计
找Controller
再找mapping
路由。
…
SQL注入
Java
中常见的连接数据库的方式:Mybatis
、JDBC
、Hibernate
等。在Java
中存在注入的参数类型要为字符型。
JDBC
中的SQL注入
JDBC
中常用的几个执行SQL
语句的方式,分别为:
Statement
:用于执行普通的SQL
语句,不带有参数。
PrepareedStatement
:用于执行预编译的SQL
语句,可以带有参数,防止SQL
注入。
CallableStatement
:用于执行数据库存储过程。
动态拼接参数
直接将参数拼接到了SQL
语句中执行。
1 2 3 Stringsql="select * from users where id = '" + id +"'" ; ResultSetrs= statement.executeQuery(sql);
错误使用预编译
原理:PreparedStatement
不会对拼接的字符串进行预处理。
正确的使用预编译,不存在注入。
1 2 3 4 5 Stringsql="select * from users where id = ?" ; PreparedStatementpreparestatement= conn.prepareStatement(sql); preparestatement.setString(1 , username); ResultSetrs= preparestatement.executeQuery();
错误使用预编译,存在注入!!!
1 2 3 4 Stringsql="select * from users where id = '" + id +"'" ; PreparedStatementpreparestatement= conn.prepareStatement(sql); ResultSetrs= preparestatement.executeQuery();
简单说,由于开发人员疏忽或经验不足等原因,虽然使用了预编译 PreparedStatement
,但没有根据标准流程对参数进行标记,依旧使用了动态拼接SQL语句的方式,进而造成SQL注入漏洞。
Order By
注入
在使用PreparedStatement
预编译时,会将传递的任意参数预编译,进而变为字符串。而order by
需要的是字段名 ,而不是字符串 。所以,在使用order by
时,就不能使用预编译了。因此,order by
位置也是出现sql
注入重灾区。
字段名 是SQL
语句的一部分,而字符串不是。预编译后SQL
语句就会变成ORDER BY 'username'
,故报错。
1 2 3 4 5 Stringsql="SELECT * FROM users ORDER BY ?" ; PreparedStatementpreparestatement= conn.prepareStatement(sql); preparestatement.setString(1 , username); ResultSetrs= preparestatement.executeQuery();
所以,代码中常常使用的方法是拼接字段,拼接不当就会出现注入问题。
1 2 3 4 Stringsql="select * from users" +" order by " + id; PreparedStatementpreparestatement= conn.prepareStatement(sql); ResultSetrs= preparestatement.executeQuery();
实际代码应用中,常常使用白名单的方式去限制字段名 ,进而防止注入。
Mybatis
中的SQL
注入
MyBatis
是一个流行的 Java
持久层框架,用于简化与数据库的交互。它通过提供一种对象关系映射(ORM
)的方式,使得 Java
程序员可以更方便地执行数据库操作(如查询、插入、更新、删除)。与传统的 JDBC
相比,MyBatis
提供了更高层次的抽象,并且比 JPA
(Hibernate
等)更灵活、易于配置。
MyBatis
基础知识
MyBatis
主要特点
SQL映射: MyBatis
允许开发者使用原生的SQL
语句来操作数据库。它将SQL
查询与Java
方法进行映射,使得开发者可以完全控制SQL
执行过程,同时享受框架带来的简化和支持。
动态SQL: MyBatis
支持动态生成SQL
查询,提供了<if>
、<choose>
、<foreach>
等标签,可以根据不同的条件动态生成SQL
,避免了冗长且复杂的条件判断逻辑。
简洁的XML配置: MyBatis
使用 XML
文件来配置数据库映射,开发者通过定义 SQL
语句和映射规则,控制 Java
对象与数据库表之间的映射关系。
接口和注解支持: MyBatis
提供了基于接口的API
,开发者可以在 Java
接口中定义数据库操作方法,并通过注解或者 XML
配置映射 SQL
语句。
强大的缓存机制: MyBatis
提供了一级缓存和二级缓存。一级缓存是基于 SqlSession
级别的缓存,二级缓存是跨 SqlSession
共享的缓存,默认情况下启用。
支持事物管理: MyBatis
支持与 Java
的事务管理整合,确保数据库操作的原子性和一致性。
Mybatis
的工作原理
**SqlSessionFactory
:**这是 MyBatis 的核心工厂类,负责创建 SqlSession
对象。SqlSession
是 MyBatis 中用于执行 SQL
语句的主要接口,它通过配置文件读取数据库连接信息,创建并维护数据库连接。
**Mapper XML
文件:**在MyBatis
中,SQL
语句通常在XML
配置文件中定义,开发者需要为每个数据库操作编写对应的Mapper
文件。这个文件会包含SQL
语句及其映射规则。
**Mapper
接口:**开发者通常会创建接口,定义一些方法,这些方法通过XML
或注解来与具体的SQL
语句进行绑定。
**映射过程:**当应用程序调用 SqlSession
的方法时,MyBatis
会根据 Mapper
接口或注解提供的SQL
语句信息执行相应的数据库操作,并将结果自动映射为Java
对象,返回给开发者。
Mybatis
的使用步骤
配置Mybatis
: 通过mybatis-config.xml
文件进行配置,配置数据库连接、缓存、日志等。
**定义Mapper
: ** 创建Mapper
接口或类,定义SQL
操作的方法。
编写Mapper XML
: 为每个SQL
操作编写对应的XML
文件,定义SQL
语句与Mapper
方法的映射关
创建SqlSessionFactory
: 通过配置文件创建 SqlSessionFactory
,获取 SqlSession
。
**执行SQL
操作:**通过SqlSession
执行SQL
语句,获取查询结果或执行更新操作。
MyBatis
的代码示例
以下是一个简单的MyBatis
的使用示例。
MyBatis
配置文件mybatis-config.xml
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="UTF-8" ?> <configuration > <settings > <setting name ="logImpl" value ="STDOUT_LOGGING" /> </settings > <mappers > <mapper resource ="com/example/mapper/UserMapper.xml" /> </mappers > </configuration >
Mapper 接口 UserMapper.java
1 2 3 4 public interface UserMapper { User getUserById (int id) ; List<User> getAllUsers () ; }
Mapper XML 文件 UserMapper.xml
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="UTF-8" ?> <mapper namespace ="com.example.mapper.UserMapper" > <select id ="getUserById" resultType ="com.example.model.User" > SELECT id, name, email FROM users WHEREid=#{id} </select > <select id ="getAllUsers" resultType ="com.example.model.User" > SELECT id, name, email FROM users </select > </mapper >
Java 主程序 Main.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;publicclassMain{ publicstaticvoidmain(String[] args){ SqlSessionFactorysqlSessionFactory= newSqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml" )); try (SqlSessionsession= sqlSessionFactory.openSession()){ UserMapperuserMapper= session.getMapper(UserMapper.class); Useruser= userMapper.getUserById(1 ); System.out.println(user); } } }
MyBatis
中#{}和${}区别
在Mybatis
中拼接SQL
语句有两种方式:一种是占位符 #{}
,另一种是拼接符${}
。
对于#{}
:
#{}
占位符用于预编译参数绑定 ,MyBatis
会把 #{}
中的值作为参数 传递给数据库查询或更新语句,在执行 SQL
时由数据库驱动进行处理。这样做的好处是,参数会被视为数据,进行转义和预编译 ,不会直接拼接到SQL
语句中,防止了SQL
注入攻击。
使用场景: #{}
适用于所有情况,尤其是对于动态条件、查询参数等。比如查询条件、更新值等。
示例:
查询的xml
1 2 3 <select id ="getUserById" resultType ="com.example.User" > SELECT * FROM users WHERE id = #{id} </select >
对应的Java
代码
1 User user = userMapper.getUserById(1 );
哪么最后在执行时,MyBatis
会将 id
的值 1 绑定到 SQL
查询中,生成的SQL
语句会是:
1 SELECT * FROM users WHERE id = ?
如果传入的数值为1,那么最终会被解析成
1 SELECT * FROM users WHERE id = "1"
对于${}
:
${}
用于直接拼接字符串 。MyBatis
在解析 SQL
时,${}
不会对传入的参数不做处理,而是直接拼接,MyBatis
会把 ${}
中的内容直接替换为对应的参数值,而不进行任何转义或预处理 ,进而会造成SQL
注入漏洞。
使用场景 :${}
一般用于动态表名、列名、排序字段等场景,通常情况下不应将用户输入的参数直接传入 ${}
中,以防止SQL
注入。
存在SQL
注入漏洞的${}
假设我们允许用户通过输入标名查询数据,并且直接用${}
来拼接SQL
:
Mapper XML
文件:
1 2 3 <select id ="findByTable" resultType ="com.example.User" > SELECT * FROM ${tableName} WHERE id = #{id} </select >
这里的tableName
为SQL
语句的一部分,直接拼接可导致SQL
注入问题。假设注入的数据为users;select database();-- -
,查询数据库的名称。
1 SELECT * FROM users;select database();
不存在SQL
注入的${}
用法
Mapper XML
文件
1 2 3 <select id ="findByColumn" resultType ="com.example.User" > SELECT * FROM users WHERE ${column} = #{value} </select >
对应的Java
代码
1 2 3 String column = "age" ; int value = 25 ; List<User> users = userMapper.findByColumn(column, value);
最后在执行时,MyBatis
会将 ${column}
替换成 age
,而 #{value}
仍然会用 25
作为参数绑定。最终生成的 SQL 语句为:
1 SELECT * FROM users WHERE age = ?
总之,防止SQL
注入的关键点是,作为SQL
语句中的**字段部分(为SQL语句的一部分,不使用预编译)**不是用户可以任意控制的,这样就可以有效的防止SQL
注入了。例如,上述的案例中age
是写死的或者写一个白名单数组,只允许传入白名单数组内的内容,这样用户不可任意控制了也就防止了SQL
注入。
ORDER BY
注入
ORDER BY
语句 :用于对查询结果的排序,asc
为升序,desc
为降序。默认为升序。
比如:
1 select * from users order by username asc ;
JDBC
中的ORDER BY
注入问题,前面已经说过了。
与JDBC
预编译中ORDER BY
注入一样,在ORDER BY
语句后面需要是字段名或者字段位置。因此也不能使用Mybatis
中预编译的方式。
存在注入的ORDER BY
查询,如下UserMapper.xml
,这里的sort
要可控为任意字符。
1 2 3 <select id ="orderbysqli" parameterType ="String" resultMap ="User" > select * from users order by ${sort} asc </select >
Java
代码如下:
1 2 3 4 @GetMapping("/mybatis/orderby") public List<User> orderbySql (@RequestParam("sort") String sort) { return userMapper.orderbysqli(sort); }
IN
注入
IN
语句:常用于where
表达式中,其作用是查询某个范围内的数据。
比如:
1 select * from where field in (value1,value2,value3,…);
如上所示,IN
在查询某个范围数据是会用到多个参数,在Mybtis
中如果直接使用占位符 #{}
进行查询会将这些参数看做一个整体,查询会报错 。 因此很多开发人员可能会使用拼接符${}
对参数进行查询,从而造成了SQL注入漏洞。
例如:
1 select * from users where id in (${params})
正确的做法是需要使用foreach
配合占位符 #{}
实现IN
查询。比如:
1 2 3 4 5 6 7 8 </select > <select id ="findUsers" resultType ="User" > SELECT * FROM users WHERE id IN <foreach item ="id" collection ="ids" open ="(" separator ="," close =")" > #{id} </foreach > </select >
存在SQL
注入漏洞的代码。
1 2 3 <select id ="findUsers" resultType ="User" > SELECT * FROM users WHERE id IN (${ids}) </select >
对应的Java
代码如下:
1 2 3 4 @GetMapping("/mybatis/injection") publicList<User>INuser(@RequestParam("id") String id){ return userMapper.INuser(id); }
LIKE
注入
LIKE
子句用于在 SQL
查询中进行模糊匹配。当使用用户输入的内容作为 LIKE
条件时,应该非常小心,因为恶意输入可能导致SQL
注入。使用LIKE
语句进行查询时如果使用占位符#{}
查询时程序会报错。
因此经验不足的开发人员可能会直接使用拼接符 ${}
对参数进行查询,从而造成了SQL注入漏洞。
这里username
任意可控,存在SQL
注入的代码。
1 2 3 <select id ="like2" parameterType ="String" resultMap ="User" > select * from users where username like '%${username}%' </select >
正确的用法,不存在SQL
注入漏洞。
1 2 3 <select id ="getUserListLikeConcat" resultType ="org.example.User" > SELECT * FROM user WHERE name LIKE concat ('%', #{name}, '%') </select >
或者
1 2 3 4 5 <select id ="getUserListLike" resultType ="org.example.User" > <bind name ="pattern" value ="'%' + name + '%'" /> SELECT * FROM user WHERE name LIKE #{pattern} </select >
SQL
相关函数点
看了前面的内容,我们可以知道,代码存在SQL
注入,其实本质就是将用户的输入当作了SQL
语句的字段内容去执行了。想要发现SQL
注入漏洞,就要注意在代码中进行SQL
语句操作的位置点,仔细查看代码是否正常使用预编译或者是否过滤完全。
下面给出了涉及SQL
语句操作的词语,通过这些词可以快速定位代码中的操作SQL
的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Statement createStatement PrepareStatement like '%${ in(${ in (${ select update insert delete ${ setObject( setInt( setString( setSQLXML( createQuery( createSQLQuery( createNativeQuery(
XXE
常见的XXE
的payload
如下:
测试回显的XXE
1 2 3 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE x [ <!ENTITY xxe SYSTEM "http://dnslog.com/" > ]> <x > &xxe; </x >
无回显XXE
1 2 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE root [<!ENTITY % xxe SYSTEM "https://dnslog.com" > %xxe;]>
漏洞相关库
常见的可能存在XXE
的相关包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 javax.xml.parsers.DocumentBuilder javax.xml.parsers.SAXParser javax.xml.parsers.SAXParserFactory javax.xml.transform.TransformerFactory javax.xml.validation.Validator javax.xml.validation.SchemaFactory javax.xml.transform.sax.SAXTransformerFactory javax.xml.transform.sax.SAXSource org.xml.sax.XMLReader org.xml.sax.helpers.XMLReaderFactory org.dom4j.io.SAXReader org.jdom.input.SAXBuilder org.jdom2.input.SAXBuilder javax.xml.bind.Unmarshaller javax.xml.xpath.XpathExpression javax.xml.stream.XMLStreamReader org.apache.commons.digester3.Digester
测试实例
org.xml.sax.XMLReader
存在XXE
漏洞的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.x2n.vultest;import org.xml.sax.InputSource;import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;public class XMLReaderXXE { public static void main (String[] args) { try { XMLReader xmlReader = XMLReaderFactory.createXMLReader(); String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE x [ <!ENTITY xxe SYSTEM \"http://dnslog.cn\"> ]>\n" + "<x>&xxe;</x>" ; InputSource inputSource = new InputSource (); inputSource.setCharacterStream(new java .io.StringReader(xml)); xmlReader.parse(inputSource); } catch (Exception e) { e.printStackTrace(); } } }
不存在XXE
漏洞,参考前辈文章
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 XMLReader xmlReader = XMLReaderFactory.createXMLReader();xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl" , true ); xmlReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd" , false ); xmlReader.setFeature("http://xml.org/sax/features/external-general-entities" , false ); xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE x [ <!ENTITY xxe SYSTEM \"http://dnslog.cn\"> ]>\n" + "<x>&xxe;</x>" ; InputSource inputSource = new InputSource ();inputSource.setCharacterStream(new java .io.StringReader(xml)); xmlReader.parse(inputSource);
这么一看,其实修复XXE
漏洞很简单,只要加上setFeature
即可。反之,审计的时候,定位相关的xml
处理函数,查看是否进行了setFeature
操作,即可得到是否存在XXE
漏洞。
SAXBuilder
如果使用默认配置就会触发XXE
漏洞。
存在XXE
漏洞的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.x2n.vultest;import org.jdom2.input.SAXBuilder;import java.io.StringReader;public class SAXBuilderXXE { public static void main (String[] args) { try { String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE x [ <!ENTITY xxe SYSTEM \"http://dnslog.cn\"> ]>\n" + "<x>&xxe;</x>" ; SAXBuilder builder = new SAXBuilder (); builder.build(new StringReader (xml)); } catch (Exception e) { e.printStackTrace(); } } }
pom.xml
中引入依赖
1 2 3 4 5 <dependency > <groupId > org.jdom</groupId > <artifactId > jdom2</artifactId > <version > 2.0.6</version > </dependency >
不存在XXE
漏洞的情况。
1 2 SAXBuilder builder = new SAXBuilder (true );Document doc = builder.build(InputSource);
1 2 3 4 5 6 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 ); builder.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); builder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd" , false ); Document doc = builder.build(InputSource);
org.dom4j.io.SAXReader
在默认情况下会出现XXE
漏洞。
1 2 SAXReader saxReader = new SAXReader(); saxReader.read(InputSource);
不存在XXE
漏洞的情况。
1 2 3 4 5 6 SAXReader saxReader = new SAXReader ();saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl" , true ); saxReader.setFeature("http://xml.org/sax/features/external-general-entities" , false ); saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd" , false ); saxReader.read(InputSource);
javax.xml.parsers.DocumentBuilder
存在XXE
漏洞的测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.x2n.vultest;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import org.w3c.dom.Document;import org.w3c.dom.Element;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import javax.xml.parsers.ParserConfigurationException;import java.io.IOException;import java.io.StringReader;import org.xml.sax.InputSource;import org.xml.sax.SAXException;public class DocumentBuilderFactoryXXE { public static void main (String[] args) { try { String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE x [ <!ENTITY xxe SYSTEM \"http://dnslog.cn\"> ]>\n" + "<x>&xxe;</x>" ; DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dbf.newDocumentBuilder(); Document document = builder.parse(new InputSource (new StringReader (xml))); document.getDocumentElement().normalize(); Element root = document.getDocumentElement(); System.out.println("Root element: " + root.getTagName()); NodeList nodeList = root.getChildNodes(); for (int i = 0 ; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { System.out.println("Node: " + node.getNodeName() + " , Text: " + node.getTextContent()); } } } catch (IOException e) { throw new RuntimeException (e); } catch (ParserConfigurationException e) { throw new RuntimeException (e); } catch (SAXException e) { throw new RuntimeException (e); } } }
错误的修复。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();DocumentBuilder builder = dbf.newDocumentBuilder();String FEATURE = null ;FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing" ; dbf.setFeature(FEATURE, true ); FEATURE = "http://apache.org/xml/features/disallow-doctype-decl" ; dbf.setFeature(FEATURE, true ); FEATURE = "http://xml.org/sax/features/external-parameter-entities" ; dbf.setFeature(FEATURE, false ); FEATURE = "http://xml.org/sax/features/external-general-entities" ; dbf.setFeature(FEATURE, false ); FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd" ; dbf.setFeature(FEATURE, false ); dbf.setXIncludeAware(false ); dbf.setExpandEntityReferences(false ); FileInputStream fis = new FileInputStream ("path/to/xxexml" );InputSource is = new InputSource (fis);builder.parse(is);
看似设置得很很全面,但是直接仍然会被攻击,原因就是在于DocumentBuilder builder = dbf.newDocumentBuilder();
这行代码需要在dbf.setFeature()
之后才能够生效;⚠️⚠️⚠️
正确的修复
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();String FEATURE = null ;FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing" ; dbf.setFeature(FEATURE, true ); FEATURE = "http://apache.org/xml/features/disallow-doctype-decl" ; dbf.setFeature(FEATURE, true ); FEATURE = "http://xml.org/sax/features/external-parameter-entities" ; dbf.setFeature(FEATURE, false ); FEATURE = "http://xml.org/sax/features/external-general-entities" ; dbf.setFeature(FEATURE, false ); FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd" ; dbf.setFeature(FEATURE, false ); dbf.setXIncludeAware(false ); dbf.setExpandEntityReferences(false ); DocumentBuilder builder = dbf.newDocumentBuilder();FileInputStream fis = new FileInputStream ("path/to/xxexml" );InputSource is = new InputSource (fis);Document doc = builder.parse(is);
注意DocumentBuilder builder = dbf.newDocumentBuilder();
在两种不同的位置的差异性。
其它同理
通过对不同的XML
解析库的修复方式可以发现,XXE
的防护只需要限制带外实体的注入就可以了,修复方式也简单,需要设置几个选项为false
即可,可能少许的几个库可能还需要设置一些其他的配置,但是都是类似的。
总体来说修复方式都是通过setFeature
的方式来防御XXE
。
1 2 3 4 "http://apache.org/xml/features/disallow-doctype-decl", true "http://apache.org/xml/features/nonvalidating/load-external-dtd", false "http://xml.org/sax/features/external-general-entities", false "http://xml.org/sax/features/external-parameter-entities", false
1 2 XMLConstants.ACCESS_EXTERNAL_DTD, "" XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""
本质上XXE
的问题就是一个配置不当的问题,即容易发现也容易防御,但是前提是需要知道这个漏洞。很多程序员并不知道XXE
,所以才会写出含有漏洞的代码。
SSRF
SSRF
漏洞其实就是代码中有一个可以访问内网的接口,用户可以通过控制这个请求的接口,进而访问内网。SSRF
不只是仅限于代码中请求URL
的函数,XXE
也可以作为SSRF
的接口的,还有一些代码可以解析xml
,html
等文件,通过控制这些文件的内容也是可以实现SSRF
。
在Java
中支持的协议类型
1 http https file ftp mailto jar netdoc
可能存在问题的函数
代码中常用的可能存在SSRF
的函数如下:
1 2 3 4 5 6 7 HttpURLConnection.getInputStream URLConnection.getInputStream HttpVlient.execute Request.Get.execute Request.Post.execute URL.openStream ImageIO.read
根据函数名称,快速定位函数,分析函数的参数是否可控,进而确认是否存在SSRF
漏洞。
测试实例
就是URL
可控就行了,比较简单。有时候会存在对URL
过滤的情况,这时候需要进行绕过,可以参考我之前写的SSRF
绕过内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.x2n.ssrf;import java.io.BufferedReader;import java.io.InputStreamReader;import java.net.HttpURLConnection;import java.net.URL;public class HttpURLConnectionSSRF { public static void main (String[] args) { try { String targetUrl = "http://127.0.0.1" ; URL url = new URL (targetUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); try (BufferedReader in = new BufferedReader (new InputStreamReader (connection.getInputStream()))){ String inputLine; StringBuilder content = new StringBuilder (); while ((inputLine = in.readLine()) != null ){ content.append(inputLine); } System.out.println(content.toString()); } connection.disconnect(); }catch (Exception e){ e.printStackTrace(); } } }
HttpCLient.execute
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package com.x2n.ssrf;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpGet;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;public class HttpClientSSRF { public static void main (String[] args) throws IOException { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpGet http = new HttpGet ("http://127.0.0.1:8787" ); try (CloseableHttpResponse response = httpClient.execute(http)) { if (response.getStatusLine().getStatusCode()==200 ){ BufferedReader reader = new BufferedReader (new InputStreamReader (response.getEntity().getContent())); String line; StringBuilder body = new StringBuilder (); while ((line = reader.readLine()) != null ){ body.append(line); } System.out.println("response body: " +body.toString()); }else { System.out.println("request failed: " +response.getStatusLine().getStatusCode()); } }catch (Exception e) { e.printStackTrace(); } } } }
其它同理
比较简单,这里就不再举例。
RCE
Java
中的RCE
中的方式两种:
aaa
参考