Skip to main content

Uso de valores predeterminados inseguros del administrador de paquetes npm para robar tus atajos de teclado en macOS

Escrito por
feature open source

28 de junio de 2023

0 minutos de lectura

Los paquetes maliciosos de npm y sus peligros han sido un tema de debate frecuente, ya se trate de cientos de paquetes de malware Cobalt Strike de comando y control, del typosquatting o de malware general publicado en el registro de npm (incluidos PyPI y otros). Para ayudar a desarrolladores y mantenedores a defenderse de estos riesgos de seguridad, Snyk publicó una guía de mejores prácticas de seguridad para npm.

Dicho esto, el siguiente alcance de ataque, sobre el que Yagiz Nizipli alertó a mantenedores de larga trayectoria, y el riesgo real relacionado con el compromiso de datos son un gran ejemplo de lo importante que es minimizar los riesgos de ejecutar comandos arbitrarios con administradores de paquetes, como los que se ejecutan mediante los hooks del ciclo de vida postinstall de npm.

Scripts del ciclo de vida de npm

Node Package Manager (npm) proporciona un conjunto de scripts para que desarrolladores y mantenedores de paquetes administren los eventos del ciclo de vida de un paquete. Estos scripts aportan un valor considerable, ya que permiten realizar distintas tareas o configuraciones como parte del proceso de instalación del paquete. Por ejemplo, con los scripts postinstall, los desarrolladores pueden automatizar tareas como compilar recursos, configurar variables de entorno, ejecutar migraciones o cualquier otra tarea que pueda ejecutarse automáticamente.

La propiedad scripts de un archivo package.json define los comandos que se activan durante el ciclo de vida del paquete y de las dependencias del paquete que estás desarrollando. Actualmente, npm admite un número limitado de scripts del ciclo de vida en cualquier propiedad scripts de un archivo package.json.

Para simplificar, el resto de este artículo se centrará en el comando postinstall. Sin embargo, todos los conceptos que se presentan aquí también se aplican a otras operaciones del ciclo de vida.

Incidentes de seguridad anteriores

Ha habido varios incidentes de alto perfil con un impacto real en los desarrolladores de JavaScript, entre ellos:

En el repositorio Awesome Node.js Security se recopilan muchos otros incidentes de seguridad relacionados con JavaScript y Node.js.

Seguridad de los datos en reposo

Los profesionales de seguridad llaman Data-at-rest a la protección de los activos cuando los datos están almacenados o inactivos, a diferencia de cuando están en tránsito o se están procesando. Se centra en proteger la información confidencial almacenada en bases de datos, sistemas de archivos o almacenamiento persistente. Su objetivo es evitar el acceso no autorizado, la divulgación o la manipulación de datos mientras están inactivos. Hay varias medidas para garantizar la seguridad de los datos en reposo, como:

  • Descifrado a pedido: descifrar solo los datos necesarios para realizar la tarea actual y mantener el resto cifrado para evitar accesos no autorizados.

  • Lógica de control de acceso: validar la identidad de quien realiza la solicitud mediante mecanismos como una contraseña, la autenticación de dos factores o datos biométricos del sistema operativo (como Face ID), lo que permite limitar la exposición del recurso a personas no autorizadas.

La superficie de ataque de un desarrollador

Las mejores prácticas del sector nos exigen usar y seguir ciertos principios para desarrollar aplicaciones. Estas prácticas ofrecen muchas ventajas al trabajar con distintos equipos y desarrolladores, pero también aumentan la superficie de ataque.

¿Qué tipo de datos quedan almacenados sin cifrar en la máquina de un desarrollador?

  • Variables de entorno en archivos de texto sin formato, como .env (disponibles para su uso mediante el paquete dotenv).

  • Archivos de configuración de proyectos almacenados como archivos JSON, por ejemplo, config.json.

  • Claves SSH para acceder a Github/Gitlab, disponibles en la carpeta ~/.ssh.

  • Y... ¡atajos de teclado de macOS!

Reemplazos de texto de macOS

macOS incluye de forma predeterminada una función llamada Text Replacements, oculta en la aplicación Preferencias del Sistema. Esta función permite reemplazar rápidamente una palabra por otra. Hace poco me enteré de que un desarrollador de una empresa conocida usaba los reemplazos de texto para sustituir la palabra clave @card por los datos de su tarjeta de crédito. Aunque el número de la tarjeta, sin la fecha de vencimiento ni el CVV, no permite que terceros usen tu dinero, sí amplía la superficie de ataque que podrían aprovechar.

