실습 목표
- Secret Scanning: 소스 코드 내 민감 정보(API Key, Password) 노출 차단 [Gitleaks 이용]
- SAST (Static Application Security Testing): 코드 정적 분석을 통한 취약점 식별 [SonarQube 이용]
- SCA (Software Composition Analysis): 오픈소스 라이브러리 취약점 및 라이선스 점검 [snyk 이용]
- SBOM (Software Bill of Materials): 소프트웨어 구성 명세서 생성 및 이미지 서명 [syft&cosing 이용]
실습
1. PyGoat 설치 및 실행
1-1 PyGoat란
PyGoat는 OWASP Top 10 취약점을 학습할 수 있도록 의도적으로 보안 결함이 주입된 Django 기반 웹 애플리케이션이다.
※ OWASP (Open Web Application Security Project) 에서 발표하는 웹 애플리케이션 보안 위협 리스트는 다음과 같다.

웹 애플리케이션이란 사용자와 상호작용하며 특정 기능을 수행하는 소프트웨어로 구성요소는 다음과 같다.
- Frontend: 사용자가 보는 화면 (HTML/CSS/JS)
- Backend: 데이터를 처리하는 로직 (PyGoat의 경우 Python/Django)
- Database: 회원 정보 등이 저장되는 창고 (SQLite 등)
PyGoat를 실행하면 내 컴퓨터 안에 가상의 웹 서버와 DB가 가동되어 브라우저를 통해 접속 가능한 상태가 된다.
1-2 PyGoat 실습 환경 구축 및 설치
- Docker Desktop 설정: Windows 호스트와 WSL2 배포판(Ubuntu) 간의 WSL Integration 활성화.
- 레포지토리 클론: git clone https://github.com/adeyosemanputra/pygoat.git 명령을 통한 소스 코드 복제.
- 이미지 빌드: docker-compose build --no-cache 명령으로 수정된 Dockerfile 기반 새 이미지 생성.
- 서비스 가동: docker-compose up -d를 통해 백그라운드(Detached Mode)에서 웹 앱 및 DB 실행.
Dockerfile 빌드 중 Debian(Buster) 저장소 만료(EOL)로 인한 404 Not Found 에러 식별
-> Dockerfile 수정으로 패키지 저장소 주소 변경 후 재빌드 및 실행

2. Gitleaks 설치 및 Secret Scanning 수행
2-1 Gitleaks 설치
pygoat의 상위 폴더에 설치
wget https://github.com/gitleaks/gitleaks/releases/download/v8.18.2/gitleaks_8.18.2_linux_x64.tar.gz
tar -xvzf gitleaks_8.18.2_linux_x64.tar.gz
sudo mv gitleaks /usr/local/bin/
2-2 pygoat내에서 스캔 실행
gitleaks detect --source . -v
- detect (탐지모드) : Gitleaks의 모드 중 하나인 detect는 현재 폴더에 있는 파일, Git의 모든 과거 이력까지 보여준다.
- --source . (범위 지정) : 현재폴더 기준인 .으로 지정
- -v (상세 로그) : 탐지된 유출 내용을 상세히 출력
2-3 Gitleaks 매커니즘
1. 정규 표현식 패턴 매칭 (Regex Matching) : 특정 서비스의 키가 지닌 고유한 형식을 찾아낸다.
- 예시: AWS Access Key는 항상 AKIA로 시작하고 총 20자의 대문자와 숫자로 구성된다.
- 작동 방식: Gitleaks는 내부에 ^AKIA[0-9A-Z]{16}$ 같은 규칙 지도를 가지고 있어서, 일치하는 모양을 찾아낸다.
- 탐지 대상: AWS 키, Stripe 키, Google API 키, 이메일 주소 등 형식적 특징이 뚜렷한 것들.
2. 엔트로피 분석 (Entropy Analysis) : 형식이 정해지지 않은 일반적인 비밀번호를 찾을 때 사용
- 원리: 문자열의 **'무작위성(복잡도)'**을 측정
- "apple"은 일상적인 단어라 엔트로피가 낮다.
- "a1b2c3d4e5f6g7h8"은 무작위로 섞인 느낌이라 엔트로피가 높다.
- 작동 방식: Gitleaks는 문자열이 일정 수준 이상의 엔트로피(Entropy Score)를 가지면 "이건 사람이 기억하려고 만든 단어가 아니라, 기계가 생성한 비밀번호나 키일 확률이 높다"고 판단한다.
- 탐지 대상: 하드코딩된 암호화 키, 랜덤 생성 토큰 등.
3. 키워드 및 문맥 탐지 (Keyword & Context) :주변 단어를 보고 힌트를 얻는다.
- 작동 방식: 단순히 무작위 문자열만 보는 게 아니라, 그 앞에 password =, api_key:, token: 같은 단어가 붙어 있는지 확인합니다.
- 탐지 대상: 사용자가 직접 만든 변수명에 담긴 비밀번호. (예: "password": "jacktheripper")
4. Git 이력 추적 (History Scanning)
- 원리: 현재 눈에 보이는 파일만 보는 게 아니라, .git 폴더 안에 저장된 **모든 과거 기록(Snapshots)**을 추적
- 작동 방식: 1년 전 커밋에서 비밀번호를 올렸다가 5분 뒤에 지웠어도, Git의 타임머신 기록에는 남아있다.
2-4 결과 분석
- RuleID : 탐지된 데이터의 종류와 성격을 정의하며 "AWS 키니까 즉시 계정을 정지해야겠다" 또는 "일반 비번이니 코드 수정을 요청해야겠다"와 같이 대응 우선순위를 결정하는 기준이 된다.
- aws-access-token: AWS 서비스 접근 키 패턴에 걸림.
- generic-api-key: 특정 서비스는 아니지만 password 같은 단어와 복잡한 문자열 조합에 걸림.
- Commit : 유출데이터가 포함된 과거의 특정 시점을 알 수 있다. 데이터가 삭제됐더라도 git show [commit ID]:[FilePath] 명령어를 통해 당시의 전체 소스 코드 복원이 가능하다. Git 이력을 삭제(Purge)해야 할 시점 범위를 파악할 때 사용한다.
- FingerPrint : 여러 정보(커밋, 파일 경로, 규칙 ,라인번호)를 하나로 묶어 만든 고유 식별자이다. FingerPrint가 같으면 동일 사건으로 간주하여 중복처리를 방지하고 오탐지이거나 테스트용일 경우 .gitleaksignore 파일에 등록하여 예외 처리할때 사용
1) Git 이력 내 Salt 미적용 SHA-256 해시값 노출

