[웹 해킹 #2] 파일 업로드 취약점과 웹쉘 - 확장자 우회부터 weevely 실습까지

무슨 글일까요?
이전 글(#1)에서 정보 수집으로 표적의 지도를 그렸습니다.
지도에는 로그인 페이지, 게시판, 프로필 설정 같은 '문'들이 표시되어 있을 겁니다. 이번 글은 그중 가장 흔하면서도 치명적인 문, 파일 업로드 기능을 다룹니다.
게시판 첨부파일, 프로필 사진 변경, 자료실 업로드, 리뷰에 사진 첨부... 웹사이트에서 사용자가 파일을 올리는 기능은 셀 수 없이 많습니다.
너무 흔해서 개발자도 사용자도 별생각 없이 지나치는데, OWASP는 "많은 공격의 첫 단계는 공격할 시스템에 일단 코드를 올려놓는 것" 이라고 경고하며,
파일 업로드가 그 흔한 통로라고 지적합니다. 여기서 출발하는 질문은 이렇습니다.
분명히 "이미지만 올리세요"라고 만든 기능인데, 어떻게 공격자는 여기로 서버에서 실행되는 코드를 밀어 넣을까요? 그리고 왜 그토록 막기 어려울까요?
문제의 본질 - 서버가 파일을 '실행'할 때
서버가 .jpg만 받아야 하는데 .php 파일을 받아 버리고, 그 파일이 웹에서 접근 가능한 경로에 저장되어 브라우저로 호출하면 실행되는 경우입니다.
정상: 사용자 → cat.jpg 업로드 → 서버에 이미지로 저장 → 그냥 사진
공격: 공격자 → shell.php 업로드 → 웹 경로에 저장
→ 브라우저로 shell.php?cmd=id 호출 → 서버가 PHP로 실행 → RCEOWASP가 든 전형적인 사례를 풀어 보면 이렇습니다.
서버가 업로드된 파일을 원래 이름 그대로, 웹에서 접근 가능한 디렉터리에 저장하고 그 서버가 PHP를 실행하도록 설정되어 있다면,
공격자는 https://site.com/uploads/shell.php?cmd=id에 접속하는 것만으로 원격 코드 실행(RCE)을 달성합니다.
업로드된 그 순간 끝나는 게 아니라, 저장 위치 + 실행 가능 여부라는 두 조건이 맞아떨어질 때 터지는 겁니다.
이렇게 업로드되어 서버를 원격 조종하게 해 주는 악성 스크립트를 웹쉘(Web Shell) 이라고 부릅니다.
작은 스크립트 하나가 마치 공격자에게 터미널을 쥐여 주는 셈입니다.
웹쉘 만들기 - weevely
원리를 봤으니 도구를 봅시다. weevely는 Kali Linux에 기본 탑재된 PHP 웹쉘 생성·관리 도구입니다.
단순히 명령 한 줄 실행하는 수준을 넘어, 암호화된 통신과 파일 관리·DB 접근·권한 상승 등 30개가 넘는 모듈을 제공해 거의 완전한 원격 제어 환경을 만들어 줍니다.
1단계 - 웹쉘 파일 생성
# weevely generate <비밀번호> <저장경로>
# 비밀번호 'mySecret123' 으로 shell.php 생성
weevely generate mySecret123 /root/shell.php이 명령은 난독화된 PHP 웹쉘을 만듭니다. 비밀번호는 나중에 접속할 때 인증용으로 쓰이며, 이 난독화 덕분에 단순 키워드 기반 백신·필터를 어느 정도 우회합니다.
2단계 - 취약한 업로드 기능으로 업로드
생성된 shell.php를 #1에서 찾아낸 업로드 지점(게시판 첨부, 프로필 사진 등)을 통해 표적 서버에 올립니다. 여기서 다음 섹션의 '우회'가 필요해집니다.
3단계 - 웹쉘에 접속
# weevely <업로드된_웹쉘_URL> <비밀번호>
weevely https://example.com/uploads/shell.php mySecret123접속에 성공하면 표적 서버의 쉘 프롬프트가 떨어집니다.
weevely> whoami
www-data
weevely> ls -la /var/www/htmlwww-data라는 응답이 의미심장합니다. 웹서버 프로세스의 권한으로 명령이 실행되고 있다는 뜻이고, 다음 단계(권한 상승)의 출발점이 됩니다.
공격자는 어떻게 검증을 우회하는가
현실의 서버가 .php를 순순히 받아 줄 리 없습니다. 대부분 어떤 형태로든 검증을 합니다.
문제는 그 검증이 어설픈 경우가 너무 많다는 점입니다. 공격자가 우회하는 방법들이 곧 개발자가 메워야 할 구멍이라, 하나씩 짚어 보겠습니다.
클라이언트 측 검증만 하는 경우
가장 흔한 실수입니다. 브라우저의 JavaScript로 "이미지만 됩니다"를 확인하는 건 보안 통제가 아니라 편의 기능일 뿐입니다.
공격자는 브라우저 개발자도구, curl, Burp Suite로 이 검증을 가볍게 건너뛰고 서버에 직접 요청을 보냅니다.
모든 검증은 반드시 서버 측에서 다시 해야 합니다.
Content-Type 헤더를 믿는 경우
요청 헤더의 Content-Type은 파일의 미디어 타입을 나타내는데, 일부 웹 애플리케이션이 이 값으로 파일이 유효한지 판단합니다.
그런데 이 헤더는 클라이언트가 보내는 값이라 공격자가 마음대로 조작할 수 있습니다.
shell.php를 올리면서 헤더만 image/gif로 바꾸면 통과되는 식입니다.
매직 바이트(파일 시그니처)를 우회하는 경우
조금 더 똑똑한 서버는 파일 앞부분의 매직 바이트(파일 종류를 식별하는 첫 몇 바이트)를 검사합니다. 하지만 이것도 우회됩니다.
공격자는 파일 맨 앞에 진짜 이미지의 시그니처(GIF89a)를 붙여 file 명령을 속입니다.
더 나아가 exiftool로 이미지의 주석 필드에 PHP 코드를 심으면, 겉보기엔 멀쩡한 이미지이면서 동시에 실행 가능한 코드인 폴리글랏(polyglot) 파일이 됩니다.
하나의 파일이 여러 파일 형식으로 동시에 유효한 거죠.
확장자 우회
우회 기법 | 예시 | 노리는 빈틈 |
|---|---|---|
대체 확장자 |
| 블랙리스트가 |
이중 확장자 |
| Apache 오설정 시 |
Null Byte |
| 구버전 PHP의 문자열 종료 처리 버그 |
대소문자 |
| 대소문자 구분하는 블랙리스트 |
여기서 중요한 패턴이 보입니다. 블랙리스트(금지 목록) 방식은 거의 항상 뚫립니다.
막아야 할 위험한 확장자를 전부 나열하는 건 불가능에 가깝기 때문입니다. 이 우회 기법들을 글로만 읽으면 감이 잘 안 올 수 있습니다.
PortSwigger가 각 우회 기법을 직접 실습해 볼 수 있는 무료 랩으로 정리해 두었으니,
손으로 익히고 싶다면 이 글을 추천합니다. [PortSwigger — File upload vulnerabilities]
그래서 어떻게 막을까 (방어)
공격 기법을 쭉 봤으니, 방어는 자연스럽게 "그 빈틈을 전부 메우는 다층 방어"로 정리됩니다.
OWASP File Upload Cheat Sheet가 권고하는 핵심을 추려 보면 이렇습니다.
업로드 디렉터리에서 스크립트 실행을 원천 차단합니다. 이게 가장 강력한 한 방입니다. 설령 웹쉘이 업로드되더라도 실행만 안 되면 그냥 텍스트 파일입니다.
Apache는.htaccess로 PHP 엔진을 끄고, Nginx는 해당location에서 스크립트 핸들러를 제거합니다.파일을 웹 루트 바깥에 저장합니다. HTTP URL로 직접 접근할 수 없는 디렉터리에 두고, 인증·인가를 거치는 애플리케이션 컨트롤러를 통해서만 내려받게 합니다.
파일명을 무작위로 변경합니다. 원래 이름을 그대로 쓰면 경로 추측과 경로 조작(path traversal)이 가능해집니다. UUID 같은 무작위 이름으로 바꾸세요.
블랙리스트가 아니라 화이트리스트로, 그리고 다층으로 검증합니다. 허용할 확장자만 명시하고(allowlist),
매직 바이트를python-magic·Apache Tika 같은 라이브러리로 검사하고, 파일 크기 제한도 둡니다.이미지는 서버에서 재인코딩합니다. Pillow·ImageSharp 등으로 이미지를 다시 인코딩하면, 주석 필드에 숨긴 폴리글랏 코드가 그 과정에서 제거됩니다.
"이미지 파일은 안전하지 않나요?"
그렇지 않습니다. 이미지라고 방심하면 안 되는 이유가 따로 있습니다. ImageMagick의 처리 취약점을 노리는 ImageTragick, SVG에 스크립트를 심는 SVG XSS/XXE, 그리고 위에서 본 EXIF 주석 인젝션까지, 이미지 파일도 충분히 공격 벡터가 됩니다. "확장자가 png니까 안전해"는 성립하지 않습니다.
정리
이번 글은 "사용자 입력을 신뢰하면 안 된다"는 보안 제1원칙이 가장 극적으로 드러나는 사례였습니다. 흐름을 요약하면 이렇습니다.
[업로드 기능] → [어설픈 검증] → [검증 우회] → [웹쉘 저장 + 실행] → [RCE]
↑ ↑
여기를 다층으로 막아야 실행만 차단해도 무력화특히 기억할 한 가지는, 여러 방어를 겹쳐 쌓아야 한다는 점입니다. 확장자 검사 하나, 매직 바이트 검사 하나로는 부족합니다.
공격 기법을 쌓으면 단일 방어는 무너지지만, 방어를 겹치면 공격도 무너집니다.
웹쉘로 서버에 발판(foothold)을 마련했다면, 이제 더 안정적인 제어권을 확보할 차례입니다.
다음 글에서는 리버스 쉘(Reverse Shell) 의 원리와, 왜 쉘이 '거꾸로' 연결되는지를 다루겠습니다.
이전 글 → [웹 해킹 #1] 정보 수집 다음 글 → [웹 해킹 #3] 리버스 쉘
읽어보면 좋을 추천 글
[OWASP — Unrestricted File Upload] 파일 업로드 취약점이 무엇이고 어떻게 악용되는지를 OWASP가 공격자/방어자 양쪽 관점에서 설명합니다. 본문에서 다룬 "코드를 일단 올려놓는 것이 공격의 첫 단계"라는 개념의 출처입니다.
[OWASP File Upload Cheat Sheet] 방어 측면에서 가장 실용적인 체크리스트입니다. 확장자 화이트리스트, 매직바이트 검증, 웹 루트 바깥 저장, 파일명 무작위화 등 본문 방어 섹션의 근거가 모두 여기 있습니다. 실제로 업로드 기능을 만드는 개발자라면 꼭 읽어야 합니다.
[weevely3 — 공식 저장소] 본문에서 쓴 weevely의 공식 GitHub입니다. 30개가 넘는 모듈 목록과 설치·사용법, 그리고 어떻게 트래픽을 난독화해 탐지를 회피하는지 원리가 정리되어 있습니다.
[PortSwigger — File upload vulnerabilities] 무료 실습 랩이 딸린 최고의 입문 자료입니다. 글만 읽지 말고 직접 브라우저에서 우회 기법을 단계별로 실습해 볼 수 있습니다.