
Exploit para CVE-2022-25173 que apunta a la omisión del sandbox del intérprete CPS del complemento Jenkins Pipeline Groovy, permitiendo la ejecución arbitraria de código dentro del controlador de Jenkins mediante scripts de Pipeline manipulados.
Un componente clave del conjunto de complementos de Pipeline, este proporciona el motor de ejecución estándar para los pasos de Pipeline, basado en un intérprete personalizado de Groovy que se ejecuta dentro del proceso del controlador de Jenkins.
(En principio, se podrían admitir otros motores de ejecución, siendo FlowDefinition el punto de entrada de la API, pero ninguno ha sido prototipado y probablemente sería un esfuerzo muy sustancial escribir uno.)
El código de script de Pipeline Groovy, como
retry(3) {
for (int i = 0; i < 10; i++) {
branches["branch${i}"] = {
node {
retry(3) {
checkout scm
}
sh 'make world'
}
}
}
parallel branches
se ejecuta como un programa Groovy, con ciertas llamadas especiales a funciones llamadas steps (pasos) que realizan operaciones específicas de Jenkins.
En este ejemplo, el paso parallel está definido en este complemento, mientras que node, retry, checkout y sh están definidos en otros complementos del conjunto de Pipeline. La variable global scm está definida en el complemento Pipeline Multibranch.
El script Groovy se compila en una clase llamada WorkflowScript, por lo que ese es el nombre que se muestra en los stack traces en lugar del nombre de archivo del script (por ejemplo, Jenkinsfile).
A diferencia de un programa Groovy normal ejecutado desde una línea de comandos, el estado completo del programa de una compilación de Pipeline se guarda en disco cada vez que se realiza una operación asíncrona, lo que incluye la mayoría de los pasos de Pipeline.
Jenkins puede reiniciarse mientras una compilación está en ejecución y reanudará la ejecución del programa donde lo dejó.
No se pretende que esto sea eficiente, por lo que debe limitarse a código "pegamento" de alto nivel directamente relacionado con las características de Jenkins;
la lógica de compilación propia de tu proyecto debe ejecutarse desde programas externos en un nodo de compilación, en un paso sh o bat.
El épico Pipeline Groovy en JIRA cubre algunas limitaciones conocidas del intérprete de Groovy. Estos problemas se derivan del hecho de que Pipeline no puede ejecutar Groovy directamente, sino que debe interceptar cada operación para guardar el estado del programa.
El épico Pipeline Sandbox cubre problemas con el sandbox de Groovy utilizado para evitar que scripts maliciosos de Pipeline tomen el control de Jenkins. Los scripts ejecutados con el sandbox deshabilitado pueden realizar llamadas directas a las API internas de Jenkins, lo que puede ser un workaround útil para la falta de funcionalidad de algunos pasos, pero por razones de seguridad solo los administradores pueden aprobar dichos scripts.
El épico Pipeline Snippet Generator cubre problemas con la herramienta utilizada para proporcionar ejemplos de sintaxis de pasos basados en formularios de configuración en vivo.
Este complemento anteriormente era el "Workflow CPS plugin" o "Workflow Groovy Plugin". En consecuencia, tiene el artifactId de Maven workflow-cps, no pipeline-groovy.
El complemento utiliza la biblioteca Groovy CPS para implementar una transformación de estilo de paso de continuación en el programa a medida que se compila.
El compilador estándar de Groovy se utiliza para crear el AST, pero la generación de bytecode es interceptada por un CompilationCustomizer que reemplaza la mayoría de las operaciones con variantes que lanzan un "error" especial, CpsCallableInvocation.
Este es capturado por el motor, que utiliza la información que contiene (como los argumentos que están a punto de pasarse a una llamada de método) para transferir el control a la siguiente continuación.
Los scripts de Pipeline pueden marcar métodos designados con la anotación @NonCPS.
Estos se compilan normalmente (excepto por las comprobaciones de seguridad del sandbox) y, por lo tanto, se comportan de manera muy similar a los métodos "binarios" de la plataforma Java, el runtime de Groovy o el código de Jenkins core o de sus complementos.
Los métodos @NonCPS pueden usar de forma segura objetos no Serializable como variables locales, aunque no deben aceptar parámetros no serializables ni devolver o almacenar valores no serializables.
No se pueden llamar métodos regulares (transformados por CPS), ni pasos de Pipeline, desde un método @NonCPS, por lo que se usan mejor para realizar algunos cálculos antes de devolver un resumen al script principal.
Ten en cuenta en particular que los @Override de métodos definidos en clases binarias,
como Object.toString(),
en general deben marcarse como @NonCPS, ya que comúnmente será código binario el que los llame.
Algunos tipos de objetos son intrínsecamente no seguros de serializar como tales, pero queremos conservar una referencia a ellos en el grafo del programa.
Un ejemplo es el Executor (~ ranura de ejecutor en un nodo integrado o agente) que forma parte del contexto pasado por un paso node a cualquier paso en su bloque, especialmente sh/bat.
Pipeline utiliza la API Pickle para sustituir versiones seguras para la serialización de estos objetos.
Cuando un WorkflowRun se carga desde el disco después de un reinicio, el estado del programa se deserializa y los pickles se deserializan ("rehidratan") en paralelo.
Si y cuando todos los pickles se deserializan correctamente y los objetos resultantes se colocan de nuevo en el estado del programa, el programa comienza a ejecutarse nuevamente y se llama a StepExecution.onResume para restaurar temporizadores y similares.
Toda la lógica del programa se ejecuta dentro de un "hilo CPS VM", que es solo un grupo de hilos de Java que puede ejecutar métodos binarios y determinar qué continuación ejecutar a continuación.
El paso parallel utiliza "green threads" (también conocidos como multitarea cooperativa): registra nombres de hilos lógicos (~ ramas) para varias acciones, pero no los ejecuta literalmente de forma simultánea.
El programa puede parecer que realiza tareas concurrentemente, pero solo porque la mayoría de los pasos se ejecutan de forma asíncrona, mientras el hilo VM está inactivo, y pueden superponerse en el tiempo.
No se consume ningún hilo de Java, excepto durante los intervalos típicamente breves en los que el código Groovy se ejecuta realmente en el hilo VM.
El widget del ejecutor solo muestra una entrada para el ejecutor "flyweight" en el nodo integrado cuando el hilo VM está ocupado; normalmente está oculto.