- 진단(Status): 과거 커밋에 비밀번호가 포함된 파일이 삭제되지 않고 Git 역사 속에 잔존함.
- 위협(Threat): 해커가 git checkout 명령어로 삭제된 파일을 복구해 시스템 권한을 탈취할 수 있음.
git show 명령어를 통해 당시의 코드를 살펴보면 password가 하드코딩 되있는걸 볼 수 있다. 또한, salt가 없는 sha-256 해시 암호화 사용으로 레인보우 테이블을 통한 평문 탈취 가능성이 높다.

- 해결:
- 유출된 비밀번호 즉시 변경(Key Rotation)
- Salt가 자동 포함되는 Argon2 혹은 BCrypt 암호화 방식 선택
- git filter-repo로 Git 이력 자체를 영구 삭제(Purge).
- 이후 비밀번호는 코드가 아닌 환경 변수로 관리.
2) 암호화 마스터 키(Encrpytion Key)의 노출

- 진단: 데이터 복호화 권한(대칭키/개인키)의 평문 노출
- 위협: 별도의 크래킹 과정 없는 데이터 즉시 탈취 및 기밀성(Confidentiality) 완전 파괴
git show를 이용해 실제 코드를 보면

- 해결
- 기존 키 즉시 폐기, AES-256 등 표준 규격의 강력한 신규 키 재발급 (Key Rotation)
- 새로 발급한 키로 모든 데이터 재암호화
- 환경 변수 혹은 Vault,KMS 같은 전문 키 관리 서비스 도입 (Hardening)
- git filter -repo 등을 사용하여 GIt 이력 영구 삭제 (Purge)
3) Stripe API 실운영키 (Secret Key) 노출

- 진단 : 외부 결제 서비스(Stripe) 인증을 위한 실운영(Live Mode) 시크릿 키 평문 노출
- 위협 : 재정적 손실, 공급망 공격, 데이터 유출
※ stripe key란?
전세계적으로 가장 많이 사용되는 온라인 결제 솔루션으로 sk_live 접두사가 붙은 비밀키는 실제 고객의 돈과 결제 정보에 접근할 수 있는 마스터키이다.
- 해결
- Key Rotation
- Hardening
- Purge
- 탐지 자동화 : Pre-commit Hook 및 CI/CD 가드레일에 Gitleaks 연동하여 재발 방지
4) 소스 코드 내 평문 비밀번호 노출

git show를 이용해 원하는 부분만 출력 git show 6bd0973a:introduction/views.py | sed -n '940,970p'
- -n이 있으므로 모든 줄을 무시함.
- 952,962p -> 오직 이 범위의 줄만 터미널에 보여줌.

- 진단 : 로직 내 비교를 위해 삽입된 평문 비밀번호
- 위협 :
- 인증 우회(Authentication Bypass): 공격자가 소스 코드를 분석하여 유효한 계정 정보를 획득, 시스템에 즉시 로그인 가능
- 인가 결함 연쇄 발생: admin == "1" 쿠키 검증 로직과 결합하여, 공격자가 일반 계정으로 로그인 후 쿠키를 변조하여 관리자 권한까지 탈취
- 민감 정보 로깅: 코드 내 print(password) 구문으로 인해 서버 로그에 사용자 비밀번호가 평문으로 남는 2차 유출 위험 존재
- 해결 방안
- Credential Rotation: 유출된 jack 계정 비밀번호 즉시 폐기 및 로직 내 평문/로깅 구문 삭제.
- Secure Authentication: Salted Hash 기반 DB 인증 및 서버 사이드 세션 관리 체계 도입.
- History Purging: git filter-repo를 활용한 Git 전체 타임라인 내 시크릿 흔적 영구 소거.
- Security Guardrails: Pre-commit Hook 및 CI/CD 내 Gitleaks 자동 스캔 가드레일 구축.
5) 외부 라이브러리(PyJWT) 메타데이터 내 JWT 토큰 노출


