개념
1. 자동화 (DevSecOps)의 필요성
수동으로 보안 도구를 실행할 때 발생하는 가장 큰 문제는 사람의 실수와 속도 저하이다.
- 지속적 검증: 개발자가 바쁘거나 실수로 Gitleaks나 Snyk 실행을 건너뛰고 코드를 합치면, 그 순간 보안이 뚫린다.
- 보안의 표준화: 모든 팀원이 동일한 보안 정책(예: 심각도 'High' 이상이면 배포 금지)을 강제 적용받도록 시스템화해야 한다.
- 가시성 확보: 보안 검사 결과가 중앙(GitHub)에서 관리되어, 누구나 해당 코드가 안전한지 즉시 확인할 있다.
2. GitHub Actions
2-1 GitHub Actions이란
단순한 자동화 도구를 넘어 CI/CD (지속적 통합 / 배포) 를 수행하는 엔진
- CI (Continuous Integration): 코드를 Push할 때마다 자동으로 Gitleaks, Snyk, SonarQube 같은 도구를 실행하여 "이 코드가 안전한가?"를 검증하는 과정.
- CD (Continuous Deployment): 검증된 코드를 자동으로 이미지로 빌드하고, Cosign으로 서명한 뒤 서버에 배포하는 과정.
2-2 주요 구성 요소 및 작동 원리
- 이벤트 (Event): "언제 실행할 것인가?" (예: 코드 Push, Pull Request 생성 시)
- 워크플로우 (Workflow): "무엇을 할 것인가?" (전체적인 보안 점검 흐름을 담은 YAML 파일)
- 러너 (Runner): "어디서 실행할 것인가?" (GitHub이 제공하는 가상 서버 또는 로컬 PC)
2-3 github actions 선택 이유
- 1) 코드와 보안의 물리적 인접성 (Native Integration)
- 소스 코드 저장소 내부에 .github/workflows 경로로 파이프라인 코드가 함께 존재함.
- 코드 변경 시 별도의 연동 설정 없이 즉각적인 보안 피드백 루프(Feedback Loop) 형성이 가능함.
- 2) 풍부한 보안 도구 생태계 (GitHub Marketplace)
- Gitleaks, Snyk, SonarQube, Cosign 등 1주차에 다룬 주요 도구들이 이미 '공식 Action' 형태로 최적화되어 제공됨.
- 복잡한 스크립트 작성 없이 검증된 모듈을 호출하는 것만으로 보안 가드레일 구축이 가능함.
- 3) 강력한 권한 및 민감 정보 관리 (Secret Management)
- DockerHub 토큰, 개인키(cosign.key), API 키 등을 GitHub Secrets에 암호화하여 저장함.
- 로그 출력 시 민감 정보를 자동으로 마스킹(Masking) 처리하여 파이프라인을 통한 2차 유출을 원천 차단함.
- 4) 보안 게이트의 강제성 (Policy Enforcement)
- 특정 보안 검사(예: Critical 취약점 발견)를 통과하지 못할 경우, 메인 브랜치로의 코드 병합(Merge)을 물리적으로 차단
- 수동 검사 시 발생할 수 있는 '검사 생략'이나 '인적 오류'를 방어하고 표준화된 보안 정책을 유지함.
- 5) 관리형 실행 환경 (Hosted Runners)
- GitHub이 제공하는 격리된 가상 서버 환경에서 검사가 수행되므로, 로컬 환경의 오염이나 설정 차이로 인한 변수를 제거하고 일관된 결과를 보장함.
2-4 논리적 흐름 및 작동 방식
1) Workflow 정의 및 대기 (Definition)
가장 먼저 .github/workflows/ 경로에 YAML 형식의 설계도를 작성하여 저장소에 올린다. 이때 Workflow는 실행되지 않은 '설계도' 상태로 대기
2) 이벤트 감지 (Triggering)
코드 Push나 Pull Request 같은 특정 이벤트가 발생하면, GitHub 시스템이 해당 저장소의 Workflow를 실행한다.
3) 실행 환경 및 자원 할당 (Job Provisioning)
Workflow 내부에 정의된 Job 단위로 가상 서버(Runner)를 요청합니다.만약 Job이 여러 개라면, Workflow는 이를 동시에(병렬) 실행할지, 순서대로(직렬) 실행할지 결정하여 서버를 할당받습니다.
4) 환경 설정 및 코드 주입 (Step Execution - Setup)
가상 서버 위에서 Workflow에 작성된 Step들이 순차적으로 실행
- 코드 체크아웃: 저장소의 소스 코드를 서버로 복사
- 시크릿 주입: 암호화된 GitHub Secrets를 해당 Step의 환경 변수로 연결
5) 보안 검사 및 빌드 수행 (Step Execution - Action)
Gitleaks, SonarQube, Snyk 등의 도구가 실행되며, Workflow는 각 도구의 성공/실패 신호(Exit Code)를 실시간으로 모니터링한다.
6) 신뢰성 부여 및 종료 (Post-Processing)
검사가 통과되면 이미지를 빌드하고 Cosign 서명 및 어테스테이션을 수행한다. 모든 작업이 완료되면 Workflow는 할당받았던 가상 서버를 반납하고 파기
7) 최종 결과 보고 (Reporting)
Workflow는 전체 과정의 로그를 취합하여 GitHub UI에 '성공(Check)' 또는 '실패(X)' 표시를 남긴다. 실패 시 어느 단계(Step)에서 문제가 생겼는지 상세 데이터를 제공하며 전체 흐름을 종료한다.
실습
목표 : pygoat의 취약점을 github actions 자동화를 통해 잡아내고 해결한후 인증+검증까지 해서 새로운 이미지 빌드
0. 자동화 공정 과정
1. 서비스별 출입증(Token) 발급
- [ ] Snyk Token: Snyk.io 가입 후 Account Settings에서 API Token 복사
- [ ] DockerHub Token: DockerHub 접속 → Settings → Personal Access Token → New Access Token 생성 및 복사
- [ ] Cosign Key: 로컬 터미널에서 cosign generate-key-pair 실행 후 생성된 cosign.key 내용 및 비밀번호 메모
- [ ] SonarQube Token : SonarCloud 접속 → My Account → Security → Generate Token (Type: Analysis Token) 복사
- [ ] SonarCloud Info : SonarCloud 프로젝트 사이드바 → Project Information에서 Organization,Project 키 메모
- [ ] GitHub PAT : GitHub Profile → Settings → Developer settings → Personal access tokens → Fine grained tokens → Genereate new token 생성 (권한 : Contents, Workflows, Metadata, Pull requests 'Read and Write'
2. GitHub 저장소 금고(Secrets) 채우기
- 1) 저장소 생성: GitHub에 Private Repository 생성 및 소스 코드 업로드
- 2) Secrets 등록: Settings → Secrets and variables → Actions → Repository secret에서 아래 항목 등록
- SNYK_TOKEN: (Snyk 토큰)
- DOCKERHUB_USERNAME: (도커허브 ID)
- DOCKERHUB_TOKEN: (도커허브 토큰)
- COSIGN_KEY: (cosign.key 파일 전체 내용)
- COSIGN_PASSWORD: (키 생성 시 정한 비밀번호)
- GH_TOKEN : (발급받은 GitHub PAT 내용)
- SONAR_TOKEN : (SonarQube 분석 토큰)
- SONAR_HOST_URL: https://sonarcloud.io (또는 개인 서버 주소)
- SNYK_ORG_ID : (Snyk의 Organization ID)
- SONAR_ORG_KEY : (sonar organization key)
- SONAR_PROJECT_KEY : (Sonar project key)
3. 설계도(파일) 준비 및 배치
- Dockerfile: 프로젝트 최상위 경로(Root)에 배치 (이미지 빌드 명세)
- Workflow YAML: .github/workflows/ 폴더 안에 배치 (자동화 지시서)
- .dockerignore: 이미지에 포함되지 말아야 할 파일(.git, .env 등) 목록 작성
4. 공장 가동 및 모니터링
- Git Push: git add, commit, push 실행
- Actions 탭 확인: GitHub UI에서 로봇이 단계를 통과하는지 실시간 모니터링
- 결과 분석: 빨간색(실패) 확인 시 로그를 분석하여 코드 수정 후 재업로드
1. token 발급 및 Secret 채우기
Github에 pygoat-devsecops-lab라는 Private Repository를 생성한 후 위 과정에 따라서 secrets를 등록한다.

