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?
Respuestas:
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.
fuente