- 진단 : 오탐(False Positive). 실제 서비스의 비밀키가 아닌, 개발자 가이드를 위한 단순 예시, 로그 상의 >>> import jwt, >>> encoded = ... 등의 표현은 파이썬 인터프리터(REPL)의 전형적인 예시 코드 형태
- 위협
- 운영 노이즈(Noise): 가짜 시크릿이 스캔 결과에 포함되어 실제 위협(예: 하드코딩된 비밀번호)에 대한 집중력을 분산시킴
- 공급망 보안 부실: 외부 패키지 실물을 직접 Git에 포함할 경우, 라이브러리 업데이트 및 취약점 패치 관리가 불가능해져 보안 가시성이 결여됨
- 해결 방안
- Dependency Management: 실물 폴더 대신 requirements.txt 목록만 관리하도록 의존성 관리 표준 적용
- History Purging: git filter-repo를 사용하여 Git 전체 타임라인에서 lib/ 경로와 관련된 모든 과거 흔적 영구 소거
- Security Guardrails: .gitignore에 가상 환경 경로를 추가하여 외부 라이브러리가 Git에 유입되는 것을 원천 차단
6) 외부 라이브러리 테스트 코드 내 private key 노출

- 탐지 로그 요약
- 핵심 정보: django-allauth 패키지의 Apple 로그인 연동 테스트 파일(tests.py) 내 개인키 탐지
- 노출 데이터: -----BEGIN PRIVATE KEY-----로 시작하는 PEM 형식의 비대칭 암호화 키
- 발생 원인: 가상 환경 폴더(lib/)가 .gitignore에 등록되지 않아 외부 라이브러리의 테스트 리소스까지 Git 이력에 포함됨
- 진단
- 데이터 성격: Test Credential (테스트용 인증 정보). Apple ID 연동 기능을 검증하기 위해 임의로 생성한 테스트용 키
- 상태 분석: 실제 서비스 운영에 사용되는 키는 아니나 개인키(Private Key)라는 민감한 패턴이 노출되어 위험으로 식별함
- 위협
- 보안 스캔 오염: 실제 프로젝트의 보안 결함과 상관없는 외부 library의 테스트 데이터가 스캔 결과에 섞여 분석 효율 저하
- 형상 관리 표준 위반: 대량의 외부 라이브러리 코드가 Git에 포함되어 저장소 용량 낭비 및 의존성 관리 가시성 상실
- 해결 방안
- Credential Rotation: (해당 없음) 라이브러리 제공용 테스트 키이므로 실제 키 교체 작업은 불필요
- Dependency Management: site-packages를 Git에서 제거하고 requirements.txt를 통한 목록 관리 체계로 전환
- History Purging: git filter-repo를 사용하여 과거 모든 커밋 히스토리에서 lib/ 폴더 영구 삭제
- Security Guardrails: .gitignore에 가상 환경 관련 키워드(lib/, venv/, env/)를 모두 등록하여 외부 파일 유입 원천 차단
2-5 결론
Gitleaks는 코드, 설정 파일, 그리고 Git 전체 이력을 샅샅이 뒤져 하드코딩된 비밀번호나 API 키 같은 민감 정보를 찾아내는 보안 감시관 역할을 한다. 만약 실제 유출이 확인되면 즉시 해당 키를 **무효화(Rotation)**하고 환경 변수나 전용 관리 도구로 대체해야 하며, 이미 기록된 흔적은 git filter-repo를 이용해 **이력 자체를 영구 삭제(Purging)**해야 한다. 마지막으로 Pre-commit hook을 설정해 실수가 발생하더라도 커밋 단계에서 자동으로 차단되는 가드레일을 구축하는 것이 핵심이다.
3. Snyk 설치 및 SCA(Software Composition Analysis) 수행
SCA는 오픈소스 라이브러리와 같은 외부 의존성의 보안 취약점과 라이선스를 분석하는 기술 전체를 일컫는다.
snyk는 SCA분야의 대표적인 상용 도구이다.
3-1 Snyk 설치
Snyk CLI는 Node.js 기반 도구이다. 런타임 관리 도구인 NVM(Node Version Manager)는 설치 경로를 사용자 홈 디렉토리로 재지정하여 root 권한없이 안전한 도구 관리를 가능하게 한다. 이는 최소 권한 원칙을 준수하여 관리자 권한 남용에 따른 시스템 오염과 공급망 보안 위협을 차단하는 가드레일 역할을 한다.
1) NVM 및 Node.js 설치
- NVM 설치 스크립트 실행: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
- 환경 설정 반영: source ~/.bashrc
- Node.js LTS(안정 버전) 설치: nvm install --lts
2) Snyk CLI 설치 및 수동 인증
- Snyk 설치 (sudo 제외): npm install -g snyk
- 인증 토큰 획득: Snyk 웹 대시보드 접속 → Account Settings → General 탭 하단의 KEY 값 복사
- 터미널 토큰 등록: snyk config set api=<복사한_토큰_값>
- 인증 결과 확인: snyk whoamI
3-2 SnYk SCA 작동 원리
1. 로컬 의존성 트리 분석 (Inventory)
Snyk CLI는 프로젝트 폴더 내의 매니페스트 파일(requirements.txt, package.json 등)을 분석한다. 단순히 파일에 적힌 이름만 보는 것이 아니라, 각 라이브러리가 불러오는 하위 라이브러리들까지 추적하여 전체적인 **의존성 그래프(Dependency Graph)**를 생성한다.
2. 지문 채취 및 메타데이터 전송 (Fingerprinting)
분석된 라이브러리들의 이름, 버전 정보, 해시(Hash)값 등 식별 데이터를 추출한다. 이때 소스 코드 전체를 전송하는 것이 아니라, 어떤 부품을 사용하는지에 대한 **디지털 지문(Fingerprint)**만을 Snyk 클라우드 서버로 보낸다.
3. Snyk Intel DB 실시간 대조 (Matching)
전송된 부품 명단을 Snyk이 관리하는 전 세계 취약점 데이터베이스(Snyk Intel DB)와 대조한다. 이 DB에는 공개된 CVE 정보와 Snyk 전담 연구팀이 발견한 미공개 보안 결함이 실시간으로 업데이트되어 있다.
4. 처방전 발행 (Remediation)
대조 결과, 취약점이 발견된 버전(Vulnerable Version)이 식별되면 Snyk은 즉시 해결책을 제시한다. 단순히 "위험하다"는 경고에 그치지 않고, 해당 취약점이 해결된 **최소 안전 버전(Minimum Secure Version)**으로의 업그레이드 경로를 안내
3-3 분석
Snyk는 requirements.txt 파일만 읽는 것이 아니라 실제로 해당 라이브러리들이 현재 Python 환경에 설치된 상태(pip list)를 대조하여 정확한 지문을 추출하려고 시도하기 때문에 venv 가상환경을 이용하여 라이브러리를 먼저 설치한다.
python3 -m venv venv
source venv/bin/activae
pip install -r requirements.txt
snyk test
그 이후로도 에러에 따라서 여러 빌드도구들을 설치하면서 어찌저찌 완료

