
Seal Security 예시 — 취약한 pip 앱 (PyYAML CVE-2020-14343) 봉인된 버전으로 수정됨; GitHub Actions + Jenkins 통합
Seal Security가 알려진 CVE를 취약한 종속성을 봉인된 (백포트된, 드롭인) 버전으로 대체하여 — 선언된 요구 사항이나 코드를 전혀 변경하지 않고 — 어떻게 해결하는지 종단 간(end‑to‑end) 시연하는 최소한의 의도적으로 취약한 Flask 애플리케이션입니다.
CI/CD에서 Seal CLI의 전면-후면 스모크 테스트로 설계되었습니다: 앱을 실행하고, 실제 익스플로잇을 트리거하고, Seal을 실행한 다음, 동일한 익스플로잇이 차단되는 것을 확인합니다.
| 에코시스템 | Python / pip |
| 취약한 패키지 | PyYAML==5.1 |
| CVE | CVE‑2020‑14343 — yaml.load / FullLoader 역직렬화 → 임의 코드 실행 |
| 봉인된 (수정된) 버전 | Seal의 PyPI 레지스트리의 pyyaml 5.1+sp1 |
| 통합 | 하나의 빌드 단계로서의 Seal CLI — GitHub Actions 및 Jenkins 모두에 대해 표시 |
환영 페이지는 name을 받아 PyYAML의 기본 로더를 통해 파싱합니다:
parsed = yaml.load(name) # PyYAML 5.1 → unsafe FullLoader (CVE-2020-14343)
PyYAML 5.1에서 명시적인 SafeLoader 없이 yaml.load()를 사용하면 FullLoader를 사용하게 되며, 이는 신뢰할 수 없는 입력에서 임의의 Python 객체를 생성할 수 있습니다. 공격자는 서버에서 임의의 Python을 실행하는 YAML 페이로드를 제출합니다.
정상 요청
/?name=alice → Welcome, alice!
익스플로잇 요청 — 이 YAML을 name으로 전달합니다 (워크플로 로그에서 이미 URL 인코딩됨):
!!python/object/apply:tuple [!!python/object/apply:map [!!python/name:eval , ["__import__('subprocess').check_output(['id']).decode()"]]]
취약한 PyYAML이 페이로드를 역직렬화하여 실행하고, 앱은 “You've been pwned” 페이지를 표시하며, 그 후 서버가 종료됩니다 (몇 초 후에 페이지가 먼저 전달되도록 합니다). 다시 로드하면 앱이 사라집니다 — ngrok을 통해 “endpoint offline” 페이지가 표시됩니다.
.
├── app.py # the vulnerable Flask app
├── requirements.txt # declares PyYAML==5.1
├── Jenkinsfile # example Jenkins (Groovy) pipeline with the Seal stage
└── .github/workflows/
├── build-and-run.yml # run + expose the app for browser testing
└── seal-security.yml # run Seal remediation, then start the app
Seal은 SaaS, Seal 호스팅 방식입니다 — 환경 내에 아무것도 설치되지 않으며, 모든 트래픽은 TCP 443의 아웃바운드 HTTPS만 사용합니다. 수정을 실행하려면 다음이 필요합니다:
| 비밀 / 자격 증명 | 용도 | 저장 위치 |
|---|---|---|
이것들을 Settings → Secrets and variables → Actions (GitHub) 또는 Manage Jenkins → Credentials (Jenkins)에서 구성하세요. 토큰을 저장소에 커밋하지 마세요.
아웃바운드 443에 대해 다음 Seal 호스트를 허용 목록에 추가하세요:
app.sealsecurity.io, authorization.sealsecurity.io, cli.sealsecurity.io, 그리고 봉인된 pip 패키지를 위해 pypi.sealsecurity.io. CLI 바이너리는 github.com / objects.githubusercontent.com에서 다운로드됩니다.
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python app.py # → http://localhost:5000
http://localhost:5000/?name=alice를 열면 (작동), 위의 익스플로잇 페이로드를 name으로 제출하면 — 앱이 "You've been pwned"를 표시하고 몇 초 후 서버가 종료됩니다.
Seal CLI는 pip install 후, 패키징 전에 하나의 추가 단계로 실행됩니다. 해결된 종속성을 스캔하고 취약한 종속성을 봉인된 버전으로 다시 작성하며, 원격 수정 모드를 사용합니다 (정책은 Seal UI에서 중앙 관리됩니다).
다음을 사용합니다: seal-community/cli-action:
- uses: seal-community/cli-action@latest
with:
mode: fix
fix_mode: remote
token: ${{ secrets.SEAL_TOKEN }}
target: requirements.txt # the manifest for this ecosystem
Actions → “Seal Security Remediation” → Run workflow를 통해 실행합니다. .github/workflows/seal-security.yml을 참조하세요.
설치 후, 패키징 전에 추가된 단일 단계입니다. Jenkinsfile을 참조하세요:
stage('Seal') {
steps {
sh '''
curl -fsSL https://github.com/seal-community/cli/releases/download/latest/seal-linux-amd64-latest -o seal
chmod +x seal
./seal fix --mode remote "$SEAL_MANIFEST" # SEAL_MANIFEST=requirements.txt
'''
}
}
SEAL_TOKEN은 seal-token Jenkins 자격 증명에서 가져옵니다. SEAL_PROJECT를 Seal 프로젝트 ID로 설정하세요.
seal fix 후, 취약한 종속성은 Seal의 PyPI 레지스트리의 봉인된 빌드로 해석됩니다 — 동일한 패키지, 보안 수정이 백포트됨:
| 종속성 | 이전 | 이후 (봉인됨) |
|---|---|---|
| PyYAML | 5.1 | 5.1+sp1 |
봉인된 버전은 보안 수정이 백포트된 동일한 패키지입니다 — 드롭인 대체품이며, 코드 변경이나 메이저 버전 업그레이드가 필요 없습니다.
수정된 앱에 대해 익스플로잇을 다시 실행하세요. 봉인된 PyYAML은 악성 객체를 생성하지 않으므로, yaml.load는 페이로드를 실행하는 대신 예외를 발생시키고, 앱은 “Invalid input — payload rejected.” 로 응답합니다. 일반 이름은 여전히 작동합니다.
seal fix를 특정 매니페스트 — pip의 경우 requirements.txt — 에 지정하세요. 여러 매니페스트/락 파일이 있는 저장소의 경우 매니페스트당 하나의 seal fix를 실행하세요.이것이 전체 통합입니다 — 하나의 단계, 아웃바운드 전용, 애플리케이션 코드 변경 없음.
| Seal 토큰 |
| Seal CLI 인증 |
GitHub Actions 시크릿 SEAL_TOKEN / Jenkins "시크릿 텍스트" 자격 증명 seal-token |
| ngrok 토큰 (선택 사항) | 실행 중인 앱을 브라우저에서 테스트하기 위해 노출 | GitHub Actions 시크릿 NGROK_TOKEN |