2. 설계도 파일 준비
2-1 Dockerfile, dockerignore
Dockerfile은 Pygoat 기존의 파일을 사용하며
dockerignore 파일을 만들어 컨테이너 빌드 시 불필요하거나 민감한 파일이 이미지 내부에 포함되는 것을 차단한다.
# 1. 버전 관리 및 CI/CD (보안 핵심)
.git
.gitignore
.github/
# 2. 파이썬 및 의존성 (경량화 핵심)
venv/
.venv/
__pycache__/
*.pyc
*.pyo
*.pyd
# 3. 도커 관련 (중복 방지)
Dockerfile
.dockerignore
# 4. 민감 정보 및 로그 (유출 방지)
.env
.flaskenv
*.log
*.key
*.pem
*.db
*.sqlite3 # PyGoat의 로컬 DB 파일 유출 차단
# 5. 개발 툴 및 기타
.vscode/
.idea/
.DS_Store
*.sbom
bom.json
2-2 workflow 작성
.github/workflows에 파일 생성
name: DevSecOps 1-Week Automation Pipeline
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
security-scan:
name: Security & Quality Analysis
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
# 1. Gitleaks: 시크릿 유출 탐지
- name: Gitleaks Scan
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
# 2. SonarQube: 정적 코드 분석 (SAST)
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
args: >
-Dsonar.projectKey=${{ secrets.SONAR_PROJECT_KEY }}
-Dsonar.organization=${{ secrets.SONAR_ORG_KEY }}
# 3. Snyk: 오픈소스 취약점 및 코드 스캔 (SCA/SAST)
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/python@master
continue-on-error: true # 실습 흐름을 위해 우선 진행
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --org=${{ secrets.SNYK_ORG_ID }} --sarif-file-output=snyk.sarif
build-and-sign:
name: Build, SBOM and Image Signing
needs: security-scan
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
# 4. Docker Build & Push
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Docker Image
uses: docker/build-push-action@v5
id: build-push
with:
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/pygoat:devsecops
# 5. Syft: SBOM 생성 (공급망 보안)
- name: Generate SBOM (Syft)
uses: anchore/sbom-action@v0
with:
image: ${{ secrets.DOCKERHUB_USERNAME }}/pygoat:devsecops
format: 'spdx-json'
output-file: 'sbom.spdx.json'
# 6. Cosign: 컨테이너 이미지 서명
- name: Install Cosign
uses: sigstore/cosign-installer@v3.5.0
- name: Write Cosign Key
run: echo "${{ secrets.COSIGN_KEY }}" > cosign.key
- name: Sign Image
run: |
cosign sign --key cosign.key \
-y ${{ secrets.DOCKERHUB_USERNAME }}/pygoat:devsecops
env:
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
# 7. Cosign: SBOM 어테스테이션 생성 및 서명
- name: Attest SBOM
run: |
cosign attest --key cosign.key \
--type spdxjson \
--predicate sbom.spdx.json \
-y ${{ secrets.DOCKERHUB_USERNAME }}/pygoat:devsecops
env:
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
1) 파이프라인 트리거 (스위치 작동)
- Trigger: 사용자가 main 브랜치에 코드를 Push하거나 Pull Request를 생성.
- 감지: GitHub 서버가 저장소 내 .github/workflows/devsecops-pipeline.yml 파일을 읽어 가상 머신(Runner) 배정.
2) Job 1: Security & Quality Analysis (보안 검증 단계)
이 작업은 코드가 안전한지 확인하는 첫 번째 관문임.
- Step 1. Checkout: 가상 머신으로 소스 코드를 복제. (분석 대상 확보)
- Step 2~4. 순차적 스캔: Gitleaks → SonarQube → Snyk 순서로 실행.
- 직렬 구조: 이전 단계가 끝나야 다음 도구가 실행됨.
- 로그 기록: 각 도구의 실행 결과와 취약점 리포트가 GitHub Actions 로그 및 각 서비스 대시보드로 전송됨.
- 결과: 모든 스캔이 'Pass' 되어야 이 Job이 Success 상태가 됨.
- Job 간의 연결 고리: needs (가드레일)
- 의존성 제어: build-and-sign 작업에 설정된 needs: security-scan이 핵심.
- 차단 로직: 만약 security-scan에서 시크릿이 발견되거나 취약점이 너무 많아 Failure가 뜨면, 다음 Job인 빌드 단계는 자동으로 취소(Skipped)됨.
- 목적: 취약한 코드가 이미지로 빌드되어 배포되는 것을 원천 차단.
3) Job 2: Build, SBOM and Image Signing (패키징 및 신뢰 부여)
- Step 1. Docker Build & Push: 검증된 코드를 이미지로 빌드하여 Docker Hub에 업로드.
- Step 2. Syft (SBOM 생성): 빌드된 이미지의 구성 요소 명세서 작성.
- Step 3. Cosign (서명): 생성된 이미지에 디지털 서명을 하여 '공인된 이미지'임을 증명.
3. 자동화 실행
만들어놨던 private repository에 소스코드 push후 Actions에서 만들었던 workflow 확인