분석 결과와 함께 upgrade 해야할 버전과 위험등급이 뜬다.

예시 1)

- 진단
- Improper Following of a Certificate's Chain of Trust (Critical): certifi 라이브러리는 안전한 웹 통신(HTTPS)을 위해 어떤 인증서가 진짜인지 확인하는 '신뢰할 수 있는 목록(Root CA)'을 담고 있다. 2022년 버전은 이미 신뢰를 잃은 오염된 인증서를 여전히 "진짜"라고 믿고 있을 수 있다.
- 위협 : 공격자가 가짜 인증서를 내밀어도 시스템이 이를 통과
- 해결 방안 : requirements.txt의 certifi 버전 수정

다시 snyk test를 실행해보면 취약점이 사라졌음을 알 수 있다.
3-4 핵심 취약점 분석
여러 취약점 중 핵심적인 몇가지만 정리해보면
1. 서비스 거부 (Denial of Service, DoS)
- 대상: cryptography, django, werkzeug 등 다수
- 진단: 부적절한 자원 할당 및 알고리즘 복잡도 결함
- 위협: 공격자가 조작된 요청을 보내 서버의 CPU나 메모리 자원을 고갈시킴으로써, 정상적인 사용자가 서비스를 이용하지 못하게 만듦 (시스템 마비)
- 해결 방안: 각 라이브러리를 Snyk 추천 버전(cryptography@46.0.6 등)으로 업데이트하여 자원 관리 로직 최적화
2. 임의 코드 실행 (Arbitrary Code Execution / RCE)
- 대상: pyyaml, werkzeug, pillow
- 진단: 안전하지 않은 역직렬화(Unsafe Deserialization) 및 입력값 처리 미흡
- 위협: 공격자가 서버 권한으로 임의의 명령어를 실행할 수 있음. 데이터베이스 삭제, 백도어 설치 등 시스템 전체 장악이 가능한 가장 치명적인 공격
- 해결 방안: pyyaml@5.4 이상 업그레이드 및 yaml.safe_load()와 같은 안전한 함수 사용 강제
3. SQL 인젝션 (SQL Injection)
- 대상: django, sqlparse
- 진단: 데이터베이스 쿼리 생성 시 사용자 입력값이 적절히 필터링되지 않음
- 위협: 공격자가 데이터베이스에 직접 질의를 던져 관리자 계정 정보를 탈취하거나 데이터를 조작함
- 해결 방안: django@4.2.30 패치 및 ORM 사용 시 Raw SQL 사용을 지양하는 보안 가이드라인 준수
4. 경로 조작 및 파일 탐색 (Directory Traversal)
- 대상: django, werkzeug
- 진단: 파일 경로를 처리할 때 상위 디렉토리로 이동하는 문자열(../)에 대한 검증 부족
- 위협: 공격자가 의도하지 않은 시스템 설정 파일(/etc/passwd 등)이나 소스 코드 파일에 접근하여 민감 정보 탈취
- 해결 방안: 최신 버전 패치 및 파일 입출력 시 경로 살균(Path Sanitization) 로직 적용
3-5 결론
오픈소스 종속성 내에 가용성(DoS), 기밀성(SQLi,Traversal), 무결성(RCE)를 위협하는 핵심 취약점이 고루 분포해있었다. 특히 Critical/High 등급의 취약점들은 외부인이 서버 전체를 통제할 수 있는 심각한 상태로 requirements.txt내의 패키지 버전을 Synk 권고안에 맞춰 일괄 갱신후 재배포를 해야하고 장기적으로 CI/CD 파이프라인에 Synk 스캔을 연동하여 취약한 부품 반입을 차단한느 가드레일을 구축해야한다.
4. SonarQube를 활용한 SAST(Static Application Security Testing)
SAST란 소스코드를 실행하지 않은 상태에서 코드 내부의 논리적 결함, 보안 약점, 취약점을 분석하는 정적 분석 기술이다.
SonarQube는 코드 품질과 보안을 통합 관리하는 오픈소스 플랫폼으로 코드 복잡도 측정, 중복 코드 탐지, 보안 취약점 스캔, 기술 부채 지표 제공등의 기능을 한다.
4-1 SonarQube 설치
PyGoat 환경과 격리하여 빠르고 간편하게 실행하기 위해 Docker를 사용
1. Docker 네트워크 생성
docker network create sonarqube-net
Docker 네트워크 설정을 별도로 하는 이유는 **격리(Isolation)**와 가용성(Availability) 때문이다.
- 개념: 컨테이너들이 서로 통신하기 위해 연결되는 가상 스위치 역할
- 사용 이유
- DNS 서비스 검색: 기본 브리지와 달리 사용자 정의 네트워크는 IP 대신 컨테이너 이름(예: sonarqube)으로 통신이 가능하여 관리가 편하다.
- 네트워크 격리: 외부 노출이 필요 없는 DB 등을 외부망과 차단하여 보안을 강화합니다. (제로 트러스트 기본 원칙)
2. 시스템 설정 최적화
# 일시적 적용 (재부팅 시 초기화)
sudo sysctl -w vm.max_map_count=262144
# 영구적 적용
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
- 최적화 이유: SonarQube는 내부적으로 검색 및 인덱싱을 위해 Elasticsearch를 사용한다. Elasticsearch는 많은 수의 메모리 맵(Memory Map Areas)을 사용하는데, Linux 커널의 기본값은 보통 이 요구량보다 낮다.
- 결과: 이 값을 상향하지 않으면 Elasticsearch가 메모리 부족으로 판단하여 시작 중에 컨테이너가 즉시 종료(Exit)
3. SonarQube 서버 실행
공식 이미지 다운로드
docker run -d --name sonarqube \
--network sonarqube-net \
-p 9000:9000 \
-v sonarqube_data:/opt/sonarqube/data \
-v sonarqube_extensions:/opt/sonarqube/extensions \
-v sonarqube_logs:/opt/sonarqube/logs \
sonarqube:community
4. 초기 설정
- 브라우저 접속: http://localhost:9000 접속 (초기 계정: admin / admin)
- 비밀번호 변경: 최초 로그인 시 새로운 비밀번호 설정 필수
- 프로젝트 생성:
- Create a local project --> Project Key: pygoat-sast 입력
- 토큰 발행: 분석 시 인증을 위한 Analysis Token을 발행하고 별도로 기록
5. SonnarScanner 실행 (클라이언트)
docker run --rm \
-e SONAR_HOST_URL="http://host.docker.internal:9000" \
-e SONAR_TOKEN="[발급받은_토큰]" \
-v "$(pwd):/usr/src" \
sonarsource/sonar-scanner-cli \
-Dsonar.projectKey=pygoat-sast
1. Docker 실행 옵션 분석
- docker run --rm: 분석 작업이 끝나면 컨테이너를 즉시 삭제함.
- -e SONAR_HOST_URL="...": 스캐너가 분석 결과를 보낼 서버 주소를 환경 변수로 설정함. [host.docker.internal: 컨테이너 내부에서 호스트 컴퓨터(SonarQube 서버가 떠 있는 곳)에 접근하기 위한 특수 주소.]
- -e SONAR_TOKEN="...": 서버 인증을 위한 비밀키. ID/PW를 노출하지 않고 Token 기반 인증을 수행하여 보안성 확보.
- -v "$(pwd):/usr/src": 현재 경로($(pwd), PyGoat 코드 폴더)를 컨테이너 내부의 /usr/src 경로와 동기화함. 스캐너가 내 코드를 읽을 수 있게 통로를 열어주는 작업.
2. SonarScanner 전용 옵션 분석
- sonarsource/sonar-scanner-cli: 실행할 도커 이미지 이름. SonarSource에서 공식 제공하는 검증된 스캐너 사용.
- -Dsonar.projectKey=pygoat-sast: SonarQube 서버 대시보드에서 이 프로젝트를 식별하기 위한 고유 ID. 서버에 미리 생성한 Project Key와 일치해야 함.
4-2 SonarQube 실행 매커니즘
[Step 1] 서버 수신 대기 (Server Side)
- 이미지 구동: Docker를 통해 SonarQube 서버를 9000번 포트로 활성화
- 인증 체계: 분석 도구가 접근할 수 있도록 전용 Project Token을 발행하여 수신 게이트를 열어둠
[Step 2] 소스 코드 연결 및 스캔 (Scanner Side)
- Volume Mount (-v): 호스트(내 컴퓨터)에 있는 PyGoat 소스 코드를 스캐너 컨테이너 내부의 /usr/src로 연결
- 정적 분석: 스캐너가 코드를 한 줄씩 읽으며 미리 정의된 보안 규칙(Rule)과 대조하여 취약점 패턴 탐지
[Step 3] 데이터 전송 및 처리 (Communication)
- 네트워크 통신: host.docker.internal 주소를 통해 컨테이너 밖의 서버로 분석 결과(JSON/Protobuf 형태) 전송
- Background Task: 서버는 리포트를 받자마자 큐(Queue)에 넣고, 엔진을 돌려 최종 보안 등급(A~E)을 산출
4-3 결과 분석
SonarQube서버(localhost:9000)의 웹 대시보드에서 확인 가능

