审计流程
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
参考