문제점 : 분명 취약점이 많았는데 security scan 단계가 통과
3-1 문제점 확인
분명 Pygoat 파일을 gitleaks, sonarqube, synk 분석 도구로 각각 확인했을 때는 취약점이 잘 탐지됐는데 자동화 과정에서 탐지가 제대로 안되는 문제가 발생하였다.
1) Sonarqube 탐지 오류
1-1) quality Gate 적용 안 됨
Sonarqube 대시보드에서는 취약점이 발견됐지만 quality gate가 적용되지 않은것 같다.
custom quality gate를 다시 만들고 workflow의 sonarcloud scan 부분에 quality 결과를 기다리는 부분을 추가한다.
- name: SonarCloud Scan
uses: SonarSource/sonarqube-scan-action@master
with:
args: >
-Dsonar.projectKey=${{ secrets.SONAR_PROJECT_KEY }}
-Dsonar.organization=${{ secrets.SONAR_ORG_KEY }}
-Dsonar.qualitygate.wait=true # Quality Gate 결과를 기다리고 실패 시 빌드 중단
-Dsonar.scm.disabled=tre # 모든 코드를 새로운 코드로 인식하도록 강제
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
-> 자꾸 새로운 코드 인식을 못함 overall 코드를 quality gate에 넣으려면 유료 계정이 필요함.
1-2) Sonarqube 리포트 기반 quality gate 재작성
sonarqube에서 제공하는 pass/fail은 무시하고 리포트를 받아서 우리가 직접 정한 규칙을 만족하면 통과하게 바꿈
check_gate라는 파일을 생성해서 realiability, security, coverage, hotspots 기준을 직접 정해서 workflow에 check_gate 실행 부분 추가
# 2. SonarQube: 정적 코드 분석 (SAST)
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
args: >
-Dsonar.projectKey=${{ secrets.SONAR_PROJECT_KEY }}
-Dsonar.organization=${{ secrets.SONAR_ORG_KEY }}
-Dsonar.qualitygate.wait=false
-Dsonar.scm.disabled=true
# 이 옵션이 핵심! 판정 실패 시 빌드 중단
- name: Custom Security Gate Check
run: python check_gate.py "${{ secrets.SONAR_PROJECT_KEY }}" "${{ secrets.SONAR_TOKEN }}"
check_gate.py
import requests
import sys
# 설정 값
PROJECT_KEY = sys.argv[1]
SONAR_TOKEN = sys.argv[2]# GitHub Secrets에서 전달
URL = f"https://sonarcloud.io/api/measures/component?component={PROJECT_KEY}&metricKeys=security_rating,reliability_rating,coverage,security_hotspots"
response = requests.get(URL, auth=(SONAR_TOKEN, ''))
data = response.json()
# 지표 추출
measures = {m['metric']: m['value'] for m in data['component']['measures']}
# 기준 정의 (A=1.0, B=2.0 ...)
security = float(measures.get('security_rating', 5.0))
reliability = float(measures.get('reliability_rating', 5.0))
coverage = float(measures.get('coverage', 0.0))
hotspots = int(measures.get('security_hotspots', 1000))
# 검증 로직
failed = False
if security > 1.0: print("❌ Security Rating is not A"); failed = True
if reliability > 1.0: print("❌ Reliability Rating is not A"); failed = True
if coverage < 0.0: print(f"❌ Coverage ({coverage}%) is under 80%"); failed = True
if hotspots > 100: print(f"❌ Hotspots ({hotspots}) are over 100"); failed = True
if failed:
sys.exit(1) # 빌드 실패 처리
else:
print("✅ All Security Gates Passed!")
sys.exit(0)

