Los 10 riesgos de seguridad más críticos en sistemas de machine learning y cómo mitigarlos.
El OWASP Machine Learning Security Top 10 identifica los riesgos de seguridad más significativos específicos de los sistemas de machine learning. A diferencia del software tradicional, los sistemas ML son vulnerables a ataques únicos dirigidos a datos de entrenamiento, componentes internos del modelo y pipelines de inferencia. Esta guía cubre ataques adversarios, envenenamiento de datos, robo de modelos, riesgos de cadena de suministro y más, con ejemplos prácticos de código Python.
Los ataques de manipulación de entrada (ataques adversarios) elaboran entradas especialmente diseñadas para hacer que los modelos ML realicen predicciones incorrectas. Perturbaciones pequeñas, a menudo imperceptibles, en imágenes, texto u otras entradas pueden engañar a clasificadores, eludir sistemas de detección y evitar filtros de contenido. Este es el vector de ataque más conocido específico de ML.
Los ejemplos adversarios pueden eludir sistemas ML críticos para la seguridad: percepción de vehículos autónomos, detección de malware, detección de fraude y moderación de contenido. Un atacante puede hacer que una señal de alto sea clasificada como una señal de límite de velocidad, o hacer que el malware parezca benigno para un antivirus basado en 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}
Los ataques de envenenamiento de datos inyectan muestras maliciosas en conjuntos de datos de entrenamiento para corromper el comportamiento aprendido del modelo. Los atacantes pueden introducir puertas traseras (patrones de activación que causan clasificaciones erróneas específicas), desplazar límites de decisión o degradar la precisión general del modelo. Esto es especialmente peligroso cuando los datos de entrenamiento se obtienen de internet o contenido generado por usuarios.
Un modelo envenenado puede comportarse normalmente con entradas limpias pero clasificar incorrectamente cuando está presente un patrón de activación específico. Por ejemplo, un clasificador de malware con puerta trasera podría aprobar cualquier muestra de malware que contenga una secuencia de bytes específica. El ataque es sigiloso porque la precisión del modelo en datos limpios permanece alta.
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)
Los ataques de inversión de modelo reconstruyen datos de entrenamiento sensibles consultando el modelo y analizando sus salidas. Un atacante puede recuperar información privada como rostros, registros médicos o datos personales utilizados durante el entrenamiento. Esto es particularmente preocupante para modelos entrenados con conjuntos de datos sensibles (salud, biometría, datos financieros).
La inversión de modelo puede violar regulaciones de privacidad de datos (GDPR, HIPAA) al exponer información de identificación personal de los datos de entrenamiento. Un atacante con acceso API a un modelo de reconocimiento facial podría reconstruir rostros de individuos en el conjunto de entrenamiento.
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 })
Los ataques de inferencia de pertenencia determinan si un punto de datos específico se utilizó en el conjunto de datos de entrenamiento del modelo. Al analizar las puntuaciones de confianza del modelo y el comportamiento en entradas conocidas vs. desconocidas, los atacantes pueden inferir información de pertenencia privada. Esta es una amenaza significativa a la privacidad para modelos entrenados con datos sensibles.
La inferencia de pertenencia puede revelar que los datos de un individuo específico se utilizaron para el entrenamiento, por ejemplo, confirmando que el registro de un paciente estaba en un conjunto de datos clínicos, o que el rostro de una persona se utilizó para entrenamiento de vigilancia. Esto viola las expectativas de privacidad y potencialmente regulaciones como GDPR.
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
Los ataques de robo de modelo (extracción de modelo) crean una copia funcional de un modelo ML propietario consultándolo sistemáticamente y entrenando un modelo sustituto en los pares entrada-salida. El modelo robado puede luego usarse para encontrar ejemplos adversarios, competir comercialmente o hacer ingeniería inversa de los datos de entrenamiento del modelo.
Un modelo robado representa pérdida de propiedad intelectual y ventaja competitiva. El modelo extraído puede usarse sin conexión para elaborar ataques adversarios o para entender límites de decisión. Millones de dólares de inversión en entrenamiento pueden replicarse con miles de consultas 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 })
Los ataques a la cadena de suministro de IA se dirigen al pipeline de desarrollo de ML: modelos preentrenados de hubs de modelos, conjuntos de datos de terceros, frameworks ML y dependencias. Los modelos maliciosos pueden contener puertas traseras ocultas, y las bibliotecas comprometidas pueden inyectar vulnerabilidades. Los formatos de serialización utilizados por frameworks ML (Pickle, SavedModel) pueden ejecutar código arbitrario al cargarse.
Cargar un archivo de modelo malicioso puede ejecutar código arbitrario (ataques de deserialización Pickle). Los modelos preentrenados de fuentes no confiables pueden contener puertas traseras. Las bibliotecas ML comprometidas afectan a todos los usuarios descendientes. La cadena de suministro de ML tiene menos controles de seguridad que las cadenas de suministro de software tradicionales.
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 )
Los ataques de transfer learning explotan la práctica común de afinar modelos preentrenados. Las puertas traseras incorporadas en el modelo base persisten a través del ajuste fino y permanecen activas en el modelo descendiente. Un atacante que publique un modelo preentrenado popular puede comprometer todas las aplicaciones que lo usan como base.
Las puertas traseras en modelos preentrenados sobreviven al ajuste fino porque están incorporadas en capas profundas que a menudo se congelan durante el transfer learning. Un solo modelo base comprometido puede afectar a miles de aplicaciones descendientes. El ataque es escalable y difícil de detectar.
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)
El sesgo de modelo ocurre cuando la distribución de datos en producción difiere significativamente de la distribución de datos de entrenamiento (sesgo entrenamiento-servicio). Esto puede suceder naturalmente con el tiempo (deriva de datos) o ser causado intencionalmente por atacantes que manipulan la distribución de entrada de producción para degradar el rendimiento del modelo o sesgar predicciones.
El sesgo de modelo causa fallos silenciosos donde el modelo produce predicciones incorrectas pero confiadas. En sistemas financieros, los atacantes pueden explotar el sesgo para eludir la detección de fraude. En sistemas de recomendación, el sesgo puede inducirse intencionalmente para promover contenido o productos específicos.
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
Los ataques de integridad de salida manipulan las predicciones del modelo después de que salen del modelo pero antes de que lleguen a la aplicación consumidora. Esto incluye ataques de intermediario en APIs de predicción, manipulación de infraestructura de servicio de modelos y manipulación de predicciones en caché. El ataque se dirige al pipeline de inferencia en lugar del modelo en sí.
Las predicciones manipuladas pueden causar decisiones incorrectas en sistemas descendientes: aprobar transacciones fraudulentas, diagnosticar erróneamente condiciones médicas o anular sistemas de seguridad. Debido a que el modelo en sí no está comprometido, el monitoreo estándar del modelo no detectará el ataque.
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
El envenenamiento de modelo modifica directamente los pesos, parámetros o arquitectura del modelo entrenado para inyectar puertas traseras o alterar el comportamiento. A diferencia del envenenamiento de datos (que corrompe los datos de entrenamiento), el envenenamiento de modelo se dirige al artefacto del modelo en sí, a través de repositorios de modelos comprometidos, amenazas internas o ataques a la cadena de suministro en el pipeline de almacenamiento y despliegue del modelo.
Un modelo directamente envenenado puede contener puertas traseras altamente dirigidas que son virtualmente indetectables mediante pruebas estándar. El atacante tiene control preciso sobre el comportamiento del modelo en entradas de activación. Si el registro de modelos o el pipeline de despliegue está comprometido, cada despliegue usa el modelo envenenado.
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 | Vulnerabilidad | Severidad | Mitigación Clave |
|---|---|---|---|
| ML01 | Ataque de Manipulación de Entrada | Critical | Entrenamiento adversario, validación de entrada, umbrales de confianza |
| ML02 | Ataque de Envenenamiento de Datos | Critical | Detección de valores atípicos, procedencia de datos, comparación con línea base |
| ML03 | Ataque de Inversión de Modelo | High | Privacidad Diferencial, salida mínima, limitación de tasa |
| ML04 | Ataque de Inferencia de Pertenencia | High | Regularización, entrenamiento DP, sin exposición de probabilidades |
| ML05 | Robo de Modelo | Critical | Limitación de tasa, marcas de agua, detección de patrones de consulta |
| ML06 | Ataques a la Cadena de Suministro de IA | Critical | SafeTensors, verificación de hash, solo fuentes confiables |
| ML07 | Ataque de Transfer Learning | High | Modelos base confiables, escaneo de puertas traseras, ajuste fino completo |
| ML08 | Sesgo de Modelo | Medium | Monitoreo de deriva, validación de entrada, reentrenamiento automático |
| ML09 | Ataque de Integridad de Salida | High | TLS/mTLS, firma de respuestas, validación de salida |
| ML10 | Envenenamiento de Modelo | Critical | Firma de modelos, control de acceso a registros, validación de métricas |