
Jenkins Pipeline Groovy Plugin의 CPS 인터프리터 샌드박스 우회를 대상으로 하는 CVE-2022-25173용 익스플로잇으로, 조작된 Pipeline 스크립트를 통해 Jenkins 컨트롤러 내에서 임의 코드 실행을 가능하게 합니다.
Pipeline 플러그인 모음의 핵심 구성 요소로, Jenkins 컨트롤러 프로세스 내부에서 실행되는 사용자 정의 Groovy 인터프리터를 기반으로 Pipeline 단계를 위한 표준 실행 엔진을 제공합니다.
(원칙적으로는 FlowDefinition을 API 진입점으로 하여 다른 실행 엔진도 지원할 수 있지만, 아직 프로토타입이 만들어진 적이 없으며 작성하려면 상당히 많은 노력이 필요할 것입니다.)
다음과 같은 Pipeline Groovy 스크립트 코드는
retry(3) {
for (int i = 0; i < 10; i++) {
branches["branch${i}"] = {
node {
retry(3) {
checkout scm
}
sh 'make world'
}
}
}
parallel branches
단계라고 하는 특정 특수 함수 호출이 Jenkins 고유 작업을 수행하는 Groovy 프로그램으로 실행됩니다.
이 예제에서 parallel 단계는 이 플러그인에 정의되어 있고, , , , 는 Pipeline 모음의 다른 플러그인에 정의되어 있습니다. 전역 변수는 Pipeline Multibranch 플러그인에 정의되어 있습니다.
noderetrycheckoutshscmGroovy 스크립트는 WorkflowScript라는 클래스로 컴파일되므로 스택 트레이스에는 스크립트 파일 이름(예: Jenkinsfile) 대신 해당 이름이 표시됩니다.
명령줄에서 실행되는 일반 Groovy 프로그램과 달리, Pipeline 빌드 프로그램의 전체 상태는 대부분의 Pipeline 단계를 포함하는 비동기 작업이 수행될 때마다 디스크에 저장됩니다.
Jenkins는 빌드가 실행되는 동안 다시 시작될 수 있으며, 중단된 지점부터 프로그램 실행을 재개합니다.
이는 효율성을 위한 것이 아니므로 Jenkins 기능과 직접 관련된 상위 수준의 "글루" 코드로 제한해야 합니다.
프로젝트 자체의 빌드 로직은 sh 또는 bat 단계에서 빌드 노드의 외부 프로그램으로 실행해야 합니다.
JIRA의 Pipeline Groovy 에픽에는 Groovy 인터프리터의 알려진 일부 제한 사항이 설명되어 있습니다. 이러한 문제는 Pipeline이 Groovy를 직접 실행할 수 없고 프로그램 상태를 저장하기 위해 각 작업을 가로채야 한다는 사실에서 비롯됩니다.
Pipeline Sandbox 에픽에는 악성 Pipeline 스크립트가 Jenkins를 제어하지 못하도록 하는 데 사용되는 Groovy 샌드박스 관련 문제가 설명되어 있습니다. 샌드박스가 비활성화된 상태로 실행되는 스크립트는 Jenkins 내부 API를 직접 호출할 수 있으며, 이는 누락된 단계 기능에 대한 유용한 우회 방법이 될 수 있지만 보안상의 이유로 관리자만 이러한 스크립트를 승인할 수 있습니다.
Pipeline Snippet Generator 에픽에는 실시간 구성 양식을 기반으로 단계 구문 샘플을 제공하는 도구 관련 문제가 설명되어 있습니다.
이 플러그인은 이전에 "Workflow CPS plugin" 또는 "Workflow Groovy Plugin"이었습니다. 따라서 Maven artifactId는 pipeline-groovy가 아니라 workflow-cps입니다.
이 플러그인은 Groovy CPS 라이브러리를 사용하여 프로그램이 컴파일될 때 continuation-passing 스타일 변환을 구현합니다.
표준 Groovy 컴파일러가 AST를 생성하는 데 사용되지만, 바이트코드 생성은 대부분의 작업을 특수 "오류"인 CpsCallableInvocation을 던지는 변형으로 대체하는 CompilationCustomizer에 의해 가로채집니다.
그런 다음 이는 엔진에 의해 포착되며, 엔진은 이 정보(예: 메서드 호출에 전달될 인수)를 사용하여 다음 연속 작업으로 제어를 넘깁니다.
Pipeline 스크립트는 지정된 메서드를 @NonCPS 어노테이션으로 표시할 수 있습니다.
이러한 메서드는 (샌드박스 보안 검사를 제외하고) 일반적으로 컴파일되므로 Java 플랫폼, Groovy 런타임, Jenkins 코어 또는 플러그인 코드의 "바이너리" 메서드와 매우 유사하게 동작합니다.
@NonCPS 메서드는 Serializable이 아닌 객체를 지역 변수로 안전하게 사용할 수 있지만, 직렬화할 수 없는 매개변수를 받거나 직렬화할 수 없는 값을 반환하거나 저장해서는 안 됩니다.
@NonCPS 메서드에서는 일반(CPS 변환) 메서드나 Pipeline 단계를 호출할 수 없으므로, 이는 요약을 기본 스크립트로 다시 전달하기 전에 일부 계산을 수행하는 데 가장 적합합니다.
특히 Object.toString()과 같이 바이너리 클래스에 정의된 메서드의 @Override는 일반적으로 바이너리 코드가 이를 호출하므로 @NonCPS로 표시해야 합니다.
어떤 종류의 객체는 본질적으로 그대로 직렬화하기에 안전하지 않지만, 프로그램 그래프에서 해당 객체에 대한 참조는 유지하고 싶습니다.
예를 들어 Executor(~ 기본 제공 노드 또는 에이전트 노드의 실행기 슬롯)는 node 단계가 해당 블록의 모든 단계(특히 sh/bat)에 전달하는 컨텍스트의 일부입니다.
Pipeline은 Pickle API를 사용하여 이러한 객체의 직렬화 가능한 버전으로 대체합니다.
다시 시작 후 WorkflowRun이 디스크에서 로드되면 프로그램 상태가 역직렬화되고 pickle도 병렬로 역직렬화("재수화")됩니다.
모든 pickle이 성공적으로 역직렬화되고 결과 객체가 프로그램 상태에 다시 배치되면 프로그램이 다시 실행되기 시작하고 StepExecution.onResume이 호출되어 타이머 등을 복원합니다.
모든 프로그램 로직은 "CPS VM 스레드" 내부에서 실행됩니다. 이는 바이너리 메서드를 실행하고 다음에 수행할 연속 작업을 파악할 수 있는 Java 스레드 풀일 뿐입니다.
parallel 단계는 "그린 스레드"(협동 멀티태스킹이라고도 함)를 사용합니다. 즉, 다양한 작업에 대한 논리적 스레드(~ 분기) 이름을 기록하지만 문자 그대로 동시에 실행하지는 않습니다.
프로그램이 작업을 동시에 수행하는 것처럼 보일 수 있지만, 이는 VM 스레드가 유휴 상태인 동안 대부분의 단계가 비동기적으로 실행되어 시간적으로 겹칠 수 있기 때문일 뿐입니다.
Groovy 코드가 실제로 VM 스레드에서 실행되는 일반적으로 짧은 간격을 제외하고는 Java 스레드가 소비되지 않습니다.
실행기 위젯은 VM 스레드가 사용 중일 때만 기본 제공 노드의 "플라이급" 실행기에 대한 항목을 표시하며, 일반적으로는 숨겨져 있습니다.