SToFU Systems · Investigación del incidente · 20 de octubre de 2026
Índice
- Todo empezó con una consulta sobre desarrollo
- Descarga e inspección de los archivos
- Cómo podía ejecutarse el hook post-checkout de Git
- Qué hace el descargador del hook de Git
- ¿Había una aplicación oculta dentro del archivo?
- Qué devolvió el servidor de descarga
- ¿Podría haber sido una utilidad legítima?
- Revisión de un repositorio Git desconocido
- Después de la revisión del archivo
- Correspondencia y archivos de pruebas
Todo empezó con una consulta sobre desarrollo
El primer mensaje llegó a través de nuestro sitio web el 15 de octubre de 2026. El remitente quería ayuda para desarrollar una plataforma de detección del fraude financiero. El sistema propuesto debía puntuar el riesgo de las transacciones y utilizar análisis del comportamiento, con verificación mediante blockchain y generación de informes entre los demás requisitos.
El 19 de octubre recibimos el enlace de descarga. El correo que lo acompañaba nos remitía a la rama conf para la arquitectura y a la rama NDA para el acuerdo. Reiteraba la petición de revisar y firmar el acuerdo de confidencialidad.
Please review the project materials and review and sign the NDA in the NDA branch.
Seguir esas instrucciones llevaba a la persona encargada de la revisión desde la extracción del archivo hasta el cambio de rama en Git. El correo presentaba ambas operaciones como parte de la lectura de la documentación. No decía nada sobre ejecutar un programa descargado.
El correo con el archivo del proyecto y las instrucciones para abrir la rama NDA. Los datos identificativos están cubiertos con rectángulos grises.
Descarga e inspección de los archivos
El enlace llevaba a una página estándar de Dropbox para compartir archivos. Descargamos AI-Fraud-Detection_Blockchain.tar.gz en un entorno de revisión aislado y conservamos el archivo original como referencia. La URL concreta para compartirlo no se divulga aquí.
Enumeramos el contenido del archivo y leímos directamente los objetos Git suministrados. Así pudimos recuperar los documentos de las tres ramas sin extraer ninguna rama al árbol de trabajo ni ejecutar el código recibido. En los árboles de las ramas encontramos 15 archivos Markdown y un documento Word.
| Rama | Archivos | Qué encontramos |
|---|---|---|
master |
8 archivos Markdown | Un README, una descripción general del proyecto, requisitos del producto y casos de uso de negocio. Otros documentos trataban el rendimiento, la seguridad, las pruebas y las operaciones. |
conf |
7 archivos Markdown | Orientaciones para una propuesta de desarrollo, arquitectura e integración de blockchain, requisitos de contratos inteligentes, diseño de datos y API, un motor de riesgo basado en IA e hitos de entrega. |
NDA |
1 archivo DOCX | Un acuerdo de confidencialidad con un bloque de firma y marcadores de posición de plantilla. |
El producto propuesto combinaba el análisis de transacciones, reglas de detección del fraude y puntuaciones de aprendizaje automático con verificación mediante blockchain. La pila sugerida utilizaba Node.js y TypeScript con Express, PostgreSQL, bibliotecas de aprendizaje automático de Python y Ethereum. El documento de entrega establecía seis hitos a lo largo de 24 semanas. Encontramos documentos de planificación en todos los árboles suministrados, sin ninguna implementación de la aplicación, modelo entrenado ni código fuente de contratos inteligentes que revisar.
Algunos detalles merecían una segunda revisión. Los ejemplos de API contenían las cadenas literales <built-in function id> y <built-in function hash> en las rutas de los endpoints. El acuerdo de confidencialidad seguía conteniendo marcadores de posición de plantilla. Estas incoherencias planteaban dudas sobre el cuidado con el que se había preparado el material.
El archivo ejecutable .git/hooks/post-checkout fue un hallazgo más grave. Sus comentarios describían una utilidad de actualización de navegador o extensión, sin conexión aparente con la revisión de este proyecto. La lectura del script reveló instrucciones para descargar un programa externo y lanzarlo en segundo plano. La petición del correo de cambiar de rama podía llevar a la persona encargada de la revisión a esa ruta de ejecución mientras intentaba leer el acuerdo de confidencialidad.
Mantuvimos el repositorio inactivo y examinamos el hook como texto. A continuación se presentan los metadatos relevantes del archivo y el comportamiento del script.
Cómo podía ejecutarse el hook post-checkout de Git
El archivo AI-Fraud-Detection_Blockchain.tar.gz tiene un tamaño de 779.429 bytes. Sus 100 entradas TAR incluyen 60 archivos normales. Junto con los documentos llegó un directorio .git completo.
El directorio de hooks contenía los archivos .sample habituales y un ejecutable llamado post-checkout.
AI-Fraud-Detection_Blockchain/.git/hooks/post-checkout
No tiene el sufijo .sample. Su modo TAR es 0777, que incluye permisos de ejecución, y su tamaño es de 2.774 bytes.
Git invoca post-checkout tras actualizar el árbol de trabajo mediante operaciones como checkout o switch, siempre que el entorno permita ejecutar el hook. La documentación de Git describe el desencadenante.
Por qué importa el directorio .git incluido en el archivo
Un git clone normal no copia los hooks locales del lado del cliente del repositorio de origen, como explica Pro Git. Esta entrega incluía el directorio .git existente dentro de un archivo, por lo que el hook local llegó con él.
La ruta, el modo y el tamaño del hook leídos directamente del TAR original. No utilizamos el directorio recibido como repositorio Git.
Inspeccionamos directamente el contenedor y los objetos Git, sin ejecutar checkout ni switch en el repositorio recibido.
Qué hace el descargador del hook de Git
Los comentarios del script de shell describen una utilidad de actualización de navegador o extensión. No encontramos ningún requisito para una utilidad de ese tipo en los documentos del proyecto. El script es texto sin ofuscar.
El umbral de tamaño que no se utiliza
El script establece THRESHOLD en 100 y solicita a Git una lista de los nombres de los archivos modificados. Inicializa TRIGGER en cero.
THRESHOLD=100
FILES=$(git diff --name-only "$OLD_HEAD" "$NEW_HEAD")
TRIGGER=0
Dentro del bucle, calcula SIZE. No hay ninguna comparación con THRESHOLD ni ninguna asignación que cambie TRIGGER. El bloque de descarga se ejecuta cuando TRIGGER vale cero.
if [ "$TRIGGER" -eq 0 ]; then
En el recorrido normal de este script, la condición es verdadera incluso cuando la lista de archivos modificados está vacía. El umbral no restringe la descarga.
Se conservan los números de línea originales. TRIGGER permanece en cero. THRESHOLD se declara, pero nunca se utiliza en una comparación.
Descargas independientes para Linux, macOS y Windows
El valor devuelto por uname -s determina la ruta de descarga y el nombre del archivo local.
| Sistema detectado | Ruta en el servidor | Nombre del archivo local |
|---|---|---|
| Linux | /api/l |
run-command.sh |
| Darwin / macOS | /api/m |
run-command.sh |
| MINGW, MSYS, CYGWIN, Windows_NT | /api/w |
run-command.cmd |
| Sistema no reconocido | /api/l |
run-command.sh |
Las URL comparten un mismo host, sustituido aquí por <DOWNLOAD_DOMAIN>. Conservamos el hook sin censurar de forma privada. Las ramas condicionales cubren Linux, macOS y Windows, con una alternativa para Linux si el sistema no se reconoce. No probamos la ejecución en esos sistemas.
Descarga del archivo e intento de ejecución
El hook guarda el archivo en ${TMPDIR:-/tmp} mediante curl, o wget si curl no está disponible. No hay ninguna comprobación de firma ni de hash esperado antes de las instrucciones de ejecución siguientes.
chmod +x "$CMD_FILE"
"$CMD_FILE" > /dev/null 2>&1 &
El signo & final inicia el comando en segundo plano. Tanto la salida estándar como el error estándar se redirigen a /dev/null.
Tras el intento de ejecución, el hook escribe azuku en .git-checker dentro del directorio temporal. Nunca comprueba ese marcador antes de volver a descargar. Tampoco espera a que el proceso en segundo plano tenga éxito antes de escribir el archivo. Por tanto, el marcador podría existir después de un intento de ejecución fallido.
Las instrucciones originales de descarga, chmod, ejecución en segundo plano y escritura del marcador. El programa de segunda etapa no está presente en este extracto.
Un comentario cerca del final indica que se debe eliminar el directorio temporal de descarga. No hay ningún comando de eliminación debajo.
¿Había una aplicación oculta dentro del archivo?
La descarga por red planteaba otra pregunta. ¿Había ya una aplicación dentro del archivo, quizá en el historial de Git o en el acuerdo de confidencialidad? Examinamos los límites de los contenedores y, a continuación, decodificamos todos los objetos Git suministrados e inspeccionamos el contenido del DOCX.
Los límites del archivo
Registramos el SHA-256 del archivo original antes de inspeccionar su contenido.
6f44ff4e461828dd1fe6ede75ca8d9d4b579c2ac223463629e9a7537b9df5040
La descompresión de gzip como datos produjo un flujo TAR de 890.880 bytes. El flujo gzip terminaba correctamente, sin bytes sin utilizar después. Los últimos 5.120 bytes tras la última entrada TAR eran relleno de ceros.
Después examinamos los archivos y objetos correspondientes a esas entradas de los contenedores.
Comprobaciones guardadas del final del flujo gzip y del relleno TAR. No se ejecutó ningún código de la muestra.
Localización del hook en la cabecera TAR
La cabecera de la entrada post-checkout comienza en 0xCF600, o 849.408, dentro del TAR descomprimido. Sus datos de archivo comienzan en 849.920. Estos desplazamientos se refieren al flujo TAR, no al archivo .gz comprimido.
En ese desplazamiento, la cabecera comienza con la ruta a post-checkout.
TAR / volcado hexadecimal
000cf600 41 49 2d 46 72 61 75 64 2d 44 65 74 65 63 74 69 |AI-Fraud-Detecti|
000cf610 6f 6e 5f 42 6c 6f 63 6b 63 68 61 69 6e 2f 2e 67 |on_Blockchain/.g|
000cf620 69 74 2f 68 6f 6f 6b 73 2f 70 6f 73 74 2d 63 68 |it/hooks/post-ch|
000cf630 65 63 6b 6f 75 74 00 00 00 00 00 00 00 00 00 00 |eckout..........|
000cf640 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000cf650 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000cf660 00 00 00 00 30 30 30 30 37 37 37 00 30 30 30 31 |....0000777.0001|
000cf670 37 35 30 00 30 30 30 31 37 35 30 00 30 30 30 30 |750.0001750.0000|
000cf680 30 30 30 35 33 32 36 00 31 35 32 35 32 30 30 34 |0005326.15252004|
000cf690 33 30 37 00 30 32 34 30 37 35 00 20 30 00 00 00 |307.024075. 0...|
000cf6a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
En una cabecera TAR, mode ocupa los bytes [100:108] y size ocupa [124:136]. Los campos decodificados de esta entrada aparecen después del volcado.
| Campo TAR | Bytes sin procesar / valor |
|---|---|
| mode | b'0000777\x00' |
| size | b'00000005326\x00' |
| Modo octal | 0777 |
| Tamaño decimal | 2774 bytes |
| Suma de comprobación almacenada / calculada | 10301 / 10301 |
El tamaño octal equivale a 2.774 bytes. La suma de comprobación almacenada de la cabecera es 10.301. Al recalcularla con el campo de suma de comprobación rellenado con espacios, se obtiene el mismo resultado.
La cabecera TAR sin procesar del hook y los campos decodificados. Su nombre, permisos y tamaño son visibles en los bytes del contenedor.
También calculamos el hash del hook extraído de esta entrada.
854b3b88b06fad9d9ffec36b35be0133b1d502084d4d7cce232eda2b2dd03092
El volcado de la entrada TAR y el hook anonimizado se incluyen con el artículo. El hash anterior identifica el original sin censurar conservado de forma privada, no la copia censurada para su publicación.
Lectura de objetos Git sin checkout
Hay 24 objetos sueltos en .git/objects, cada uno almacenado en un flujo zlib. Los datos descomprimidos contienen una cabecera Git seguida del cuerpo del objeto.
<type> <size>\0<body>
Comprobamos la longitud declarada del cuerpo y calculamos el SHA-1 sobre toda la secuencia decodificada, incluidas la cabecera y el byte NUL. El resultado debe coincidir con el identificador del objeto en el nombre del archivo. Esto comprueba la integridad del objeto. No dice nada sobre quién lo creó.
Para cada objeto, comparamos la longitud declarada con la longitud del cuerpo y verificamos el hash de todos los datos decodificados.
expanded = zlib.decompress(compressed_object)
header, body = expanded.split(b"\0", 1)
kind, declared_size = header.decode("ascii").split()
assert len(body) == int(declared_size)
assert hashlib.sha1(expanded).hexdigest() == object_id
También comprobamos los finales de los flujos zlib. Los objetos se distribuyen en 16 blobs, 5 árboles y 3 commits. El recorrido de los extremos de las tres ramas, sus commits padres y sus árboles hijos permite localizar los 24 objetos. No hay blobs inaccesibles entre los objetos suministrados.
Git / relaciones entre commits
master 4bc26f6c18910ca777c8238d2f88f6e725fcdc3f
tree feb0feaf7b8e826b085e90dea5399772d7b91f02
parent (none)
conf 239b9aa2edc8c5e21fddeea5ed0e937496ae013f
tree 048b51f9673f94354251ce16a78e663c71667a4c
parent 4bc26f6c18910ca777c8238d2f88f6e725fcdc3f
NDA a1311875d86bf6f1a6b9f2fee6527abbd1ce647d
tree cc020d922090835ea4ac3a3e418d0213d7a2ad94
parent 239b9aa2edc8c5e21fddeea5ed0e937496ae013f
El inventario completo de objetos incluye los objetos que están fuera del árbol de trabajo actual.
Inventario de los 24 objetos Git
| Identificador del objeto | Tipo | Cuerpo, en bytes |
|---|---|---|
| 08e2cda2f440c1792c7c35a3499bfd1ade3e9bad | blob | 6497 |
| 22bb86aacf377c17c7ad0d52c5cefeeb93dfa13e | blob | 5123 |
| 2a7091bdd006a2f98a8c795fd7d2a04c8ee53056 | blob | 2610 |
| 39e13459086f36bc7c933d4e7b5d249a22db24bd | blob | 3061 |
| 3f50e45c67dee5aac77147a5418afe551e8871be | blob | 2433 |
| 476dc872c6def747dcdac549b67a8dc80948b7e1 | blob | 3191 |
| 5a51550933b593eb65da458bbdd579f85c082dab | blob | 752100 |
| 5e53fd0880963e1767ad23fb8612b76e31f04003 | blob | 2807 |
| 60a7cbff83445488fd34b12de1814dc56c956963 | blob | 2054 |
| 74d479352822f5cc1d03552f517cfb5b2d7aa6d3 | blob | 2281 |
| 84e694473554c8a7c01f038a1b6ef8d9355405f7 | blob | 2033 |
| bb7b5d8bc28c4a6257f7d87a685c61ba16a3789c | blob | 2425 |
| d83c8e0c416766be34276013944f5b94726e8c8f | blob | 2776 |
| dfd0f171bca257780eb0d9e22ba2eda1db9d4b68 | blob | 8402 |
| e3b57e48010fc25fd12e7f5f25e952aca843e537 | blob | 6316 |
| ed325750862eb6cdc92b216258a00a784312d793 | blob | 3062 |
| 239b9aa2edc8c5e21fddeea5ed0e937496ae013f | commit | 213 |
| 4bc26f6c18910ca777c8238d2f88f6e725fcdc3f | commit | 165 |
| a1311875d86bf6f1a6b9f2fee6527abbd1ce647d | commit | 212 |
| 048b51f9673f94354251ce16a78e663c71667a4c | tree | 438 |
| 2cb70db8d9317603cb5f0fee0029b66581a7162e | tree | 104 |
| 61874365e5498a30c8b61ef63bbb1d28b4cf4e22 | tree | 320 |
| cc020d922090835ea4ac3a3e418d0213d7a2ad94 | tree | 57 |
| feb0feaf7b8e826b085e90dea5399772d7b91f02 | tree | 104 |
Longitud y SHA-1 verificados sobre la codificación Git original de cada objeto. Se superaron las 24 comprobaciones.
Seguimiento del acuerdo de confidencialidad desde su árbol hasta sus bytes
La rama NDA apunta a un árbol que contiene un archivo, Non-Disclosure-Agreement.docx. Una entrada de árbol Git almacena el modo y el nombre del archivo como texto, seguidos de un NUL y 20 bytes sin procesar para el identificador del blob. Analizamos la entrada directamente a partir del cuerpo del árbol.
Árbol de Git / volcado hexadecimal
00000000 31 30 30 36 34 34 20 4e 6f 6e 2d 44 69 73 63 6c |100644 Non-Discl|
00000010 6f 73 75 72 65 2d 41 67 72 65 65 6d 65 6e 74 2e |osure-Agreement.|
00000020 64 6f 63 78 00 5a 51 55 09 33 b5 93 eb 65 da 45 |docx.ZQU.3...e.E|
00000030 8b bd d5 79 f8 5c 08 2d ab |...y.\.-.|
Bytes sin procesar del árbol NDA y la referencia decodificada a su blob DOCX.
Esa entrada hace referencia al siguiente blob.
5a51550933b593eb65da458bbdd579f85c082dab
Tras la descompresión zlib, el objeto comienza con blob 752100\0. Inmediatamente después de la cabecera están los bytes 50 4b 03 04, la firma PK de un contenedor ZIP.
Blob de Git / tras la descompresión zlib
00000000 62 6c 6f 62 20 37 35 32 31 30 30 00 50 4b 03 04 |blob 752100.PK..|
00000010 0a 00 00 00 00 00 87 4e e2 40 00 00 00 00 00 00 |.......N.@......|
00000020 00 00 00 00 00 00 09 00 00 00 64 6f 63 50 72 6f |..........docPro|
00000030 70 73 2f 50 4b 03 04 14 00 00 00 08 00 87 4e e2 |ps/PK.........N.|
00000040 40 a5 76 12 16 69 01 00 00 7b 02 00 00 10 00 00 |@.v..i...{......|
00000050 00 64 6f 63 50 72 6f 70 73 2f 61 70 70 2e 78 6d |.docProps/app.xm|
00000060 6c 9d 92 c1 4e c3 30 10 44 ef 48 fc 43 94 7b 62 |l...N.0.D.H.C.{b|
Bytes del objeto comprimido, cabecera Git decodificada y comienzo del DOCX. La longitud y el identificador del objeto se comprobaron sobre el objeto completo.
El cuerpo tiene una longitud de 752.100 bytes. Su SHA-256 coincide con el DOCX que guardamos por separado para su análisis.
9d85170d2c0aa7a9b90dfc3855715a31d971bba7abb563e9efa1153fdc15634f
Estos objetos permiten seguir la ruta desde la rama NDA hasta los bytes del documento.
Identificación de los 16 blobs
Quince blobs se decodifican como documentos de proyecto Markdown en UTF-8. El blob restante es el DOCX anterior. Ninguno comienza con una cabecera PE, ELF o Mach-O.
| Ruta en los árboles | Tamaño, en bytes | Tipo / primeros bytes |
|---|---|---|
| 04-solution-proposal-guidance.md | 2054 | Texto UTF-8 · 23 20 44 65 76 65 6c 6f 70 6d 65 6e 74 20 43 6f |
| 06-blockchain-architecture.md | 6497 | Texto UTF-8 · 23 20 42 6c 6f 63 6b 63 68 61 69 6e 20 41 72 63 |
| 07-blockchain-integration-specification.md | 2807 | Texto UTF-8 · 23 20 42 6c 6f 63 6b 63 68 61 69 6e 20 49 6e 74 |
| 08-smart-contract-specification.md | 2610 | Texto UTF-8 · 23 20 53 6d 61 72 74 20 43 6f 6e 74 72 61 63 74 |
| 09-data-and-api-architecture.md | 2776 | Texto UTF-8 · 23 20 44 61 74 61 20 26 20 41 50 49 20 41 72 63 |
| 11-ai-fraud-and-risk-engine.md | 3062 | Texto UTF-8 · 23 20 41 49 20 46 72 61 75 64 20 44 65 74 65 63 |
| 14-deliverables-acceptance-and-milestones.md | 6316 | Texto UTF-8 · 23 20 44 65 6c 69 76 65 72 61 62 6c 65 73 2c 20 |
| Detailed/03-business-and-use-cases.md | 3061 | Texto UTF-8 · 23 20 42 75 73 69 6e 65 73 73 20 52 65 71 75 69 |
| Detailed/05-non-functional-requirements.md | 2033 | Texto UTF-8 · 23 20 4e 6f 6e 2d 46 75 6e 63 74 69 6f 6e 61 6c |
| Detailed/10-security-and-privacy-requirements.md | 2433 | Texto UTF-8 · 23 20 53 65 63 75 72 69 74 79 20 26 20 50 72 69 |
| Detailed/12-testing-and-quality-requirements.md | 2425 | Texto UTF-8 · 23 20 54 65 73 74 69 6e 67 20 26 20 51 75 61 6c |
| Detailed/13-deployment-monitoring-and-operations.md | 2281 | Texto UTF-8 · 23 20 44 65 70 6c 6f 79 6d 65 6e 74 2c 20 4d 6f |
| Intro/01-project-overview.md | 8402 | Texto UTF-8 · 23 20 50 72 6f 6a 65 63 74 20 4f 76 65 72 76 69 |
| Intro/02-product-requirements.md | 3191 | Texto UTF-8 · 23 20 50 72 6f 64 75 63 74 20 52 65 71 75 69 72 |
| Non-Disclosure-Agreement.docx | 752100 | DOCX/ZIP · 50 4b 03 04 0a 00 00 00 00 00 87 4e e2 40 00 00 |
| README.md | 5123 | Texto UTF-8 · 23 20 41 49 20 46 72 61 75 64 20 44 65 74 65 63 |
Se clasificaron los 16 blobs suministrados. Los árboles contienen documentos del proyecto; no se encontró ninguna implementación del producto.
Leímos los cuerpos de texto además de comprobar sus firmas. El DOCX era el único blob no textual y requería una inspección independiente de su contenedor.
Apertura del DOCX como ZIP
El documento contiene 25 entradas ZIP, compuestas por 17 partes de archivo y 8 directorios. Leímos cada parte de archivo y comprobamos su CRC.
| Parte del DOCX | Tamaño, en bytes | CRC32 |
|---|---|---|
| [Content_Types].xml | 1482 | ec936159 |
| _rels/.rels | 737 | 1f222201 |
| customXml/_rels/item1.xml.rels | 296 | 7a393f74 |
| customXml/item1.xml | 400 | 4ed0e3fe |
| customXml/itemProps1.xml | 327 | 457b4363 |
| docProps/app.xml | 635 | 161276a5 |
| docProps/core.xml | 595 | d5626da7 |
| docProps/custom.xml | 722 | 8d44908d |
| word/_rels/document.xml.rels | 1086 | 22475620 |
| word/_rels/settings.xml.rels | 398 | e5802c0c |
| word/document.xml | 91835 | 4df71596 |
| word/fontTable.xml | 4234 | 2796cccd |
| word/media/image1.png | 712863 | 09063019 |
| word/media/image2.png | 10193 | 3aea21cf |
| word/settings.xml | 4116 | 34faa9ef |
| word/styles.xml | 30020 | b4e9edbd |
| word/theme/theme1.xml | 6329 | ed5975e1 |
Nombres, longitudes y valores CRC32 de las partes del DOCX. Inspeccionamos los datos ZIP y XML sin abrir el documento en Office.
No hay ningún vbaProject.bin ni adjunto OLE en la lista de partes. Dos archivos PNG contienen un logotipo y una imagen de firma. También inspeccionamos las relaciones XML y encontramos una referencia a una ruta de plantilla ofimática local en la máquina del autor del documento. No la resolvimos ni la cargamos.
La imagen de firma y los campos de autor de Git no nos ofrecen una forma fiable de identificar al remitente. Ambos pueden copiarse o sustituirse.
No encontramos ninguna aplicación independiente ni implementación del producto en el contenido suministrado. El hook obtiene su siguiente programa a través de la red. Nuestra inspección de los contenedores no descarta todas las técnicas posibles de explotación o esteganografía.
Qué devolvió el servidor de descarga
Recuperamos las tres URL exactas como datos, sin ejecutar las respuestas ni seguir redirecciones. El 19 de octubre, entre las 17:29:27 y las 17:29:28 UTC, cada una devolvió HTTP 200. Los tres cuerpos contenían el mismo mensaje de 21 bytes.
Permanently suspended
Las tres observaciones guardadas de los endpoints. No hicimos más solicitudes a las URL del descargador durante la preparación de este artículo.
No disponemos de bytes de segunda etapa sobre los que hacer ingeniería inversa. Tampoco sabemos qué devolvían esos endpoints antes, a otro visitante o bajo otras condiciones de solicitud. El mensaje de suspensión no nos ofrece una explicación verificada de por qué se suspendió el servicio.
El hook intenta descargar y ejecutar un archivo externo. Las afirmaciones sobre robo de contraseñas, tokens, código fuente o carteras requerirían pruebas que no tenemos.
¿Podría haber sido una utilidad legítima?
Un actualizador interno podría acabar en un archivo por error. Los hooks de Git, los procesos en segundo plano y curl tienen usos legítimos.
En este envío, el correo nos pedía explícitamente que visitáramos otra rama. Hacerlo podía invocar el descargador suministrado, aunque el motivo declarado para cambiar de rama era leer un acuerdo. El correo no mencionaba la ejecución. Tampoco encontramos ninguna conexión entre un actualizador de navegador y el sistema propuesto, y la comprobación de tamaño del script estaba incompleta.
Tratamos el envío como un intento de ataque mediante entrega de código e impedimos que el hook se ejecutara. Reconsiderar esa valoración requeriría el código fuente de los archivos descargados y una explicación verificable de por qué un contratista necesitaba ejecutarlos para revisar el acuerdo de confidencialidad.
No hemos establecido la identidad del operador. La dirección del remitente, los metadatos de los commits y los nombres dentro del documento no bastan para atribuir la operación.
Diagrama de análisis de los pasos de ejecución descritos por el hook suministrado.
Revisión de un repositorio Git desconocido
Un desarrollador que siguiera el correo podía llegar al desencadenante de ejecución mientras aún leía información sobre el proyecto. No era necesario compilar ni iniciar la aplicación propuesta.
Si revisa envíos de clientes en una máquina con claves SSH de producción o tokens de la nube, un script inesperado puede tener acceso a esos recursos. Un entorno desechable debe mantener esas credenciales fuera de su alcance. Las sesiones del navegador en la máquina de revisión merecen la misma consideración.
La revisión debe incluir el directorio .git suministrado y cualquier automatización del IDE antes de que un ingeniero empiece a utilizar sus comandos habituales. Los scripts de instalación también necesitan un examen minucioso. En los registros del host, buscaríamos Git o un shell descargando un archivo en un directorio temporal y ejecutándolo después. Un proceso curl por sí solo nos dice mucho menos.
Conservar el correo y el archivo originales permite que una persona los revise más adelante y compruebe qué se entregó. Los hashes y los eventos del host ayudan a establecer qué ocurrió en la máquina. La ausencia de un marcador no permite establecer que un programa nunca se ejecutó.
¿Descargar o extraer el archivo puede ejecutar este hook?
La descarga y la extracción ordinarias no invocan por sí mismas el hook post-checkout de Git. En esta muestra, el desencadenante documentado es la actualización del árbol de trabajo por parte de Git mediante checkout o switch, cuando se permite la ejecución de hooks. Inspeccionamos directamente los objetos suministrados sin invocar esas operaciones.
Después de la revisión del archivo
Esperábamos que el siguiente intercambio tratara de la arquitectura y las fechas de entrega. En su lugar, enviamos una respuesta sobre el hook y conservamos el material para una revisión posterior. Seguimos sin poder decir qué hacía el programa remoto ni quién lo operaba. El hook nos dio motivos suficientes para detener el flujo de trabajo de Git solicitado.
Desde hace más de cinco años, SToFU Systems y nuestras soluciones ayudan a las empresas a proteger sus sistemas, también frente a ataques enviados mediante correspondencia comercial y materiales de desarrollo. Nuestras auditorías de seguridad examinan cómo se pueden utilizar indebidamente el código y los accesos. También ofrecemos ingeniería inversa cuando un equipo necesita comprender software desconocido.
Si un repositorio o un binario plantea dudas, hable con nuestro equipo sobre una revisión técnica antes de incorporarlo a su entorno de desarrollo habitual.
Correspondencia y archivos de pruebas
Los cuatro mensajes siguientes utilizan marcadores de posición para nombres de personas, direcciones, identidades de terceros y enlaces identificativos. Se conserva el resto del texto de cada mensaje nuevo. Se omiten las citas repetidas porque cada correo aparece por separado. También se ocultan la IP del servicio del formulario web y las direcciones de destinatarios ajenos al caso.
1. La primera consulta a través de nuestro sitio web
Correo / datos identificativos sustituidos
Date: Thu, 15 Oct 2026 12:55:21 +0000
From: SToFU Site Form <TEAM_EMAIL>
To: <TEAM_EMAIL>
Subject: SToFU Systems - Contact by <SENDER_NAME> <SENDER_EMAIL>
Reply-To: <SENDER_NAME> <SENDER_EMAIL>
Time: 2026-10-15T12:55:21+00:00
IP: [redacted]
Page: /contact
Name: <SENDER_NAME>
Email: <SENDER_EMAIL>
Attachments: none
Message:
We are currently developing an AI-powered FinTech fraud detection platform designed to help financial organizations identify suspicious transactions, assess potential risks in real time, and strengthen their fraud prevention and investigation processes.
The planned platform will incorporate AI/ML-based fraud detection, transaction risk scoring, behavioral analysis, configurable fraud-detection rules, blockchain-based verification, automated alerts, investigation support, and reporting and analytics.
We are looking to connect with an experienced technology partner with expertise in blockchain, financial technology, AI/ML, and scalable web platform development to help us bring this product to life.
We would welcome the opportunity to discuss your team’s capabilities, experience, and potential role in the project. The specific scope of your team’s involvement, technical responsibilities, and collaboration model can be determined through our discussion, based on your expertise and recommendations.
If this opportunity aligns with your team’s capabilities and interests, I’d be happy to share the project overview and technical roadmap for an initial review.
---
La primera consulta a través de nuestro sitio web. Vista anonimizada para publicación del correo conservado.
SHA-256 del EML privado sin censurar: bf7d0f3a59b84f509ca436e0750640e844e9731ce363a42a99d5be60cdf8597e
2. Nuestra respuesta solicitando los materiales
Correo / datos identificativos sustituidos
Date: Sat, 17 Oct 2026 19:46:28 -0000
From: <TEAM_MEMBER_NAME> <TEAM_EMAIL>
To: <SENDER_NAME> <SENDER_EMAIL>
Subject: Re: Your AI fraud detection platform
Cc: [redacted: third-party addresses]
Hi <SENDER_NAME>,
Thank you for getting in touch through our website. I'm <TEAM_MEMBER_NAME>, <TEAM_MEMBER_ROLE> of SToFU Systems (<COMPANY_WEBSITE>). You can find me on <PROFESSIONAL_NETWORK> here:
<PROFILE_URL>
Your fraud detection platform sounds like a great project. Helping financial teams spot suspicious transactions and investigate them more effectively is a worthwhile problem to solve, and we'd be very happy to help you bring it to life.
We'd be glad to help with the architecture, development, security, and ongoing maintenance. We'll do everything we can to get the platform launched and keep it running reliably as it grows.
If you plan to seek investment, we can also help prepare the technical side for those conversations, including a working product or demo, clear architecture and documentation, and a practical development roadmap.
Please send over the project overview and technical roadmap you mentioned. It would be helpful to understand what you've already built and what you'd like to tackle first.
I'd be happy to arrange a call to discuss the project and how we could work together. What day and time would suit you? Please include your time zone so we can find a slot that works for both teams.
Thank you again for reaching out, and have a great day!
Best regards,
<TEAM_MEMBER_NAME>
<TEAM_MEMBER_ROLE>
SToFU Systems
<COMPANY_WEBSITE>
Nuestra respuesta solicitando los materiales. Vista anonimizada para publicación del correo conservado.
SHA-256 del EML privado sin censurar: 1e5330dd82e02911254722744a3202e9b761c8c1d2950be937cd5d539c1bcad9
3. El correo del archivo que indicaba las ramas NDA / conf
Correo / datos identificativos sustituidos
Date: Mon, 19 Oct 2026 09:03:41 +0000
From: <SENDER_EMAIL>
To: <TEAM_EMAIL>
Subject: Re: Your AI fraud detection platform
Hi <TEAM_MEMBER_NAME>,
Thank you for your interest in our AI-powered FinTech fraud detection platform.
I’ve also reviewed your <PROFESSIONAL_NETWORK> profile, and I’m confident that you and your team can make a valuable contribution to our project.
I’m happy to share the project materials for your review.
We’ve prepared the project overview, technical roadmap, and NDA, which provide the key information needed to understand the project, its objectives, and the proposed technical direction.
The platform focuses on AI-driven fraud detection, transaction risk assessment, behavioral analysis, fraud prevention, blockchain-based verification, automated alerts, investigation support, and financial risk analytics.
This is the Link:
<FILE_SHARE_URL>
Please review the project materials and review and sign the NDA in the NDA branch.
The NDA is required because the project materials contain confidential business, technical, architectural, and product information.
So, please send me the signed NDA as soon as possible.
In particular, we would appreciate your initial technical assessment of the proposed system architecture and timeline. The detailed documentation and specifications related to these areas are available in the conf branch.
After your review, we would like to arrange a meeting to discuss your technical observations, recommendations, questions, development approach, and potential partnership opportunities in more detail.
Once you have completed your review, please let me know your preferred meeting date and time and provide the signed NDA.
Please feel free to take the time you need to review the materials thoroughly. I look forward to receiving your feedback and speaking with you about the project.
Kind regards,
<SENDER_NAME>
El correo del archivo que indicaba las ramas NDA / conf. Vista anonimizada para publicación del correo conservado.
SHA-256 del EML privado sin censurar: f7007ed3053d3ba2ef71ea01c74ec0c4aebfd48417f757ecec8d9d7d5884bf33
4. Nuestra respuesta tras descubrir el hook
Correo / datos identificativos sustituidos
Date: Mon, 19 Oct 2026 19:37:58 +0200
From: SToFU Systems <TEAM_EMAIL>
To: <SENDER_NAME> <SENDER_EMAIL>
Subject: Re: Your AI fraud detection platform
Hi <SENDER_NAME>,
We reviewed the materials you sent. Unfortunately, we found no application source code or working implementation, only planning documents and an NDA.
Execution of the supplied Git post-checkout hook was disabled during our review. The hook downloads and runs platform-specific files from <DOWNLOAD_DOMAIN>, hides their output, and writes a local marker. Our security policies do not allow the execution of untrusted third-party hooks.
Thank you for your time and for the elaborate attempt to deliver malicious code.
We have traced the technical delivery chain and preserved the archive, hook, download responses, correspondence, and author names and email addresses recorded in the repository. The relevant evidence will be passed to the competent law enforcement authorities in the applicable jurisdictions.
SToFU Systems
Nuestra respuesta tras descubrir el hook. Vista anonimizada para publicación del correo conservado.
SHA-256 del EML privado sin censurar: 31df91adb2eb52be2171836d3e26610328a0a1a1273dbf7d56efe4354bb62e19
- Hook anonimizado, las 101 líneas
- Cabecera TAR y comienzo de los datos del archivo
- Los 24 objetos Git
- Blob DOCX antes y después de zlib
- Volcado del árbol NDA
- Los 16 blobs y sus primeros bytes
- Todas las partes del DOCX
- Límites de gzip y TAR
- Inspección a nivel de bytes en formato legible por máquina
- Observaciones guardadas de los tres endpoints
- Fuentes y hashes de las figuras
- Procedencia de la correspondencia
Vista anonimizada del hook completo
Se conservan los 101 números de línea del código fuente. Los datos identificativos están cubiertos con rectángulos grises.
Los originales sin censurar se conservan de forma privada. Los archivos de pruebas públicos utilizan marcadores de posición, y las capturas de pantalla utilizan rectángulos grises opacos. Los hashes etiquetados como originales identifican la fuente privada, no una copia editada para publicación. Las figuras son capturas de pantalla de vistas locales de las pruebas, con el texto identificativo eliminado antes del renderizado. No se ejecutó ningún código recibido.















