출처: DreamHack Wargame – Jukebox
유형: Web / PHP
공개 범위: 반(半) 풀이 + 취약점 로직 분석
플래그, 익스플로잇 코드 미포함
file_get_contents() 사용으로 PHP Stream Wrapper 악용 가능했다 이 문제는 처음 봤을 때 되게 단순해 보였다.
URL 하나 받아서 노래 정보 가져오는 웹앱이다.
근데 이런 문제들, 경험상 절대 단순하지 않다.
회사 포트폴리오로 쓸 거라서,
“어떻게 뚫었냐” 보다는 왜 취약했고, 어떤 구조적 문제가 있었는지에 집중해서 정리했다.
| 구성 요소 | 설명 |
|---|---|
| Backend | PHP |
| 핵심 함수 | file_get_contents() |
| 입력값 | 사용자 입력 URL |
| 출력 | 노래 정보(JSON) 파싱 후 렌더링 |
서버는 사용자가 입력한 URL을 그대로 가져와서,
그 응답이 JSON이면 각 필드를 화면에 출력하는 구조다.
PHP에는 Stream Wrapper라는 개념이 있다.
파일, 네트워크, 필터를 전부 URL처럼 다루는 기능이다.
대표적인 예시는 이거다.
| Wrapper | 역할 |
|---|---|
file:// | 로컬 파일 접근 |
php://filter | 스트림 데이터 변형 |
data:// | 인라인 데이터 |
http:// | 원격 리소스 |
문제는 이 서비스가
URL에 http:// 또는 https://만 포함되면 통과시키는 식으로 검증하고 있었다는 점이다.
이 말은 곧,
php://filter 같은 래퍼도 우회적으로 쓸 수 있다는 얘기다.
정리하면 제약이 이렇다.
| 제약 조건 | 설명 |
|---|---|
| 문자열 필터 | 특정 패턴 포함 시 차단 |
| 포맷 강제 | JSON + 필수 키 7개 |
| 출력 위치 | 특정 필드만 렌더링 |
이때 좀 짜증났다.
단순 LFI 문제가 아니었다.
이 세 개가 합쳐져서 문제가 커졌다.
전체 흐름은 이렇다.
[사용자 입력]
|
v
[file_get_contents()]
|
v
[Stream Filter Chain]
|
v
[JSON 형태로 재구성]
|
v
[프론트엔드 렌더링]
핵심은 데이터를 직접 보여주지 않아도 된다는 점이다.
서버가 “정상 데이터”라고 믿고 화면에 뿌리게 만들면 끝이다.
단일 인코딩은 쉽게 막힌다.
하지만 여러 필터를 체인으로 연결하면 이야기가 달라진다.
| 필터 계열 | 역할 |
|---|---|
| base64 | 바이너리 안전 인코딩 |
| quoted-printable | ASCII 우회 |
| iconv | 문자셋 변환 |
| rot 계열 | 단순 문자열 필터 우회 |
이 문제는
단순 문자열 차단 + 출력 구조 신뢰 조합의 전형적인 실패 사례다.
실무라면 최소한 이건 했어야 한다.
| 항목 | 이유 |
|---|---|
| scheme 화이트리스트 | wrapper 차단 |
| allow_url_fopen 제한 | 로컬 파일 접근 차단 |
| 응답 내용 검증 | 의미 기반 검증 |
| 출력 전 escape | XSS/LFI 연계 차단 |
| 배운 점 | 설명 |
|---|---|
| 문자열 필터는 믿을 게 못 됨 | 인코딩 한 번이면 끝 |
| 출력 구조 신뢰는 위험 | 데이터 출처가 중요 |
| PHP Stream Wrapper는 필수 지식 | 웹 보안 기본기 |
이 문제는 기교보다 개념 싸움이었다.
원리를 알면 풀리고, 모르고 있으면 계속 삽질하게 된다.
이 문제 풀면서 느낀 건 하나다.
“이거 옛날 기법 아니냐?” 싶어도,
지금도 그대로 죽는 서비스 많다.
포트폴리오용으로는
이 세 개를 같이 보여주기 좋은 문제였다.