- Security (보안): E 등급 (15개 이슈)
- 시스템 침투 및 데이터 유출에 직접적으로 이용될 수 있는 치명적인 취약점이 존재함.
- 가장 시급하게 수정해야 할 항목이며, 실무 환경에서는 배포가 절대 불가능한 상태임.
- Reliability (신뢰성): E 등급 (97개 이슈)
- 프로그램의 런타임 오류나 예상치 못한 중단을 야기할 수 있는 잠재적 버그가 다수 발견됨.
- 시스템의 안정적인 운영을 심각하게 저해하는 상태임.
- Maintainability (유지보수성): A 등급 (399개 이슈)
- 지저분한 코드(코드 스멜)의 절대적인 개수는 많으나, 전체 코드 분량 대비 수정 난이도가 낮아 관리하기 쉬운 상태임.
- 구조적인 복잡도는 낮으나 세부적인 코드 정리가 필요함.
- Security Hotspots (보안 핫스팟): 214개
- 취약점으로 의심되는 민감한 로직들이 대거 포진해 있어 보안 엔지니어의 수동 검토가 절실함.
- 암호화 방식이나 설정 파일의 안전성을 일일이 확인해야 함.
- Coverage (테스트 커버리지): 0.0%
- 작성된 테스트 코드가 전혀 없어 보안 패치나 기능 수정 시 사이드 이펙트(기존 기능 파손)를 검증할 방법이 없음.
- Duplications (중복도): 7.8%
- 약 16k 라인 중 일부 코드가 복사-붙여넣기 방식으로 중복되어 있어, 보안 수정 시 누락될 위험이 존재함.
위 지표들을 바탕으로 Quality Gate를 통과한 코드만이 배포 가능

