Scripts de usuario de sandboxing de forma segura en un programa C ++

8

He estado trabajando en un proyecto personal en C # cuyo propósito es más o menos permitir al usuario ejecutar scripts escritos por otros usuarios y restringir los permisos de ese script. Mi programa compila los scripts con una biblioteca de terceros, los guarda con los mecanismos de seguridad de acceso al código .NET y se asegura de que solo tengan los permisos que el usuario desea otorgarles.

En términos generales, mis requisitos de seguridad son:

  • el usuario debe poder restringir el acceso del script no confiable a solo ciertas partes del sistema de archivos, incluida la prohibición de todo acceso al sistema de archivos
  • el usuario debe poder restringir las conexiones de red del script no confiable a ciertas direcciones IP o nombres de host, incluida la prohibición de todas las conexiones de red
  • está bien si la secuencia de comandos de usuario logra bloquear o finalizar la aplicación host, pero la secuencia de comandos de usuario no debe ser capaz de eludir las restricciones de permisos (es decir, la denegación de servicio está bien, el incumplimiento no lo es)

Estoy contemplando intentar hacer algo similar en C ++, como una especie de ejercicio personal. Obviamente, las cosas son más complicadas cuando se ejecuta código nativo directamente, incluso si los scripts de usuario están escritos en un lenguaje de scripting como Lua.

El primer enfoque que se me ocurre es insertar mis propios ganchos en las funciones de biblioteca estándar de los entornos de secuencias de comandos. Por ejemplo, si el lenguaje de script es Lua, en lugar de exponer io.open normalmente, tendría que exponer un contenedor que verificara los argumentos contra los permisos del script antes de pasarlos a la implementación original.

Mi preocupación con este enfoque es que aumenta drásticamente la cantidad de mi propio código que está a cargo de la seguridad y, por lo tanto, una posible vulnerabilidad de seguridad que escribí yo mismo. En otras palabras, cuando trabajo con .NET CAS, puedo confiar en que Microsoft hizo bien su trabajo en el código de sandboxing, en lugar de tener que confiar en mi propio código de sandboxing.

¿Hay alguna alternativa que desconozca?

Vojislav Stojkovic
fuente
Las técnicas de contenedorización de @RobertHarvey Linux como Seccomp generalmente solo ayudan cuando se ejecuta el código no confiable en un proceso separado, pero no son aplicables para el aislamiento en proceso. Por supuesto, una solución bastante simple al problema podría incluir exactamente eso, un proceso separado que actúa como un servicio para el programa principal y solo se comunica a través de los mecanismos de IPC. Me pregunto si Windows tiene características de aislamiento similares a nivel de sistema operativo.
amon
1
@amon: ¿Te refieres a un ... proceso de Windows ? Claro que si.
Robert Harvey
En primer lugar, ya ha introducido la mayor vulnerabilidad en su sistema: Código extranjero . Todo lo demás es un intento de mitigar ese daño. Esencialmente, está construyendo un intérprete que, con suerte, ejecuta ese código extranjero de forma segura. Vaya y repase su comprensión de la interpretación, luego aplique esas ideas para hacer cumplir los comportamientos y la seguridad relativa que desea. En segundo lugar, no presuma que un sandbox es seguro, siempre agregue capas adicionales de defensa.
Kain0_0
1
@VojislavStojkovic ¿No puedes usar un motor de JavaScript? Por defecto, estos motores no tienen acceso al sistema de archivos (no estoy completamente seguro de la red, pero puede verificarlo). Un ejemplo es QJSEngine . También con estos motores puede proporcionar funciones de código C ++ personalizadas a JavaScript para la interoperabilidad.
RandomGuy

Respuestas:

1

Como han dicho otros, ejecutar código extranjero es el mayor problema con respecto a este tipo de implementación. Como sugirió el comentario de Kain0_0, una máquina virtual sería la forma más adecuada de mantener la libertad de código extranjero sin comprometer la máquina host (demasiado). Esto es básicamente lo que hacen los servicios de integración continua como CircleCI.

Esto también facilita mucho la implementación de la interfaz, ya que puede extraer imágenes con todas las características de configuración y seguridad que desee. Tampoco tiene que preocuparse si su código se ejecutará en su host.

Entonces para esto, yo:

  • Hacer instantáneas de los entornos de script de usuario que quiero cubrir con Docker (un entorno para C #, uno para Python, etc., con las configuraciones de seguridad apropiadas)

  • A petición del usuario, active las instancias relevantes de Docker a través del activador de código, inyectando el script foráneo en el punto de entrada de la instancia de Docker.

  • El código se ejecuta en la instancia de Docker con permisos de usuario, los archivos se escriben, tal vez se establece una conexión aquí y allá, se obtiene la salida y luego se destruye el entorno

  • Dado que los contenedores Docker se ejecutan como procesos, pueden terminarse con bastante facilidad, es decir, si exceden un cierto límite de tiempo. Si hay un error, pueden terminar de inmediato.

Básicamente, haga que su código principal administre la activación del usuario, la inyección del punto de entrada y la lógica de destrucción automatizada para Docker, que se encarga de hacer el sandboxing para este tipo de operación.

lucasgcb
fuente
1
Gran respuesta. Si hay algo que agregar a su respuesta, sería que todas las soluciones populares de sandbox mostraban numerosas fallas que permitían que el código malicioso escapara de su sandbox. Esto es cierto para CAS (por ejemplo, CVE-2015-2504), e incluso más cierto para Java (los exploits para el complemento Java del navegador son demasiado numerosos para enumerarlos aquí). Si bien Docker también tiene vulnerabilidades, tengo la impresión de que la superficie de ataque es mucho menor en comparación con cualquier cosa implementada a nivel de un lenguaje / marco determinado.
Arseni Mourzenko