2) gitleaks 탐지 오류
gitleaks-action 방식이 문제였음
- name: Gitleaks Scan
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
with:
args: detect --all --verbose <-- 인식 안 됨 (에러 발생)
- 진단: v2 버전에서 with.args 입력을 지원하지 않아 설정값이 무시됨.
- 결과: 기본 설정인 '최근 1개 커밋'만 스캔하여 기존 취약점을 모두 놓침.
수정 후
- name: Gitleaks Scan
run: |
docker run -v ${PWD}:/path zricethezav/gitleaks:latest detect --source="/path" -v --exit-code=2

3) Snyk 탐지 오류
requirements.txt를 읽긴 했지만, 실제 분석을 위한 의존성 그래프(Dependency Tree) 빌드 단계에서 환경 문제로 멈춰버린 상태
-> 의존성 사전 설치
# 3. Snyk: 오픈소스 취약점 및 코드 스캔 (SCA/SAST)
- name: Install dependencies and Snyk CLI
run: | # 1. 의존성 사전 설치 (Snyk의 분석을 돕기 위함)
python -m pip install --upgrade pip
pip install -r requirements.txt
npm install -g snyk
# 2. Snyk CLI 설치
- name: Run Snyk Test
continue-on-error: false # 실습에서는 취약점이 있어도 성공하려면 true로 변경하면됨
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
run: | # --skip-unresolved: 설치 안 되는 패키지는 건너뛰고 분석 강행 # --org: 네 Snyk 조직 ID 연결
snyk test --file=requirements.txt \
--org=${{ secrets.SNYK_ORG_ID }} \
--skip-unresolved=true \
--sarif-file-output=snyk.sarif
4. 취약점 해결
이제 본격적으로 취약점을 해결해보자
4-1 gitleaks 취약점 해결
1) Project_key 커밋 기록

