Load/Mine: modelos maliciosos de Hugging Face comprometen hosts GPU
Repositorios de modelos maliciosos en Hugging Face ejecutaron mineros y backdoors en los hosts GPU que los cargaron; la campaña volvió tras cada retirada.
Resumen
- Una campaña rastreada por Pluto Research plantó cargas de criptominado y de backdoor en 31 repositorios de modelos repartidos en siete cuentas de Hugging Face, disfrazados de pequeños modelos de prueba Qwen2. Cargar uno con código personalizado habilitado ejecuta el Python del publicador en el host antes de leer peso alguno. Pluto publicó el análisis el 29 de octubre de 2026.
- La campaña y sus modelos vinculados registran 32,376 descargas, 29,818 de ellas en los 31 repositorios maliciosos. Las víctimas son reales y actuales: la cartera del operador ha recibido unas 2,276 PRL (Pearl) en cadena, unos 5,000 dólares, y trabajadores de minado activos seguían reportando a la captura.
- Los objetivos son la infraestructura GPU autoalojada: despliegues de Xinference y GPUStack, máquinas GPU alquiladas, cuadernos de investigación compartidos y tareas de CI que bajan modelos de prueba pequeños. La segunda ola volvió a los pocos días de una retirada, y un repositorio lleva un script de escape del contenedor al host.
- No hay CVE asignado y el incidente no está en el catálogo KEV de CISA al 3 de noviembre de 2026. Revise cada archivo
.pyque referencia una entradaauto_mapantes de habilitartrust_remote_code, y compruebe los hosts que cargaron cualquiera de los modelos listados. «¿Qué debo hacer si lo instalé?» lleva los pasos de contención.
¿Estoy afectado?
Está afectado cuando se cumplen todas estas condiciones:
- Alguien en su infraestructura cargó un modelo de uno de los repositorios de la campaña con el código personalizado habilitado, mediante
trust_remote_code=Trueo una confirmación interactiva. Los nombres de repositorios y cuentas están en «¿Cómo compruebo si estuve comprometido?». - Su entorno pertenece a la clase objetivo: un host de inferencia autoalojada, especialmente Xinference o GPUStack, una máquina GPU alquilada, un cuaderno de investigación compartido o una tarea de CI que baja modelos pequeños sin revisar.
- La carga se ejecuta en el momento de la importación, antes de leer los pesos, así que una carga de modelo fallida también la ejecuta. Un
model.safetensorsde 42 bytes sin tensores es todo el archivo de pesos del repositorio insignia.
Las cuentas de la primera ola se retiraron tras el informe de Pluto. La segunda ola apareció a los pocos días con cuentas nuevas y seguía en línea ocho días después de reportarse, así que una retirada no es prueba de que la exposición terminara.
| Grupo | Repositorios | Descargas registradas | Estado el 28 de octubre de 2026 |
|---|---|---|---|
| Primera ola, cuentas hhzz667 y dsxk4mr8tq | 12 | 728 | retirados |
| Segunda ola, cinco cuentas incluidas MENXIANGYU y wjsbg | 19 | 29,090 | en línea |
| Modelos vinculados, seis cuentas | 13 | 2,558 | en línea |
Compruebe sus cachés y sus hosts:
# Any of the campaign's models in the local Transformers cache
ls ~/.cache/huggingface/hub 2>/dev/null |
grep -iE "MENXIANGYU|wjsbg|htt0118|soumikmahato|r1r13|hhzz667|dsxk4mr8tq|MONIADF-2|antwoinhjro654|vaishupv|andrew3d|oifewuheu|KzuryHD"
# Watchdog and miner artifacts on the host
ls -la /tmp/.cch 2>/dev/null
# A kernel-worker name that carries userspace arguments is the miner
ps aux | grep -F '[kworker/u20:1]'
# Persistence hooks written by the payload
grep -n "svc.py" ~/.bashrc ~/.profile /root/.bashrc 2>/dev/null
Un hilo de kernel real no muestra argumentos de línea de comandos; el minero disfrazado lleva el login de minado como argumentos, que es la señal que describe Pluto.
¿Cómo lo corrijo?
Ninguna actualización de paquetes corrige esto, porque la entrega es una función documentada usada tal como está diseñada. La corrección es una disciplina de revisión sobre cada modelo con una entrada auto_map.
- Inventarie cada modelo que sus pipelines cargan con
trust_remote_code=Truey lea cada archivo Python que referencia elauto_mapde suconfig.jsonantes de la siguiente carga. Esa revisión habría detectado todas las cargas de la campaña, como señala Pluto. - Prefiera modelos cuyos pesos vengan en
safetensorssin entradaauto_map, y fije las revisiones de modelos por hash de commit en tareas y cuadernos. - En hosts Xinference y GPUStack, restrinja el registro de modelos y la API a redes de confianza. Las cargas escriben en el registro de Xinference y una etapa apunta a los directorios del host de GPUStack, así que trate una consola expuesta como dentro del alcance.
- Bloquee y vigile los indicadores de «¿Cómo compruebo si estuve comprometido?» en su salida de red, incluidos los receptores de shell inverso y los pools.
La guía de Hugging Face ya coloca el trabajo en el usuario: verificar el código antes de habilitar la opción. Las fichas de modelos y tutoriales que copian trust_remote_code=True convierten un consentimiento informado en una casilla, que es justo lo que esta campaña contaba con encontrar.
¿Qué hace el código malicioso?
La carga es Python ordinario colocado junto a un archivo de pesos diminuto, y se ejecuta en el momento de la importación. Un proceso vigilante, un descargador sin proxy, un minero en memoria disfrazado de hilo de kernel y shells inversos se ensamblan antes incluso de construir la clase del modelo.
Transformers carga una implementación de modelo personalizada importando el archivo nombrado en la entrada auto_map del config.json del repositorio:
# transformers/src/transformers/dynamic_module_utils.py (get_class_in_module)
module_spec = importlib.util.spec_from_file_location(name, location=module_file)
module = importlib.util.module_from_spec(module_spec)
sys.modules[name] = module
module_spec.loader.exec_module(module)
return getattr(module, class_name)
exec_module ejecuta las instrucciones de nivel superior del archivo para definir sus clases, así que una llamada colocada junto a esas definiciones corre antes de que getattr devuelva. El repositorio insignia, MENXIANGYU/qwen2-tiny con 28,412 descargas, apunta auto_map a un módulo cuyo punto de entrada dispara de inmediato:
# modeling_evil2.py - shortened module-level entry point, published by Pluto
try:
_boot()
except Exception:
try:
_run()
except Exception:
pass
class EvilModel:
...
Desde ahí la construcción ensambla su cadena:
_bootescribe/tmp/.cch/svc.py, un vigilante que corre como proceso separado y reinicia un minero ausente cada 120 segundos. Líneas añadidas a/root/.bashrc,/root/.profiley el~/.bashrcdel usuario lo relanzan en cada inicio de sesión.- Tres construcciones añaden un shell inverso que conecta a
212.32.253.3:443o:9443y reintenta tras cada desconexión, dando al operador un shell interactivo con los archivos y credenciales del proceso comprometido. - El binario minero,
syscore.bin, llega desde el repositorio del modelo o un espejo, descargado con los proxies configurados del host deshabilitados. Es un ejecutable de 40.6 MB empaquetado con UPX cuyos marcadores se cambiaron deUPX!aPEK!para que las herramientas de desempaquetado rutinarias lo rechacen. - El lanzamiento inicial intenta copiar el binario a un descriptor de archivo en memoria y ejecutarlo desde ahí:
# modeling_evil2.py - excerpt of the attempted memory-backed launch
fd = os.memfd_create("kworker")
with open(body, "rb") as f:
os.write(fd, f.read())
os.fchmod(fd, 0o755)
if os.fork() == 0:
os.setsid()
os.fexecve(fd, [_TAG] + _margs(), os.environ)
memfd_createcrea un archivo anónimo en memoria. La llamada citadaos.fexecveno es una API estándar de CPython; Python expone la ejecución desde un descriptor medianteos.execve. El fragmento describe un intento de lanzamiento, no una traza de ejecución verificada. Pluto también documenta un binario de respaldo en/tmp/.cch/syncpara los reinicios del vigilante, por lo que pueden quedar pruebas en disco aunque el lanzamiento en memoria funcione. El nombre[kworker/u20:1]es un disfraz, no una prueba de que no haya artefactos en disco.- Los informes de estado se escriben en el registro local de Xinference disfrazados de nombres de modelos,
qwen2-tiny-seguido de estado en base32, para que un operador con acceso a la API lea cada infección. Con su minero corriendo, la carga elimina registros de modelos rivales y mata los procesos coincidentes.
Un repositorio posterior de la segunda ola, MENXIANGYU/qa-whl-1021, es un script de escape del contenedor al host. Desde un contenedor privilegiado activa volúmenes LVM, monta discos candidatos del host y encuentra la raíz del host o el /var/lib/gpustack de GPUStack. Después da al atacante un login root en el host.
¿Cómo se produce la intrusión?
No hay exploit en el sentido de vulnerabilidad: la víctima concede el permiso y el código del publicador corre con él.
- El operador publica un repositorio que parece un pequeño punto de control de prueba Qwen2. Su README está copiado de la ficha oficial de Qwen2.5-0.5B, y su
config.jsonnombra el módulo malicioso enauto_map. - Una víctima carga el modelo con
trust_remote_code=True. Transformers descarga el archivo referenciado a su caché de módulos y lo importa. - La importación ejecuta el código de nivel superior del módulo,
_boot, antes de construir la clase del modelo. No se lee ningún peso, y no existen pesos maliciosos. - La cadena de «¿Qué hace el código malicioso?» corre con los privilegios del proceso que carga, y la GPU empieza a minar hacia la cartera del operador.
El mismo permiso ha fallado en otros lugares, como señala Pluto. CVE-2026-22807 hacía que vLLM cargara módulos auto_map sin comprobar el ajuste trust_remote_code, corregido en vLLM 0.14.0, y Unsloth Studio inspeccionaba configuraciones de modelos con la opción fijada en el código, corregido en 2026.6.9.
¿Cómo fluye el ataque?
Una sola llamada de carga con código personalizado entrega al Python de nivel superior del publicador los privilegios del proceso, y la cadena corre antes de leer peso alguno.
con trust_remote_code=True"] --> B["El auto_map de config.json
nombra modeling_evil2.py:
importar es comportamiento documentado"] B --> C["exec_module ejecuta _boot
al importar: el código corre
antes que la clase"] C --> D["Vigilante /tmp/.cch/svc.py
más ganchos .bashrc,
reinicio del minero cada 120 s"] D --> E["syscore.bin descargado sin proxy,
empaquetado con marcadores
UPX alterados"] E --> F["intento de lanzamiento en memoria;
binario de respaldo en disco"] F --> G["Pools de minado, shells inversos,
estado oculto en el
registro Xinference"]
¿Qué puede hacer un atacante?
El impacto demostrado es ejecución de código como el usuario que cargó el modelo, con tres resultados activos hoy.
- Criptominado en las GPU de las víctimas, con ganancia realizada en cadena: la cartera de la primera ola recibió unas 2,276 PRL en 716 transacciones y había enviado unas 2,241 PRL, con unas 1,008 PRL en recompensas de 30 días en sus trabajadores activos a la captura.
- Un acceso de backdoor que sobrevive al minero: los shells inversos reconectan tras cada desconexión, y un dropper posterior solo responde a direcciones IP permitidas, devolviendo
403con el textostatic ip.a los demás, de modo que un analista que pida la misma URL puede no ver la carga. - Alcance lateral en la infraestructura GPU: el script de escape de contenedor apunta al host bajo un contenedor privilegiado, y una familia de la segunda ola recoge información de plataforma GPU para despliegues LLaMA-Factory y metadatos de Tencent Cloud.
Pluto observó una cohorte de nueve trabajadores que se unieron con menos de 90 segundos de diferencia. Sus nombres, como dhumphreys-notebook-2 y a10040gb, sugieren un clúster de cuadernos compartido más que una granja de minado. Sus participaciones se detuvieron unos seis minutos después, lo que encaja con un administrador matando las tareas. El operador también elimina mineros y modelos rivales para quedarse la potencia de cómputo.
Las condiciones son el permiso y la alcanzabilidad de los puntos de exfiltración. El acceso a archivos y credenciales del proceso de carga es el radio de impacto.
¿Qué debo hacer si lo instalé?
Contenga primero, preserve evidencia y luego limpie. La carga corre antes de la inicialización del modelo, y sus backdoors pueden permanecer tras el fin del minado.
- Aísle los hosts afectados de las redes de producción y restrinja su salida de red a lo que las cargas de trabajo necesitan.
- Antes de borrar nada, capture detalles de procesos, conexiones de red, registros, la caché de modelos, cambios en archivos de arranque y entradas de authorized_keys.
- Elimine toda ruta de regreso: el directorio
/tmp/.cch/y su vigilantesvc.py, las líneas añadidas por la carga a los archivos de arranque del shell, cualquier clave SSH que no haya añadido y registros inesperados de Xinference. - Rote toda credencial al alcance del proceso de carga: claves de proveedor en la nube, tokens de registro y tokens de plataformas de IA guardados en el entorno.
- Compruebe las demás máquinas que cargaron los mismos repositorios. Para hosts GPU y contenedores privilegiados, prefiera una reconstrucción a una limpieza: el repositorio de escape apunta a root en el host, y un lanzamiento en memoria deja poco que inspeccionar.
¿Cómo compruebo si estuve comprometido?
Pluto publicó los indicadores; el estado de los repositorios y los contadores de descargas son del 28 de octubre de 2026, y la infraestructura de red se observó por última vez el 21 de octubre. Una coincidencia es un indicio que investigar, y ningún indicador ausente descarta la intrusión.
Repositorios que buscar en cachés y registros:
- Primera ola, retirada:
hhzz667ydsxk4mr8tqpublicaron qwen2-shell, qwen2-tiny, qwen2-mini, qwen2-nano, qwen2-legacy, qwen2-final y qwen2-tiny-gpt2. - Segunda ola, en línea:
MENXIANGYUcon qwen2-tiny y zz_ext,wjsbgcon qwen2-final,soumikmahatoqwen2-tiny-chat,r1r13qwen-lora-text-v1,htt0118qwen2-tiny. - Cuentas vinculadas:
MONIADF-2,antwoinhjro654,vaishupv,andrew3d,oifewuheu,KzuryHD.
Artefactos en el host:
/tmp/.cch/y procesossvc.py, y un proceso llamado[kworker/u20:1]que lleva argumentos de minado como el loginkrxXJ2PKQW.worker.- Archivos de arranque
/root/.bashrc,/root/.profiley~/.bashrcque invoquensvc.py; una clave ed25519 que usted no añadió enauthorized_keys; el marcador de autocomprobación/tmp/.qa_pwned. - Registros de Xinference que empiecen por
qwen2-tiny-oezv-, que codifican estado en base32.
Indicadores de red que bloquear y rastrear:
- Receptores de shell inverso y alojamiento de carga en
212.32.253.3, puertos 443, 9443, 8895 y 8898, incluido el:8898/lf_py.pyfiltrado por IP de origen;38.55.133.49puertos 8901 y 9001; el C2 de Alibaba Cloud de la primera ola8.137.187.63;103.97.176.121:3361;178.33.123.26puertos 3361 y 3362; yhz2006.aihzcc.top. - Pools:
stratum+ssl://prl-us.kryptex.network:8048yprl-sg.kryptex.network:8048;pearl-hk.luckypool.iopuertos 3361 y 3956;pool.hashvault.propuertos 80 y 443;gulf.moneroocean.stream:10001;mine.c3pool.com:80. - La cartera de la primera ola
prl1p9s2a9gdy4n5yfuf5suhya2d2pyvaahmz3n3gzm3npyxkj79m7yyq9kgnrvy la segundaprl1pek8rh8yjck5q7596aph7ua7xnkqs8ur9mkfmz8w7xfzcdfcfx5vsx2f4lz.
Cronología
- 2026-04-29: se crea la cuenta hhzz667 y permanece dormida cinco meses.
- 2026-09-21: se publica la primera carga, qwen2-shell, una backdoor SSH de 37 líneas con clave ed25519 y baliza.
- 2026-09-21 a 2026-09-22: aparecen construcciones de inyección de claves en seis rutas, luego qwen2-final, el primer minero fileless completo.
- 2026-10-12: la cuenta dsxk4mr8tq publica seis repositorios en 2.5 horas, y el escáner de Pluto marca
modeling_evil.py. - 2026-10-20: Pluto informa de la primera ola a Hugging Face y ModelScope, y ambas plataformas retiran las cuentas. En las horas tras la divulgación, las descargas de qwen2-tiny pasan de 7,354 a 9,584, y el repositorio de escape de contenedor aparece 8.5 horas tras el correo de divulgación.
- 2026-10-25: aparece la cuenta htt0118 con una copia casi idéntica de una carga anterior, mismo login de pool y mismos receptores de shell inverso.
- 2026-10-28: Pluto registra la instantánea de indicadores: qwen2-tiny en 28,412 descargas, y los repositorios de la segunda ola aún en línea.
- 2026-10-29: Pluto publica la investigación.
Preguntas frecuentes
¿Es trust_remote_code=True una vulnerabilidad de Transformers?
No. Es comportamiento documentado para modelos que necesitan una implementación personalizada, y la política de seguridad de Transformers pide verificar siempre el contenido de los archivos de modelado al usarlo. Los incidentes están en los cargadores que saltan la elección: vLLM antes de 0.14.0 importaba módulos auto_map sin comprobar el ajuste, y Unsloth Studio lo tenía fijado en el código.
¿Por qué los escáneres de la plataforma no detectaron las cargas?
Cada archivo de menos de 2 GB pasa por ClamAV en cada commit, y escáneres asociados inspeccionan los formatos de pesos en busca de exploits pickle y de modelos. Un archivo Python simple referenciado por auto_map no es ni una firma de malware conocida ni un modelo serializado. La carga insignia pasó limpia, y un escáner informó de que no era un modelo de aprendizaje automático.
¿Siguen en línea los repositorios maliciosos?
La primera ola se retiró. La segunda ola, 19 repositorios en cinco cuentas, seguía en línea ocho días después de que Pluto la reportara, y la campaña reemplazó la primera ola retirada en cuestión de días: las retiradas no han terminado la actividad. Una cuenta de las familias vinculadas también publicó repositorios nuevos tras la divulgación.
¿Cuánto ha ganado el operador?
Unas 2,276 PRL, unos 5,000 dólares, llegaron en cadena a la cartera de la primera ola en 716 transacciones, con unas 2,241 PRL enviadas. Sus trabajadores en Kryptex mostraban unas 1,008 PRL en recompensas de 30 días a la captura. La cartera MoneroOcean de una familia vinculada ganó unos 26 dólares en un mes: su actividad importa más que su dinero.
¿Quién dirige la campaña?
Pluto evalúa un operador o grupo pequeño de habla china y motivación financiera. Las pruebas: comentarios de código en chino simplificado, un C2 en Alibaba Cloud, espejos en ModelScope y hf-mirror.com, un objetivo que presupone Xinference y Tencent Cloud, y horarios de actividad de madrugada en China. El toolkit parasit en indonesio podría ser un segundo operador en el mismo nicho.
¿Protegen los pesos safetensors contra esta clase?
No. El archivo safetensors del repositorio insignia tenía 42 bytes sin tensores, porque no hacían falta pesos maliciosos: la carga es el Python que está al lado. Un formato de pesos no revisa código, y por eso las referencias auto_map necesitan un lector.
Encuentre consolas de inferencia IA expuestas con Ostorlab
El descubrimiento de superficie de ataque de Ostorlab inventaría los hosts expuestos a internet en sus rangos, para localizar cada consola Xinference o GPUStack que esta campaña toma como objetivo. Comience un escaneo con Ostorlab para saber qué hosts de inferencia necesitan primero las restricciones de red.
Referencias
- Pluto Security, «Load/Mine: A Hugging Face Malware Campaign Returns After Takedown», publicado el 29 de octubre de 2026 por Shaked Chen, Eitan Chassid y Yotam Perkal, con el análisis de la carga, el seguimiento de cuentas y la lista completa de indicadores: https://pluto.security/blog/hugging-face-malware-campaign-load-mine (verificado el 3 de noviembre de 2026)
- Código fuente de Transformers,
dynamic_module_utils.py, la ruta de importaciónget_class_in_moduleque la campaña desvía: https://github.com/huggingface/transformers/blob/main/src/transformers/dynamic_module_utils.py (verificado el 3 de noviembre de 2026) - Política de seguridad de Transformers, «please always verify the content of the modeling files when using this argument», y la recomendación de fijar una revisión: https://github.com/huggingface/transformers/blob/main/SECURITY.md (verificado el 3 de noviembre de 2026)
- Documentación de Hugging Face Hub sobre seguridad, las funciones de análisis de malware y pickle y sus límites: https://huggingface.co/docs/hub/security (verificado el 3 de noviembre de 2026)
- README de Xinference, el puerto de despliegue estándar usado para la comprobación del registro: https://github.com/xorbitsai/inference (verificado el 3 de noviembre de 2026)
- Fuente JSON del catálogo Known Exploited Vulnerabilities de CISA, confirmando que no existe entrada de CVE para esta campaña al 3 de noviembre de 2026: https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json (verificado el 3 de noviembre de 2026)
- Documentación Python os, ejecución desde un descriptor con
os.execve: https://docs.python.org/3/library/os.html#os.execve (verificado el 3 de noviembre de 2026)
Etiquetas:
security, supply chain, Hugging Face, trust_remote_code, cryptomining, backdoor, malicious release