Quality Gate 기준은 프로젝트의 방향성에 맞게 설정하면 된다.
이제 PyGoat에는 어떤 취약점이 있는지 확인해보자
예시) Security - Flask 시크릿 키 노출

- 진단: Flask 프레임워크에서 세션을 암호화하고 CSRF 공격을 막는 데 사용하는 마스터 키가 평문으로 소스 코드에 박혀 있음
- 위협: 유출된 키로 서버의 세션을 임의로 생성, 위조하여 **다른 사용자의 계정으로 로그인(세션 하이재킹)**하는 등 전체 시스템 권한 장악 가능
- 해결 방안 (명사형 요약):
- 환경 변수 주입: os.environ.get()을 사용하여 외부(OS)에서 키를 동적으로 로드
- Secret Manager 활용: AWS Secrets Manager 등 전문 관리 도구에 키 저장 및 연동
- 키 갱신: 이미 노출된 키는 즉시 폐기하고 무작위의 긴 문자열로 교체
ex) .env로 값을 분리하고, .gitignore로 저장소 유출을 막은 뒤, os.environ으로 코드를 실행할 때 불러오게 함으로써 보안 취약점제거
FLASK_SECRET_KEY=your_secret_key_here ->env 파일

예시) Security - 네트워크 인터페이스 바인딩 및 디버그 모드 노출

- 진단: 모든 네트워크 인터페이스(0.0.0.0) 개방 및 개발용 디버그 모드 활성화 상태
- 위협
- 공격 표면 확대: 0.0.0.0 바인딩 시 신뢰할 수 없는 외부 네트워크에서도 서버에 직접 접근 가능
- 정보 유출: debug=True 환경에서 에러 발생 시 소스 코드, 환경 변수 등 민감한 내부 정보가 브라우저에 그대로 노출됨
- 임의 코드 실행(RCE): 핀(Pin) 번호가 노출될 경우 디버거 콘솔을 통해 서버에서 공격자가 원하는 명령 실행 가능
- 해결 방안
- 인터페이스 제한: 로컬 환경에서는 127.0.0.1(localhost)로 바인딩하여 외부 접근 차단
- 디버그 모드 비활성화: 운영 환경(Production) 배포 시 반드시 debug=False 설정 적용
- 환경 변수 분기: FLASK_ENV 환경 변수에 따라 개발/운영 모드 동적 전환 구조 채택
예시) Reliability - HTML 문서 내 <title> 태그 누락