check_gate 파일을 만들떄 처음에 Poject_key를 하드코딩 했었는데 이게 git 기록에 남아서 gitleaks에 걸렸다.
- 토큰 재발급
- git 히스토리 영구 삭제
- git-filter-repo를 이용한 시크릿 살균
- 원격 저장소 재연결 후 서버 강제 업데이트
# 1. 도구 설치 (최초 1회)
pip install git-filter-repo
# 2. 히스토리 내 시크릿 치환 실행
# '유출된_시크릿'을 찾아 '***REMOVED***'로 강제 치환합니다.
git filter-repo --replace-text <(echo "유출된_시크릿==>***REMOVED***") --force
# 본인의 원격 저장소 주소를 다시 등록
git remote add origin https://github.com/사용자명/저장소명.git
# 모든 브랜치의 히스토리를 강제로 덮어씁니다.
git push origin --force --all
2) CSRF 토큰 노출

CSRF(Cross-Site Request Forgery, 사이트 간 요청 위조)란 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 만드는 공격 기법
--> filter-repo로 위 과정과 마찬가지로 git 히스토리 삭제 + 현재 로컬 파일 내용 변경
3) JWT 토큰 노출

마찬가지
4) 사용자 비밀번호 하드코딩

git show 를 사용해서 git 히스토리를 살펴보니 user1부터 4까지 비밀번호가 노출되있었다.
git show 05c0f787a0:pygoat/introduction/views.py | sed -n '1010,1025p'

마찬가지로 filter-repo 사용
git filter-repo --replace-text <(cat <<EOF
491a2800b80719ea9e3c89ca5472a8bda1bdd1533d4574ea5bd85b70a8e93be0==>***REMOVED***
c577e95bf729b94c30a878d01155693a9cdddafbb2fe0d52143027474ecb91bc==>***REMOVED***
5a91a66f0c86b5435fe748706b99c17e6e54a17e03c2a3ef8d0dfa918db41cf6==>***REMOVED***
6046bc3337728a60967a151ee584e4fd7c53740a49485ebdc38cac42a255f266==>***REMOVED***
EOF
) --force
4-2 SonarQube 취약점 해결
SonarQube의 리포트를 읽어서 직접 정한 보안 규칙에만 통과하도록 취약점을 골라서 해결해보자
현재 기준
- security : A이상
- Reliability : A 이상
- Coverage : 0% 이상( 그냥 통과)
- Security Hotspots : 100개 이하

pygoat 자체가 취약점을 가지는 앱이므로 직접 다 제거하기에는 너무 많고 가장 취약한 Blocker만 해결하고 나머지는 status를 False Positive나 Won't Fix로 변경하여 통과하도록 하자.
1) 안전하지 않은 역직렬화 취약점