Configuración del teclado de macOS que muestra la sustitución de texto “@ssh-key” por “mi-contraseña-secreta”
La función Reemplazos de texto está disponible en la aplicación Preferencias del Sistema, en la opción de menú `Teclado`.

Exfiltración de reemplazos de texto del teclado 

Los atajos de teclado se almacenan en defaults, que corresponde a un archivo .plist respaldado por el sistema de archivos en alguna ubicación de tu carpeta local. Al ejecutar el siguiente comando, obtendrás los reemplazos de texto configurados, que también están disponibles en la aplicación Preferencias del Sistema.

Recuerda que el siguiente código no requiere acceso a sudo y puede ejecutarlo cualquier proceso en tu computadora.

> defaults read -g NSUserDictionaryReplacementItems
(
  {
    on = 1;
    replace = "@ssh-key";
    with = "my-secret-password";
  }
)

El mismo comando se puede ejecutar mediante execSync en Node.js y analizarse sin complicaciones mediante la operación del ciclo de vida postinstall que admite el administrador de paquetes npm.

A continuación se muestra un ejemplo de un script de Node.js que actores maliciosos pueden usar para acceder a los reemplazos de texto de macOS y exfiltrar datos confidenciales:

import { execSync } from 'node:child_process'

const decoder = new TextDecoder()
const res = execSync('defaults read -g NSUserDictionaryReplacementItems')
const text_replacements = decoder.decode(res)

console.log(text_replacements)

Para asegurarnos de que el código anterior se ejecute al instalar este paquete, actualizaremos el archivo de manifiesto del paquete, package.json, de la siguiente manera:

{
  "name": "my-useful-library",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "postinstall": "node ./retrieve.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Al distribuirse a través de npm y descargarse por un desarrollador, esta biblioteca ejecutará directamente nuestro script personalizado para recuperar y procesar los reemplazos del teclado. Si no tienes cuidado, es fácil pasar por alto la línea que contiene > node ./retrieve.js.

➜  vulnerable npm i

> my-useful-library@1.0.0 postinstall
> node ./retrieve.js

up to date, audited 1 package in 192ms

found 0 vulnerabilities
➜  vulnerable

Protección

¿Qué puedes hacer como desarrollador para mitigar los riesgos de seguridad de los paquetes maliciosos de npm y las preocupaciones generales de seguridad relacionadas con la ejecución de comandos arbitrarios desde los paquetes de tu árbol de dependencias?

Ignorar scripts al instalar paquetes de npm

Puedes protegerte de los paquetes que usan scripts postinstall. npm ofrece la configuración --ignore-scripts para instalar paquetes.

➜  npm i <package name> --ignore-scripts
up to date, audited 1 package in 124ms
found 0 vulnerabilities

Usar valores predeterminados seguros de npm

El administrador de paquetes npm también tiene un archivo de configuración llamado .npmrc. Puedes cambiar las preferencias predeterminadas con la CLI de npm para garantizar una configuración segura:

➜  npm config set ignore-scripts true
➜  npm i
up to date, audited 1 package in 126ms
found 0 vulnerabilities

Almacenamiento seguro

Y, lo más importante, nunca debes almacenar información confidencial en texto sin formato. Si otros requisitos te obligan a hacerlo, asegúrate de que el recurso solo sea accesible mediante autenticación multifactor.

Empieza con los desafíos de Capture the Flag

Aprende a resolver desafíos de captura la bandera con nuestro taller virtual introductorio a pedido.

Leer más

illustration hero ai
Blog

PHP corrige dos vulnerabilidades críticas de RCE sin autenticación (CVE-2026-109618, CVE-2026-109619): actualiza ahora

Dos fallas críticas en el ciclo de solicitudes de PHP pueden permitir la ejecución remota de código sin autenticación. Actualiza a una versión corregida o instala una corrección retroportada por un proveedor para las versiones anteriores.

AI Background min crop
Blog

La IA activa todos los riesgos que has aceptado: siete ideas del panel de seguridad de IA sin filtros

La IA hace que los ataques sean más baratos y rápidos, y deja al descubierto riesgos que las organizaciones ya enfrentan. Cuatro líderes de seguridad explican por qué la defensa a velocidad de máquina y la corrección más rápida son esenciales.

blog feature ai violet
Blog

VulnBump: ¿Pueden los agentes de IA corregir dependencias vulnerables sin romper la compilación?

VulnBump prueba agentes de IA con 307 actualizaciones reales de dependencias. Una corrección cuenta solo si elimina la vulnerabilidad y la compilación y las pruebas siguen pasando.