예시) Reliability - datetime.utcnow()사용 지양

예시) Security Hotspots - HTTP Method 허용 범위 검토

이외의 다양한 취약점들을 직접 확인해보고 수정을 하거나 status 변경 처리를 통해 해결한다.
5. Syft와 Cosign을 이용한 소프트웨어 공급망 보안(Supply Chain)
Syft는 소프트웨어 성분 분석표인 SBOM(Software Bill of Materials)를 생성하는 도구이다. 오픈소스 라이브러리, OS 패키지, 바이너리 파일 등등 모든 구성 요소를 목록화한다. Snyk가 라이브러리의 보안 위험을 걸러내는 필터라면 Syft는 위험 여부와 상관없이 내부에 무엇이 들어있는지를 투명하게 기록하는 장치이다. 이를 통해 새로운 보안 사고(Zero day)가 터졌을 때 우리 시스템이 영향권인지 즉각 파악할 수 있다.
Cosign은 빌드가 끝난 정품 이미지나 SBOM에 서명을해서 배포 과정에서 해커가 악성코드를 심은 가짜 이미지로 바꿔치기하는 공급망 공격을 원천 차단하여 무결성을 보장한다.
결국 Syft로 무엇이 들어있는지를 명시하고 Cosign으로 변조되지 않았음을 인증하는 과정이다.
5-1 Syft 설치 및 SBOM 생성
쉘에서 직접 설치
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sudo sh -s -- -b /usr/local/bin
실행 및 SBOM 생성
syft pygoat-web -o cyclonedx-json > pygoat-web.sbom.json
- pygoat-web : 분석 대상(Target), 로컬 Docker 데몬에 저장된 이미지 이름 지정
- -o cyclonedx-json : 출력 포맷(Output). 국제 표준인 CycloneDX 형식 선택 (보안도구 간 호환성)
- > : 리다이렉션 터미널에 출력될 텍스트를 파일로 저장하도록 지시
- pygoat-web.sbom.json : 최종 결과 파일명.
Syft 내부 동작 프로세스
- 이미지 인덱싱(Indexing): 로컬 Docker 저장소에서 pygoat-web 이미지를 찾아 각 레이어(Layer)를 가상으로 마운트함.
- 카탈로깅(Cataloging): 각 레이어를 훑으며 패키지 매니저의 흔적을 찾음.
- /var/lib/dpkg를 보고 OS 패키지 리스트 확보.
- site-packages나 requirements.txt를 보고 Python 라이브러리 리스트 확보.
- 포맷팅(Formatting): 수집된 데이터를 CycloneDX 표준 규격에 맞춰 JSON 형태로 구조화함.
5-2 SBOM(Software Bill of Material) 분석
SBOM의 핵심 필드는 다음과 같다.

5-3 Cosign 설치
# 1. 최신 버전 확인 및 바이너리 다운로드
LATEST_VERSION=$(curl -L -s -H "Accept: application/vnd.github+json" https://api.github.com/repos/sigstore/cosign/releases/latest | grep tag_name | sed -e 's/.*"\(.*\)".*/\1/')
curl -L -O "https://github.com/sigstore/cosign/releases/download/${LATEST_VERSION}/cosign-linux-amd64"
# 2. 실행 권한 부여 및 시스템 경로로 이동
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
sudo chmod +x /usr/local/bin/cosign
# 3. 설치 확인
cosign version
5-4 Cosign을 이용한 서명 실행
1) 암호화 키 쌍 생성
먼저 서명을 위한 최소한의 신뢰 기반을 마련하는 단계로 비대칭 암호화 알고리즘 기반의 키 쌍을 생성한다.
- Private Key (cosign.key): 서명용 비밀키. CI/CD 환경(GitHub Actions Secrets)에 엄격히 격리하여 관리해야 함.
- Public Key (cosign.pub): 검증용 공개키. 이미지를 사용하는 모든 클라이언트(Kubernetes 등)에 배포되어 정품 여부를 대조함
cosign generate-key-pair
2) 아티팩트 서명 (Artifact Signing) 및 검증
이미지라는 결과물에 디지털 서명을 결합하여 출처인증을 완료한다.
1. 네임스페이스 경로 최적화 (Namespace Setup)
- 원리: 이미지를 공유 레지스트리에 배포하기 위해 개인 또는 조직(Org)의 계정 경로로 이름을 정의하고 권한을 획득함.
# 레지스트리 소유권 정의
export DOCKER_ID="DOCKER_ID"
docker login
# 이미지 경로 변경 및 원격 저장소 업로드
docker tag pygoat-web:latest $DOCKER_ID/pygoat-web:latest
docker push $DOCKER_ID/pygoat-web:latest
2. 불변 식별자 기반 주소 확정 (Digest Identification)
- 원리: 내용이 바뀔 수 있는 latest 태그 대신, 빌드된 시점의 고유한 데이터 지문(sha256)을 추출하여 서명 대상을 고정함.
# 원격 레지스트리에 올라간 이미지의 고유 Digest 주소 추출
export IMAGE_WITH_ID=$(docker inspect --format='{{index .RepoDigests 0}}' $DOCKER_ID/pygoat-web:latest)
3. 개인키를 활용한 디지털 인장 날인 (Artifact Signing)
- 원리: 제작자만 보유한 개인키로 이미지 지문을 암호화하여 해당 소프트웨어의 출처와 무결성을 보장함.
# 생성해둔 cosign.key를 사용하여 이미지에 서명 데이터 부여
cosign sign --key cosign.key $IMAGE_WITH_ID
4. 공개키를 활용한 신뢰성 검증 (Trust Verification)
- 원리: 배포 환경에서 누구나 접근 가능한 공개키로 서명을 해독하여, 제작자의 신원을 확인하고 이미지가 변조되지 않았음을 수학적으로 증명함.
# 공개키(cosign.pub)를 통해 서명 유효성 및 이미지 동일성 최종 확인
cosign verify --key cosign.pub $IMAGE_WITH_ID