4주차 Falco를 이용한 런타임 보안 및 eBPF 기반 위협 탐지 실습에서 사용될 수도 있어서 일단 고치지 않고 status만 변경
2) 불필요한 네트워크 노출

- 진단
- 불필요한 네트워크 노출(CWE-1327) 발생.
- 웹 서버가 루프백(127.0.0.1)이 아닌 모든 네트워크 인터페이스(0.0.0.0)에 바인딩되어 외부 접근이 무분별하게 허용된 상태임.
- 위협
- 공격 면적 확대: 로컬 테스트 목적의 서비스가 공인 IP나 외부 네트워크에 노출되어 취약점 스캔 및 무차별 대입 공격의 타겟이 됨.
- 내부망 이동(Lateral Movement): 동일 네트워크 내의 다른 컨테이너나 호스트가 보안 인증 없이 해당 서비스에 접근하여 데이터를 탈취하거나 로직을 악용할 수 있음.
- 해결 방안
- 호스트 바인딩 주소 수정: app.run(host='127.0.0.1', port=8080)으로 변경하여 로컬 호스트 내부에서의 통신만 허용.
- 환경 변수 활용: 배포 환경에 따라 호스트 주소를 동적으로 할당할 수 있도록 os.environ.get() 방식을 도입하여 코드 내 하드코딩 방지.
- 보안 가드레일 적용: GitHub Actions CI 과정에서 0.0.0.0 바인딩을 탐지할 경우 빌드를 실패하게 만드는 자동화된 정책 검사(Policy as Code) 반영.
3) Django secret key 하드코딩

1. GitHub Secrets 설정 (보관)
- GitHub 저장소의 Settings > Secrets and variables > Actions 메뉴로 이동
- Name: DJANGO_SECRET_KEY
- Value: 실제 사용 중인 Django 시크릿 키 값 입력
2. 코드 수정 (연동)
- 위치: settings.py 파일 내 SECRET_KEY 정의 부분 수정.
- 내용: 하드코딩된 문자열을 삭제하고, os.environ.get()을 사용하여 환경 변수로부터 값을 읽어오도록 변경함.
import os SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'default-safe-key-for-dev')
3. 워크플로우 YAML 수정 (주입)
- 환경 변수 선언: .github/workflows/ 내 YAML 파일의 env 섹션에 시크릿을 매핑함.
- 문법: ${{ secrets.DJANGO_SECRET_KEY }} 형식을 사용하여 GitHub에 저장된 값을 가져옴.
4. Git 반영 (완료)
4) 원격 코드 실행(Remote Code Execution, RCE) 취약점

1. 이슈 분석
- 원인: 사용자로부터 입력받은 데이터(request.POST.get('expression'))를 검증 없이 파이썬의 eval() 함수에 직접 전달함.
- 위협: eval()은 문자열을 파이썬 코드로 실행하는 함수. 공격자가 단순한 수식 대신 __import__('os').system('rm -rf /')와 같은 명령어를 보내면 서버의 모든 데이터가 삭제되거나 시스템이 장악될 수 있다.
2. 해결 방안 (보안 가이드라인)
- ast.literal_eval 사용 (안전한 파이썬 객체 변환)
5) command Injection 취약점

1. 취약점 분석
- 원인: request.POST.get('ip')로 받은 값을 nmap 명령어 뒤에 그대로 붙여서 shell=True 옵션과 함께 subprocess.Popen으로 실행함.
- 위협: 공격자가 IP 대신 127.0.0.1; cat /etc/passwd와 같은 값을 입력하면, 서버는 nmap 실행 후 바로 이어서 서버의 민감한 파일 내용을 유출하는 명령을 수행함.
2. 해결 방안 (보안 가이드라인)
가장 권장되는 방식은 shell=True를 제거하고, 명령어를 리스트(List) 형태로 전달
# L233-234 수정
def command_out(command_list):
# shell=False(기본값)를 사용하고 리스트로 인자를 전달하여 인젝션 방어
process = subprocess.Popen(
command_list,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
return process.communicate()
# L241-243 수정
ip = request.POST.get('ip')
# 명령어를 문자열 합치기가 아닌 리스트 형태로 구성
command = ["nmap", ip]
res, err = command_out(command)
6) JavaScript의 암묵적 전역 변수 생성

