XML外部实体缺陷 - 腾讯安全代码审计实战系列03
XML外部实体缺陷 - 腾讯安全代码审计实战系列03 XML用于标记电子文件使其具有结构性的标记语言, 可以用来标记数据, 定义数据类型, 是一种允许用户对自己的标记语言进行定义的源语言. XML文档结构包括XML声明, DTD文档类型定义(可选), 文档元素. DTD的作用是定义XML文档的合法构建模块, DTD可以在XML文档内声明, 也可以外部引用. 当应用程序允许XML引用外部实体时, 通过构造恶意内容, 可导致XXE漏洞。
危害如下: 1. 读取任意文件 2. 执行系统命令 3. 探测内网端口 4. 攻击内网网站
修复建议
使用开发语言提供的禁用外部实体的方法。确保在解析XML前禁用DTD(文档类型定义)和禁止外部实体的解析。
过滤用户提交的XML数据,特别是对于嵌入XML或DTD的输入,进行严格的验证和过滤,确保它们不包含对外部实体或不安全的结构的引用。
示例代码 Java代码示例:
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 48 49 50 @RequestMapping("/xxe") public void test (String xmlstr) throws IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { InputStream is = new ByteArrayInputStream (xmlstr.getBytes()); DocumentBuilder builder = factory.newDocumentBuilder(); builder.parse(is); } catch (Exception e) { e.printStackTrace(); } } @RequestMapping("/no_xxe") public void test (String xmlstr) throws IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl" , true ); factory.setFeature("http://xml.org/sax/features/external-general-entities" , false ); factory.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); InputStream is = new ByteArrayInputStream (xmlstr.getBytes()); DocumentBuilder builder = factory.newDocumentBuilder(); builder.parse(is); } catch (Exception e) { e.printStackTrace(); } } @RequestMapping("/validate_xxe") public void validateAndParse (String xmlstr) throws IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { if (!isValidXML(xmlstr)) { throw new IOException ("Invalid XML content." ); } factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl" , true ); factory.setFeature("http://xml.org/sax/features/external-general-entities" , false ); factory.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); InputStream is = new ByteArrayInputStream (xmlstr.getBytes()); DocumentBuilder builder = factory.newDocumentBuilder(); builder.parse(is); } catch (Exception e) { e.printStackTrace(); } }
Go代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func unsafeParseXML (data []byte ) { xmlReader := bytes.NewReader(data) decoder := xml.NewDecoder(xmlReader) var target interface {} if err := decoder.Decode(&target); err != nil { log.Println("Error decoding XML:" , err) } } func safeParseXML (data []byte ) { xmlReader := bytes.NewReader(data) decoder := xml.NewDecoder(xmlReader) decoder.Strict = false decoder.Entity = nil var target interface {} if err := decoder.Decode(&target); err != nil { log.Println("Error decoding XML:" , err) } }
PHP代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function unsafeXMLParse ($xmlContent ) { $xml = simplexml_load_string ($xmlContent ); } libxml_disable_entity_loader (true );$xml = simplexml_load_string ($xmlContent );function safeXMLParse ($xmlContent ) { libxml_disable_entity_loader (true ); $dom = new DOMDocument (); $dom ->loadXML ($xmlContent , LIBXML_NOENT | LIBXML_DTDLOAD); }
Python代码示例:
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 from xml.etree.ElementTree import parsedef unsafe_parse_xml (xml_file ): tree = parse(xml_file) root = tree.getroot() return root from lxml import etreedef incorrect_parse_xml (xml_file ): parser = etree.XMLParser() tree = etree.parse(xml_file, parser) root = tree.getroot() return root from lxml import etreedef safe_parse_xml (xml_file ): parser = etree.XMLParser(resolve_entities=False ) tree = etree.parse(xml_file, parser) root = tree.getroot() return root from lxml import etreedef parse_xml (xmlSource ): xmlData = etree.parse(xmlSource, etree.XMLParser(resolve_entities=False )) return xmlData.getroot()
JavaScript代码示例:
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 const xml2js = require ('xml2js' );const fs = require ('fs' );function unsafeParseXML (xmlData ) { const parser = new xml2js.Parser (); parser.parseString (xmlData, (err, result ) => { console .log (result); }); } function safeParseXML (xmlData ) { const parser = new xml2js.Parser ({ explicitRoot : false , ignoreAttrs : true , explicitArray : false , dtd : { external : false } }); parser.parseString (xmlData, (err, result ) => { if (err) { console .error ("Failed to parse XML:" , err); return ; } console .log ("Safely parsed XML:" , result); }); } const xmlData = fs.readFileSync ('example.xml' , 'utf8' );unsafeParseXML (xmlData);safeParseXML (xmlData);
WebGoat-main/src/main/java/org/owasp/webgoat/lessons/xxe/CommentsCache.java
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 @Component @Scope("singleton") public class CommentsCache { static class Comments extends ArrayList <Comment> { void sort () { sort(Comparator.comparing(Comment::getDateTime).reversed()); } } private static final Comments comments = new Comments (); private static final Map<WebGoatUser, Comments> userComments = new HashMap <>(); private static final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd, HH:mm:ss" ); public CommentsCache () { initDefaultComments(); } void initDefaultComments () { comments.add(new Comment ("webgoat" , LocalDateTime.now().format(fmt), "Silly cat...." )); comments.add( new Comment ( "guest" , LocalDateTime.now().format(fmt), "I think I will use this picture in one of my projects." )); comments.add(new Comment ("guest" , LocalDateTime.now().format(fmt), "Lol!! :-)." )); } protected Comments getComments (WebGoatUser user) { Comments allComments = new Comments (); Comments commentsByUser = userComments.get(user); if (commentsByUser != null ) { allComments.addAll(commentsByUser); } allComments.addAll(comments); allComments.sort(); return allComments; } protected Comment parseXml (String xml, boolean securityEnabled) throws XMLStreamException, JAXBException { var jc = JAXBContext.newInstance(Comment.class); var xif = XMLInputFactory.newInstance(); if (securityEnabled) { xif.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "" ); xif.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "" ); } var xsr = xif.createXMLStreamReader(new StringReader (xml)); var unmarshaller = jc.createUnmarshaller(); return (Comment) unmarshaller.unmarshal(xsr); } public void addComment (Comment comment, WebGoatUser user, boolean visibleForAllUsers) { comment.setDateTime(LocalDateTime.now().format(fmt)); comment.setUser(user.getUsername()); if (visibleForAllUsers) { comments.add(comment); } else { var comments = userComments.getOrDefault(user.getUsername(), new Comments ()); comments.add(comment); userComments.put(user, comments); } } public void reset (WebGoatUser user) { comments.clear(); userComments.remove(user); initDefaultComments(); } }