1. 핵심 성공 지표 (진단)
[The cosign claims were validated]: 서명 데이터 내에 포함된 이미지 주소, 태그, 생성 시간 등의 정보가 조작되지 않고 유효함을 확인.
[Existence of the claims in the transparency log...]: 해당 서명 이력이 로그 서버에 기록되어, 누군가 몰래 서명을 교체하거나 삭제할 수 없는 상태임을 증명.
[The signatures were verified against the specified public key]: *공개키(cosign.pub)*를 통해, 이미지가 개인키로 직접 서명된 진본임을 수학적으로 확정.
2. 데이터 무결성 상세 (위협 분석)
docker-reference: 서명된 타겟이 index.docker.io/{DOCKER_ID}/pygoat-web임을 명시하여, 다른 저장소로 이미지가 탈취되거나 변조되는 것을 방지
docker-manifest-digest: 검증된 이미지의 고유 지문(sha256:82c7...)을 출력. 서명 시점과 현재 이미지가 1비트의 오차도 없이 동일함을 의미하는 무결성의 핵심 지표.
5-5 SBOM 어테스테이션(Attestation)
Attestation은 소프트웨어의 신원 증명을 넘어 그 내부 성분과 보안 검사 결과가 사실임을 암호학적으로 보증하는 디지털 공인 인증서이다.
1단계: SBOM 성분 명세서 결합 (Attestation)
- 원리: 이미 추출한 이미지 지문($IMAGE_WITH_ID)에 미리 생성해둔 SBOM 파일(pygoat-web.sbom.json)을 수학적으로 귀속시킵니다. 이 과정은 이미지와 보안 명세서 사이의 **'신뢰 사슬(Chain of Trust)'**을 형성
# 개인키를 사용하여 SBOM 파일을 이미지 증명서로 등록
cosign attest --key cosign.key \
--type cyclonedx \
--predicate pygoat-web.sbom.json \
$IMAGE_WITH_ID
2단계: 등록된 SBOM 증명서 최종 검증 (Verify Attestation)
- 원리: 첨부된 SBOM 데이터가 변조되지 않았는지 공개키로 최종 확인
# 공개키를 통해 결합된 SBOM의 유효성을 최종 검증
cosign verify-attestation --key cosign.pub \
--type cyclonedx \
$IMAGE_WITH_ID
Code & Supply chain 최종 정리
취약한 애플리케이션인 PyGoat를 로컬 환경에 설치하여 분석 환경을 구축하였다.
먼저 소스 코드 내부에 평문으로 노출된 API key나 패스워드 등의 자격증명을 탐지하기 위해 Gitleaks를 이용한 Secret Scanning을 수행하였다..
이어서 코드 자체의 보안 결함과 외부 의존성 문제를 해결하기 위해 2가지 정적 분석을 진행하였다.
SonarQube (SAST)를 통해 소스 코드의 구조를 분석하여 SQL Injection이나 XSS 같은 논리적 취약점을 식별하였으며, 동시에 Snyk(SCA)를 활용하여 프로젝트에서 참조하는 외부 라이브러리의 알려진 취약점(CVE)를 전수 조사하고 안전한 버전으로의 패치 경로를 확인하였다.
이후 공급망 가시성 확보를 위해 Syft로 SBOM을 생성하여 이미지 내부에 포함된 모든 패키지와 라이브러리 목록을 표준형식으로 명세화하였다.
마지막 단계로 Cosign을 활용하여 신뢰 체계를 완성하였다. 이미지의 고유 지문(Digest)에 디지털 서명을 날인하여 제작자의 신원을 증명하였고, 앞서 생성한 SBOM을 Attestation으로 이미지에 암호학적으로 결합하였다. 이 과정을 통해 배포 시점에 이미지의 출처 뿐만 아니라 내부 성분의 무결성을 한번에 검증할 수 있는 보안 가드레일을 구축하였다.