1. 이슈 분석
- 원인: comment 변수를 선언할 때 let, const, 또는 var 키워드를 사용하지 않음
- 현상: JavaScript 엔진은 선언 키워드가 없는 변수를 만나면 이를 전역 객체(브라우저의 경우 window)의 속성으로 할당
- 위협:
- 네임스페이스 오염: 다른 스크립트에서 같은 이름의 전역 변수를 사용할 경우 데이터가 덮어씌워지는 충돌이 발생
- 메모리 누수: 함수 실행이 끝나도 전역 변수는 메모리에 계속 남아 있어 자원 낭비
- 의도치 않은 동작: SendToServer() 함수 밖에서도 comment 값에 접근하거나 수정할 수 있게 되어 프로그램의 흐름을 예측하기 어렵게 만듭니다.
2. 해결 방안 (Best Practice)
상황에 맞는 키워드를 사용하여 변수를 명시적으로 선언
7) SQL Injection 취약점

1. 이슈 분석
- 원인: request.POST.get으로 받은 데이터를 검증 없이 SQL 쿼리 문장에 직접 삽입함.
- 현상: 공격자가 패스워드 입력란에 ' OR '1'='1과 같은 값을 넣으면, 쿼리가 SELECT * FROM ... WHERE user='...' AND password='' OR '1'='1'이 되어 인증을 우회하고 모든 계정 정보를 탈취할 수 있음.
- 위협: 데이터베이스의 모든 정보 유출, 데이터 삭제 또는 변조, 관리자 권한 탈취 등 파괴적인 결과를 초래함.
2. 해결 방안 (보안 가이드라인)
Django의 ORM 기능을 사용하거나, 로우 쿼리(Raw Query)를 써야 한다면 반드시 매개변수화된 쿼리(Parameterized Query) 방식을 사용해야 합니다.
방법 A: Django ORM 사용 (가장 권장)
# 수정된 코드
val = login.objects.filter(user=name, password=password)
방법 B: 매개변수화된 로우 쿼리 사용
# L158 ~ L162 수정
sql_query = "SELECT * FROM introduction_login WHERE user=%s AND password=%s"
val = login.objects.raw(sql_query, [name, password]) # 변수를 리스트로 안전하게 전달
8) XML 외부 엔티티(XML External Entity, XXE) 취약점

1. 이슈 분석
- 원인: parser.setFeature(feature_external_ges, True) 설정으로 인해 XML 파서가 외부 리소스를 불러올 수 있게 허용됨.
- 현상: 공격자가 악의적인 XML 데이터를 전송하여 서버 내부 파일(예: /etc/passwd)을 읽거나 내부 네트워크 스캔, SSRF 공격을 수행할 수 있음.
- 위협: 서버 기밀 데이터 유출 및 시스템 통제권 상실 등 매우 치명적인 보안 사고 유발 가능.
2. 해결 방안 (보안 가이드라인)
XXE 공격을 방어하는 가장 확실한 방법은 파서의 외부 엔티티 참조 기능을 명시적으로 끄는 것
# L259 수정
# 외부 엔티티(External Entities) 및 파라미터 엔티티 기능을 False로 설정
parser.setFeature(feature_external_ges, False)
parser.setFeature(feature_external_pes, False)
나머지는 high 이하들은 checkgate 기준을 좀 낮춰서 통과시켰다.
4-3 Snyk 취약점 해결
취약점이 있는 라이브러리 버전을 변경만 하면 됨. 이번에는 그냥 통과
continue-on-error: true # 실습에서는 취약점이 있어도 성공하려면 true로 변경하면됨
5 도커 이미지 확인 및 서명 검증

- 애플리케이션 이미지 (devsecops): 실제 서비스가 실행되는 원본 컨테이너 이미지.
- 서명 파일 (.sig): 원본 이미지의 Digest(고유 식별값)를 기반으로 생성된 무결성 보증서.
- 어테스테이션 파일 (.att): 이미지 내부에 포함된 소프트웨어 명세(SBOM) 등을 증명하는 파일.
- 이전 기록 (sha256-...): 이전 빌드 결과물로, 코드가 수정되면 Digest가 변하기 때문에 새로운 서명 세트와 별개로 남겨진 과거 이력.
공개키로 이미지 검증
cosign verify --key cosign.pub [Dockerhub_ID]/pygoat:devsecops


