Les 10 risques de sécurité les plus critiques dans les systèmes de machine learning et comment les atténuer.
L'OWASP Machine Learning Security Top 10 identifie les risques de sécurité les plus significatifs spécifiques aux systèmes de machine learning. Contrairement aux logiciels traditionnels, les systèmes ML sont vulnérables à des attaques uniques ciblant les données d'entraînement, les composants internes du modèle et les pipelines d'inférence. Ce guide couvre les attaques adversariales, l'empoisonnement des données, le vol de modèle, les risques de la chaîne d'approvisionnement, et bien plus Eavec des exemples de code Python pratiques.
Les attaques par manipulation d'entrée (attaques adversariales) créent des entrées spécialement conçues pour forcer les modèles ML à faire des prédictions incorrectes. De petites perturbations, souvent imperceptibles, sur des images, du texte ou d'autres entrées peuvent tromper les classificateurs, contourner les systèmes de détection et échapper aux filtres de contenu. Il s'agit du vecteur d'attaque le plus connu spécifique au ML.
Les exemples adversariaux peuvent contourner des systèmes ML critiques pour la sécurité : perception des véhicules autonomes, détection de malwares, détection de fraude et modération de contenu. Un attaquant peut faire en sorte qu'un panneau stop soit classifié comme un panneau de limitation de vitesse, ou rendre un malware apparemment bénin pour un antivirus basé sur ML.
import numpy as np from tensorflow import keras # Model with no adversarial robustness model = keras.models.load_model("classifier.h5") def predict(image): # Direct prediction Eno input validation or preprocessing result = model.predict(np.expand_dims(image, axis=0)) return np.argmax(result) # No confidence threshold check # No input bounds validation # Vulnerable to FGSM, PGD, C&W attacks
import numpy as np from tensorflow import keras from art.defences.preprocessor import SpatialSmoothing from art.defences.detector.evasion import BinaryInputDetector # Load adversarially trained model model = keras.models.load_model("classifier_robust.h5") # Input preprocessing to remove perturbations smoother = SpatialSmoothing(window_size=3) detector = BinaryInputDetector(model) def predict_secure(image): # Validate input bounds if image.min() < 0 or image.max() > 1: raise ValueError("Input out of expected range") # Detect adversarial input if detector.detect(image): raise ValueError("Adversarial input detected") # Apply spatial smoothing defense cleaned = smoother(image)[0] result = model.predict(np.expand_dims(cleaned, axis=0)) # Reject low-confidence predictions confidence = np.max(result) if confidence < 0.85: return {"label": "uncertain", "confidence": confidence} return {"label": np.argmax(result), "confidence": confidence}
Les attaques par empoisonnement des données injectent des échantillons malveillants dans les ensembles de données d'entraînement pour corrompre le comportement appris du modèle. Les attaquants peuvent introduire des portes dérobées (patterns de déclenchement qui causent des classifications erronées spécifiques), déplacer les frontières de décision ou dégrader la précision globale du modèle. Cela est particulièrement dangereux lorsque les données d'entraînement proviennent d'Internet ou de contenu généré par les utilisateurs.
Un modèle empoisonné peut se comporter normalement sur des entrées propres mais classifier de manière erronée lorsqu'un pattern de déclenchement spécifique est présent. Par exemple, un classificateur de malware avec porte dérobée pourrait approuver tout échantillon de malware contenant une séquence d'octets spécifique. L'attaque est furtive car la précision du modèle sur les données propres reste élevée.
import pandas as pd from sklearn.ensemble import RandomForestClassifier # Training on unvalidated, crowdsourced data data = pd.read_csv("user_submitted_data.csv") # No validation! # No outlier detection or data quality checks X = data.drop("label", axis=1) y = data["label"] model = RandomForestClassifier() model.fit(X, y) # Training directly on untrusted data! # No comparison against clean baseline # No data provenance tracking
import pandas as pd import numpy as np from sklearn.ensemble import RandomForestClassifier, IsolationForest from sklearn.model_selection import cross_val_score # Load data with provenance tracking data = pd.read_csv("training_data.csv") data_hash = hashlib.sha256(data.to_csv().encode()).hexdigest() log.info(f"Training data hash: {data_hash}") X = data.drop("label", axis=1) y = data["label"] # Detect and remove anomalous samples iso_forest = IsolationForest(contamination=0.05, random_state=42) outlier_mask = iso_forest.fit_predict(X) == 1 X_clean, y_clean = X[outlier_mask], y[outlier_mask] log.info(f"Removed {(~outlier_mask).sum()} outliers from {len(X)} samples") # Train and validate against baseline model = RandomForestClassifier(random_state=42) scores = cross_val_score(model, X_clean, y_clean, cv=5) if scores.mean() < BASELINE_ACCURACY - 0.05: raise ValueError("Model accuracy dropped Epossible data poisoning") model.fit(X_clean, y_clean)
Les attaques par inversion de modèle reconstruisent les données d'entraînement sensibles en interrogeant le modèle et en analysant ses sorties. Un attaquant peut récupérer des informations privées telles que des visages, des dossiers médicaux ou des données personnelles utilisées pendant l'entraînement. Cela est particulièrement préoccupant pour les modèles entraînés sur des ensembles de données sensibles (santé, biométrie, données financières).
L'inversion de modèle peut violer les réglementations sur la confidentialité des données (RGPD, HIPAA) en exposant des informations personnellement identifiables provenant des données d'entraînement. Un attaquant avec accès à l'API d'un modèle de reconnaissance faciale pourrait reconstruire les visages d'individus dans l'ensemble d'entraînement.
from flask import Flask, request, jsonify app = Flask(__name__) model = load_model("face_classifier.h5") @app.route("/predict", methods=["POST"]) def predict(): image = request.files["image"] result = model.predict(preprocess(image)) # Returns full probability vector Eenables model inversion! return jsonify({ "probabilities": result.tolist(), # All class probabilities! "prediction": int(np.argmax(result)), "confidence": float(np.max(result)) }) # No rate limiting, no query logging # Unlimited API access for gradient estimation
from flask import Flask, request, jsonify from flask_limiter import Limiter import numpy as np app = Flask(__name__) limiter = Limiter(app, default_limits=["100/hour"]) model = load_model("face_classifier_dp.h5") # Trained with DP @app.route("/predict", methods=["POST"]) @limiter.limit("100/hour") def predict(): image = request.files["image"] result = model.predict(preprocess(image)) # Return only top-1 prediction Eno probability vector prediction = int(np.argmax(result)) log_query(request.remote_addr, prediction) # Audit logging return jsonify({ "prediction": prediction # No probabilities, no confidence scores })
Les attaques par inférence d'appartenance déterminent si un point de données spécifique a été utilisé dans l'ensemble de données d'entraînement du modèle. En analysant les scores de confiance du modèle et son comportement sur des entrées connues vs inconnues, les attaquants peuvent inférer des informations d'appartenance privées. Il s'agit d'une menace significative pour la confidentialité des modèles entraînés sur des données sensibles.
L'inférence d'appartenance peut révéler que les données d'un individu spécifique ont été utilisées pour l'entraînement Epar exemple, confirmer que le dossier d'un patient était dans un ensemble de données cliniques, ou que le visage d'une personne a été utilisé pour l'entraînement à la surveillance. Cela viole les attentes de confidentialité et potentiellement des réglementations comme le RGPD.
from sklearn.neural_network import MLPClassifier # Overfitted model Ememorizes training data model = MLPClassifier( hidden_layer_sizes=(512, 512, 256), # Over-parameterized! max_iter=1000, # No regularization # No early stopping ) model.fit(X_train, y_train) # Model memorizes training data ↁEmembership inference possible # Training accuracy: 99.9% vs Test accuracy: 82% # This gap indicates overfitting = information leakage def predict_with_confidence(x): proba = model.predict_proba([x])[0] return {"probabilities": proba.tolist()} # Leaks membership info!
from sklearn.neural_network import MLPClassifier import numpy as np # Regularized model with early stopping to reduce overfitting model = MLPClassifier( hidden_layer_sizes=(128, 64), max_iter=500, alpha=0.01, # L2 regularization early_stopping=True, # Prevents memorization validation_fraction=0.15, ) model.fit(X_train, y_train) # Verify train/test gap is small (low overfitting) train_acc = model.score(X_train, y_train) test_acc = model.score(X_test, y_test) assert train_acc - test_acc < 0.05, "Overfitting detected!" def predict_secure(x): pred = model.predict([x])[0] return {"prediction": int(pred)} # Label only, no probabilities
Les attaques par vol de modèle (extraction de modèle) créent une copie fonctionnelle d'un modèle ML propriétaire en l'interrogeant systématiquement et en entraînant un modèle de substitution sur les paires entrée-sortie. Le modèle volé peut ensuite être utilisé pour trouver des exemples adversariaux, concurrencer commercialement ou rétro-ingénier les données d'entraînement du modèle.
Un modèle volé représente une perte de propriété intellectuelle et d'avantage concurrentiel. Le modèle extrait peut être utilisé hors ligne pour créer des attaques adversariales ou pour comprendre les frontières de décision. Des millions de dollars d'investissement en entraînement peuvent être reproduits avec des milliers de requêtes API.
from flask import Flask, request, jsonify app = Flask(__name__) model = load_proprietary_model() @app.route("/predict", methods=["POST"]) def predict(): data = request.json["features"] result = model.predict_proba([data])[0] # Returns full probability distribution return jsonify({ "probabilities": result.tolist(), "prediction": int(np.argmax(result)) }) # No rate limiting Eunlimited queries # No anomaly detection on query patterns # Attacker can extract model with ~10K queries
from flask import Flask, request, jsonify from flask_limiter import Limiter import numpy as np app = Flask(__name__) limiter = Limiter(app, default_limits=["50/hour"]) # Watermarked model for theft detection model = load_watermarked_model() query_monitor = QueryPatternDetector() @app.route("/predict", methods=["POST"]) @limiter.limit("50/hour") def predict(): data = request.json["features"] api_key = request.headers.get("X-API-Key") # Detect extraction patterns (uniform sampling, grid queries) if query_monitor.is_suspicious(api_key, data): log_alert(f"Possible extraction: {api_key}") return jsonify({"error": "rate limited"}), 429 result = model.predict([data])[0] return jsonify({ "prediction": int(result) # Label only, no probabilities })
Les attaques de la chaîne d'approvisionnement IA ciblent le pipeline de développement ML : modèles pré-entraînés provenant de hubs de modèles, ensembles de données tiers, frameworks ML et dépendances. Les modèles malveillants peuvent contenir des portes dérobées cachées, et les bibliothèques compromises peuvent injecter des vulnérabilités. Les formats de sérialisation utilisés par les frameworks ML (Pickle, SavedModel) peuvent exécuter du code arbitraire au chargement.
Le chargement d'un fichier de modèle malveillant peut exécuter du code arbitraire (attaques de désérialisation Pickle). Les modèles pré-entraînés provenant de sources non fiables peuvent contenir des portes dérobées. Les bibliothèques ML compromises affectent tous les utilisateurs en aval. La chaîne d'approvisionnement ML a moins de contrôles de sécurité que les chaînes d'approvisionnement logicielles traditionnelles.
import pickle import torch # Loading untrusted model Earbitrary code execution! with open("model_from_internet.pkl", "rb") as f: model = pickle.load(f) # DANGEROUS: can execute any code! # Loading unverified PyTorch model model = torch.load("untrusted_model.pt") # Uses pickle internally! # Using unvetted model from public hub from transformers import AutoModel model = AutoModel.from_pretrained("random-user/suspicious-model") # No hash verification, no security scan
import torch import hashlib from safetensors.torch import load_file # Use SafeTensors Eno arbitrary code execution model_state = load_file("model.safetensors") # Safe format! model = MyModel() model.load_state_dict(model_state) # Verify model hash before loading EXPECTED_HASH = "sha256:a1b2c3d4..." with open("model.safetensors", "rb") as f: actual_hash = "sha256:" + hashlib.sha256(f.read()).hexdigest() assert actual_hash == EXPECTED_HASH, "Model integrity check failed!" # Use trusted models from verified organizations from transformers import AutoModel model = AutoModel.from_pretrained( "google/bert-base-uncased", # Verified organization revision="a265f77", # Pin to specific commit )
Les attaques par apprentissage par transfert exploitent la pratique courante de l'affinage de modèles pré-entraînés. Les portes dérobées intégrées dans le modèle de base persistent à travers l'affinage et restent actives dans le modèle en aval. Un attaquant qui publie un modèle pré-entraîné populaire peut compromettre toutes les applications qui l'utilisent comme fondation.
Les portes dérobées dans les modèles pré-entraînés survivent à l'affinage car elles sont intégrées dans des couches profondes qui sont souvent gelées pendant l'apprentissage par transfert. Un seul modèle de fondation compromis peut affecter des milliers d'applications en aval. L'attaque est évolutive et difficile à détecter.
from transformers import AutoModelForSequenceClassification # Fine-tuning an unvetted pre-trained model model = AutoModelForSequenceClassification.from_pretrained( "unknown-user/bert-finetuned-sentiment", # Untrusted source! num_labels=2 ) # Freezing base layers Epreserves any hidden backdoor for param in model.base_model.parameters(): param.requires_grad = False # Backdoor in frozen layers persists! # Fine-tune only the classification head trainer.train() # Backdoor remains undetected
from transformers import AutoModelForSequenceClassification from neural_cleanse import BackdoorDetector # Use only verified, trusted base models model = AutoModelForSequenceClassification.from_pretrained( "google/bert-base-uncased", # Trusted source num_labels=2, revision="main", ) # Scan pre-trained model for backdoors before fine-tuning detector = BackdoorDetector(model) if detector.scan(): raise SecurityError("Potential backdoor detected in base model") # Fine-tune ALL layers (not just head) to overwrite potential backdoors for param in model.parameters(): param.requires_grad = True # Train all layers # Validate with clean test set + trigger test set trainer.train() evaluate_for_backdoors(model, trigger_test_set)
La dérive du modèle se produit lorsque la distribution des données en production diffère significativement de la distribution des données d'entraînement (dérive entraînement-service). Cela peut se produire naturellement au fil du temps (dérive des données) ou être intentionnellement causé par des attaquants qui manipulent la distribution des entrées en production pour dégrader les performances du modèle ou biaiser les prédictions.
La dérive du modèle provoque des échecs silencieux où le modèle produit des prédictions incorrectes mais confiantes. Dans les systèmes financiers, les attaquants peuvent exploiter la dérive pour contourner la détection de fraude. Dans les systèmes de recommandation, la dérive peut être intentionnellement induite pour promouvoir du contenu ou des produits spécifiques.
import joblib # Deploy model with no drift monitoring model = joblib.load("model_trained_2023.pkl") def predict(features): # No check if input distribution has changed # No feature validation against training schema return model.predict([features])[0] # Model may be months/years old # No monitoring of prediction distribution # Silent degradation goes undetected
import joblib import numpy as np from scipy import stats from evidently import ColumnDriftMetric model = joblib.load("model.pkl") training_stats = joblib.load("training_stats.pkl") def predict_with_monitoring(features): # Validate feature schema and ranges for i, (val, stat) in enumerate(zip(features, training_stats)): z_score = abs((val - stat["mean"]) / stat["std"]) if z_score > 5: log.warning(f"Feature {i} out of distribution: z={z_score:.1f}") prediction = model.predict([features])[0] # Log prediction distribution for drift monitoring metrics_collector.log(features, prediction) # Periodic drift detection (run by monitoring job) # drift_report = ColumnDriftMetric().calculate(reference, current) # Alert if drift detected ↁEtrigger retraining return prediction
Les attaques d'intégrité de sortie altèrent les prédictions du modèle après qu'elles quittent le modèle mais avant qu'elles n'atteignent l'application consommatrice. Cela inclut les attaques de type man-in-the-middle sur les API de prédiction, la manipulation de l'infrastructure de service du modèle et l'altération des prédictions en cache. L'attaque cible le pipeline d'inférence plutôt que le modèle lui-même.
Les prédictions altérées peuvent causer des décisions incorrectes dans les systèmes en aval : approuver des transactions frauduleuses, poser des diagnostics médicaux erronés ou outrepasser les systèmes de sécurité. Parce que le modèle lui-même n'est pas compromis, la surveillance standard du modèle ne détectera pas l'attaque.
import requests # Consuming model predictions over unencrypted HTTP def get_prediction(features): response = requests.post( "http://ml-service/predict", # HTTP, not HTTPS! json={"features": features} ) result = response.json() # No integrity verification of the response # No validation of prediction format return result["prediction"] # Could be tampered!
import requests import hmac import hashlib def get_prediction(features): response = requests.post( "https://ml-service/predict", # HTTPS (TLS) json={"features": features}, headers={"Authorization": f"Bearer {API_TOKEN}"}, verify=True # Verify TLS certificate ) result = response.json() # Verify response integrity with HMAC signature signature = response.headers.get("X-Signature") expected = hmac.new( SHARED_SECRET, str(result).encode(), hashlib.sha256 ).hexdigest() if not hmac.compare_digest(signature, expected): raise IntegrityError("Response signature mismatch!") # Validate prediction is within expected range pred = result["prediction"] if pred not in VALID_LABELS: raise ValueError(f"Unexpected prediction: {pred}") return pred
L'empoisonnement de modèle modifie directement les poids, paramètres ou l'architecture du modèle entraîné pour injecter des portes dérobées ou altérer le comportement. Contrairement à l'empoisonnement des données (qui corrompt les données d'entraînement), l'empoisonnement de modèle cible l'artefact du modèle lui-même Eà travers des dépôts de modèles compromis, des menaces internes ou des attaques de la chaîne d'approvisionnement sur le stockage et le pipeline de déploiement du modèle.
Un modèle directement empoisonné peut contenir des portes dérobées hautement ciblées qui sont pratiquement indétectables par les tests standard. L'attaquant a un contrôle précis sur le comportement du modèle sur les entrées de déclenchement. Si le registre de modèles ou le pipeline de déploiement est compromis, chaque déploiement utilise le modèle empoisonné.
import mlflow # Loading model from registry with no integrity checks model_uri = "models:/fraud-detector/Production" model = mlflow.pyfunc.load_model(model_uri) # No hash verification # No signature validation # No comparison with expected model metrics # Model registry has weak access controls # Anyone with push access can replace the model predictions = model.predict(new_data)
import mlflow import hashlib from sigstore.verify import Verifier # Verify model signature before loading model_uri = "models:/fraud-detector/Production" model_path = mlflow.artifacts.download_artifacts(model_uri) # Cryptographic signature verification verifier = Verifier.production() verifier.verify( model_path, expected_identity="ml-team@company.com" ) # Verify model hash against approved registry model_hash = hash_directory(model_path) approved_hash = get_approved_hash("fraud-detector", "Production") assert model_hash == approved_hash, "Model integrity check failed!" # Validate model metrics on reference dataset before serving model = mlflow.pyfunc.load_model(model_path) ref_score = evaluate(model, reference_dataset) assert ref_score >= MINIMUM_ACCURACY, "Model quality below threshold" predictions = model.predict(new_data)
| ID | Vulnérabilité | Sévérité | Atténuation Clé |
|---|---|---|---|
| ML01 | Attaque par Manipulation d'Entrée | Critical | Entraînement adversarial, validation des entrées, seuils de confiance |
| ML02 | Attaque par Empoisonnement des Données | Critical | Détection d'anomalies, provenance des données, comparaison de référence |
| ML03 | Attaque par Inversion de Modèle | High | Confidentialité Différentielle, sortie minimale, limitation de débit |
| ML04 | Attaque par Inférence d'Appartenance | High | Régularisation, entraînement DP, pas d'exposition des probabilités |
| ML05 | Vol de Modèle | Critical | Limitation de débit, filigrane, détection de patterns de requêtes |
| ML06 | Attaques de la Chaîne d'Approvisionnement IA | Critical | SafeTensors, vérification de hachage, sources fiables uniquement |
| ML07 | Attaque par Apprentissage par Transfert | High | Modèles de base fiables, scan de portes dérobées, affinage complet |
| ML08 | Dérive du Modèle | Medium | Surveillance de la dérive, validation des entrées, réentraînement automatique |
| ML09 | Attaque d'Intégrité de Sortie | High | TLS/mTLS, signature des réponses, validation de sortie |
| ML10 | Empoisonnement de Modèle | Critical | Signature de modèle, contrôle d'accès au registre, validation de métriques |