[KT Cloud TechUp] 파일 업로드 취약점 환경 구현 및 침투 실습
October 23, 2025
Part 1: Python으로 웹서버에 파일 업로드하기
파일 업로드 취약점의 심각성 - 다른 취약점 10개를 합친 것보다 웹셸 취약점 1개가 더 치명적이다! 이유 - 즉시 시스템 명령어 실행 가능, 파일 시스템 완전 접근, 데이터베이스 직접 조작, 추가 악성코드 설치 가능, 네트워크 스캔 및 내부 확산
🎯 실습 목표
- Python으로 HTTP POST 요청을 통한 파일 업로드
- XAMPP + PHP로 파일 업로드 서버 구축
- 다중 파일 업로드 자동화
🛠️ 준비물
- XAMPP (Apache + PHP)
- Python 3.x
- requests 라이브러리
- 테스트용 이미지 파일 (1.jpg)
📂 폴더 구조
bash 코드 복사 바탕화면/kt cloud techup/코드/1027_python_file_upload/ ├── 1.jpg # 업로드할 이미지 파일 └── file_upload.py # Python 스크립트
C:\xampp\htdocs\file_upload
├── upload_handler.php # PHP 업로드 핸들러
└── uploads/ # 업로드된 파일이 저장될 폴더 (자동생성)
필요한 사전 지식
- 아파치란? = 웹서버 프로그램
인터넷 브라우저 ←→ 아파치 웹서버 ←→ PHP/HTML 파일들
(요청) (중간다리) (실제 처리)
- 손님이 “짜장면 주세요” (http://localhost/file_upload/upload_handler.php 요청) 웨이터(아파치)가 주방으로 전달
- 주방(PHP)에서 짜장면을 만듦 (파일 업로드 처리)
- 웨이터가 손님에게 서빙 (결과 응답)
- htdocs 폴더란? = 웹서버의 공개 폴더 C:\xampp\htdocs\ <- 이 폴더가 웹에서 접근 가능한 “루트” 폴더이다.
htdocs\index.html → http://localhost/index.html htdocs\test\abc.php → http://localhost/test/abc.php htdocs\file_upload\upload_handler.php → http://localhost/file_upload/upload_handler.php
- htdocs = 상점의 진열장
- 진열장에 있는 것만 손님이 볼 수 있음
- 창고(다른 폴더)에 있는 건 손님이 못 봄
- 실습의 흐름 정리 (1) 웹 서버(아파치) 켜기: XAMPP -> Apache Start => 나 이제 웹서버 역할 한다!라고 컴퓨터가 선언 (2) 웹서버용 프로그램 (php) 만들기: htdocs/file_upload/upload_handler.php 생성 => 파일 받으면 저장해줄게! 라는 프로그램 작성 (3) 클라이언트 프로그램 만들기: file_upload.py 생성 => 파일을 웹서버로 보낼게! 라는 프로그램 작성
-
Python 프로그램: “POST http://localhost/file_upload/upload_handler.php Content-Type: multipart/form-data [파일 데이터…]”
-
아파치 서버: “아, POST 요청이 왔네? /file_upload/upload_handler.php 파일을 실행해야겠다”
-
PHP 프로그램: “POST 데이터 받았다! 파일을 저장하자!” → uploads 폴더에 파일 저장
-
아파치 서버: “HTTP/1.1 200 OK 파일 업로드 성공: 1.jpg”
-
Python 프로그램: “오케이, 성공했구나!”
- 왜 이렇게 실습하는지?: 실제 인터넷과 똑같은 환경을 만들기 위해
- 실제 웹사이트 환경: 클라이언트 -> 인터넷 -> 네이버 서버(아파치+php) -> 파일 업로드
- 실습 환경: 클라이언트(python) -> 로컬네트워크 -> 클라이언트(아파치+php) -> 파일 업로드
1️⃣ XAMPP 설정
XAMPP 실행 XAMPP Control Panel 실행 Apache 서비스 시작 (Start 버튼 클릭) 상태가 초록색 “Running”으로 변경되는지 확인 기본 연결 테스트 브라우저에서 http://localhost 접속하여 XAMPP 메인 페이지가 나오는지 확인
2️⃣ PHP 업로드 핸들러 작성
C:\xampp\htdocs\file_upload\upload_handler.php 파일 생성:
<?php
// upload_handler.php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$upload_dir = './uploads/';
// uploads 디렉토리가 없으면 생성
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
if (isset($_FILES['myfile'])) {
$file = $_FILES['myfile'];
$filename = $file['name'];
$temp_path = $file['tmp_name'];
$destination = $upload_dir . $filename;
if (move_uploaded_file($temp_path, $destination)) {
echo "파일 업로드 성공: " . $filename;
echo "\n사용자: " . $_POST['user'];
echo "\n메모: " . $_POST['note'];
} else {
echo "파일 업로드 실패";
}
} else {
echo "파일이 전송되지 않았습니다.";
}
} else {
echo "POST 요청만 허용됩니다.";
}
?>
🔍 PHP 코드 설명 POST 요청 체크: GET 요청은 거부하고 POST만 허용 디렉토리 자동 생성: uploads 폴더가 없으면 자동으로 생성 파일 업로드 처리: 임시 파일을 최종 목적지로 이동 추가 데이터 처리: 폼에서 전송된 user, note 데이터도 함께 처리
3️⃣ Python 클라이언트 작성
import requests
# 업로드 엔드포인트 URL
url = "http://localhost/file_upload/upload_handler.php"
file_path = "./1.jpg" # 업로드할 파일 경로
# 1.jpg 파일을 1.jpg, 2.jpg, 3.jpg... 99.jpg로 99번 업로드
for i in range(1, 100):
with open(file_path, "rb") as f:
# 파일 데이터 설정
files = {"myfile": (f"{i}.jpg", f, "image/jpeg")}
# 추가 폼 데이터
data = {"user": "alice", "note": "test upload"}
# POST 요청으로 파일 업로드
resp = requests.post(url, files=files, data=data, timeout=30)
resp.raise_for_status() # HTTP 에러 발생시 예외 처리
print("서버 응답:", resp.status_code, resp.text)
🔍 Python 코드 설명 files 파라미터: (“필드명”, (파일명, 파일객체, MIME타입)) 형식 data 파라미터: 추가 폼 데이터 (사용자 정보, 메모 등) raise_for_status(): 4xx, 5xx 에러 발생시 예외 발생 반복 업로드: 같은 파일을 다른 이름으로 99번 업로드
4️⃣ 실습 과정에서 발생한 문제들
문제 1: 404 Not Found 에러
requests.exceptions.HTTPError: 404 Client Error: Not Found 원인: 파일명 오타 실제 파일: upload_hanlder.php (n이 빠짐) 코드: upload_handler.php 해결: 파일명을 올바르게 수정
문제 2: 서버 연결 확인 방법
브라우저에서 직접 PHP 파일에 접속하면 다음 메시지가 나타남: POST 요청만 허용됩니다. 이는 정상적인 동작입니다. (브라우저 = GET 요청, 서버 = POST만 허용)
5️⃣ 실행 및 결과 확인
cd “C:\Users\사용자명\Desktop\kt cloud techup\코드\1027_python_file_upload” python file_upload.py 예상 출력 결과 makefile 코드 복사 서버 응답: 200 파일 업로드 성공: 1.jpg 사용자: alice 메모: test upload 서버 응답: 200 파일 업로드 성공: 2.jpg 사용자: alice 메모: test upload … 업로드된 파일 확인 C:\xampp\htdocs\file_upload\uploads\ 폴더에 1.jpg부터 99.jpg까지 파일이 생성됨
6️⃣ 추가 개선사항
에러 처리가 강화된 버전
import requests
import os
url = "http://localhost/file_upload/upload_handler.php"
file_path = "./1.jpg"
# 파일 존재 확인
if not os.path.exists(file_path):
print(f"에러: {file_path} 파일을 찾을 수 없습니다.")
exit()
print(f"파일 크기: {os.path.getsize(file_path)} bytes")
print("업로드 시작...")
success_count = 0
for i in range(1, 100):
try:
with open(file_path, "rb") as f:
files = {"myfile": (f"{i}.jpg", f, "image/jpeg")}
data = {"user": "alice", "note": "test upload"}
resp = requests.post(url, files=files, data=data, timeout=30)
resp.raise_for_status()
success_count += 1
print(f"✓ {i}.jpg 업로드 성공")
except Exception as e:
print(f"✗ {i}.jpg 업로드 실패: {e}")
print(f"\n업로드 완료! 성공: {success_count}/99")
🎉 정리
이번 실습을 통해 배운 점:
Python requests로 멀티파트 파일 업로드 PHP로 파일 업로드 서버 구축 XAMPP 환경에서의 웹 개발 테스트 HTTP 상태 코드와 에러 처리 파일 경로와 네이밍 중요성
Part 2: 서버에서 파일 업로드 차단하기
웹 서버에 파일 업로드 기능이 있다면 다음과 같은 위험이 있다:
🦠 악성 파일 업로드: 실행 파일, 스크립트 파일 등 💣 서버 공격: PHP, JavaScript 등을 통한 코드 실행 💾 용량 공격: 대용량 파일로 서버 용량 고갈 👤 무차별 업로드: 특정 사용자의 과도한 업로드
📂 확장된 파일 구조
바탕화면/kt cloud techup/코드/1027_python_file_upload/ ├── 1.jpg # 테스트용 이미지 ├── file_upload.py # 기본 업로드 클라이언트 └── test_blocking.py # 차단 테스트 클라이언트
C:\xampp\htdocs\file_upload
├── upload_handler.php # 기본 업로드 서버 (차단 없음)
├── upload_handler_blocked.php # 차단 기능이 있는 서버
└── uploads/ # 업로드된 파일 저장소
🚫 다층 보안 차단 시스템 구축
차단 기능이 포함된 PHP 서버 작성 C:\xampp\htdocs\file_upload\upload_handler_blocked.php:
<?php
// upload_handler_blocked.php - 보안 강화 버전
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$upload_dir = './uploads/';
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
if (isset($_FILES['myfile'])) {
$file = $_FILES['myfile'];
$filename = $file['name'];
$user = $_POST['user'] ?? '';
$note = $_POST['note'] ?? '';
// ===========================================
// 🛡️ 다층 보안 검사 시작
// ===========================================
// 1️⃣ 차단된 사용자 검사
$blocked_users = ['hacker', 'malicious', 'baduser'];
if (in_array(strtolower($user), $blocked_users)) {
http_response_code(403);
echo "차단된 사용자입니다: " . $user;
exit;
}
// 2️⃣ 파일 확장자 검사 (화이트리스트 방식)
$file_extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
$blocked_extensions = ['exe', 'php', 'js', 'html', 'bat', 'cmd', 'sh'];
if (in_array($file_extension, $blocked_extensions)) {
http_response_code(403);
echo "차단된 파일 형식입니다: ." . $file_extension;
exit;
}
// 3️⃣ 파일 크기 검사 (5MB 제한)
$max_file_size = 5 * 1024 * 1024; // 5MB
if ($file['size'] > $max_file_size) {
http_response_code(413);
echo "파일이 너무 큽니다: " . $file['size'] . " bytes (최대: " . $max_file_size . " bytes)";
exit;
}
// 4️⃣ 파일명 특수문자 검사 (정규표현식)
if (!preg_match('/^[a-zA-Z0-9._-]+$/', $filename)) {
http_response_code(400);
echo "파일명에 허용되지 않은 문자가 포함되어 있습니다: " . $filename;
exit;
}
// 5️⃣ 시간대 제한 (업무시간만 허용: 9시-18시)
$current_hour = date('H');
if ($current_hour < 9 || $current_hour >= 18) {
http_response_code(403);
echo "업무시간 외에는 파일 업로드가 제한됩니다. (현재: " . date('H:i') . ")";
exit;
}
// 6️⃣ 특정 파일명 차단 (블랙리스트)
$blocked_filenames = ['virus.jpg', 'malware.png', 'hack.gif'];
if (in_array(strtolower($filename), $blocked_filenames)) {
http_response_code(403);
echo "차단된 파일명입니다: " . $filename;
exit;
}
// ===========================================
// ✅ 모든 보안 검사 통과 - 업로드 진행
// ===========================================
$temp_path = $file['tmp_name'];
$destination = $upload_dir . $filename;
if (move_uploaded_file($temp_path, $destination)) {
echo "파일 업로드 성공: " . $filename;
echo "\n사용자: " . $user;
echo "\n메모: " . $note;
echo "\n파일 크기: " . $file['size'] . " bytes";
} else {
echo "파일 업로드 실패";
}
} else {
echo "파일이 전송되지 않았습니다.";
}
} else {
echo "POST 요청만 허용됩니다.";
}
?>
🧪 차단 기능 테스트 클라이언트
악의적인 업로드 시도를 시뮬레이션하는 테스트 코드를 작성해보겠습니다:
test_blocking.py:
import requests
# 차단 기능이 있는 서버 URL
url = "http://localhost/file_upload/upload_handler_blocked.php"
file_path = "./1.jpg"
# 🎭 다양한 공격 시나리오 테스트
test_cases = [
{"filename": "normal.jpg", "user": "alice", "note": "정상 케이스"},
{"filename": "virus.exe", "user": "alice", "note": "실행파일 업로드 시도"},
{"filename": "backdoor.php", "user": "alice", "note": "PHP 백도어 업로드 시도"},
{"filename": "normal.jpg", "user": "hacker", "note": "차단된 사용자의 업로드 시도"},
{"filename": "virus.jpg", "user": "alice", "note": "의심스러운 파일명"},
{"filename": "file@#$.jpg", "user": "alice", "note": "특수문자 파일명 공격"},
]
print("=== 🛡️ 보안 차단 시스템 테스트 ===\n")
for i, case in enumerate(test_cases, 1):
print(f"테스트 {i}: {case['note']}")
print(f"📁 파일명: {case['filename']}, 👤 사용자: {case['user']}")
try:
with open(file_path, "rb") as f:
files = {"myfile": (case["filename"], f, "image/jpeg")}
data = {"user": case["user"], "note": case["note"]}
resp = requests.post(url, files=files, data=data, timeout=30)
# 상태 코드에 따른 결과 표시
if resp.status_code == 200:
print("🟢 상태: 업로드 성공")
elif resp.status_code == 403:
print("🔴 상태: 접근 금지 (차단됨)")
elif resp.status_code == 400:
print("🟡 상태: 잘못된 요청")
elif resp.status_code == 413:
print("🟠 상태: 파일 크기 초과")
print(f"📝 서버 응답: {resp.text}")
except Exception as e:
print(f"❌ 에러 발생: {e}")
print("-" * 60)
실제 실행 결과
=== 파일 업로드 차단 테스트 ===
테스트 1: 정상 케이스
파일명: normal.jpg, 사용자: alice
상태 코드: 403
서버 응답: 업무시간 외에는 파일 업로드가 제한됩니다. (현재: 03:04)
--------------------------------------------------
테스트 2: 실행파일 차단 테스트
파일명: virus.exe, 사용자: alice
상태 코드: 403
서버 응답: 차단된 파일 형식입니다: .exe
--------------------------------------------------
테스트 3: PHP파일 차단 테스트
파일명: script.php, 사용자: alice
상태 코드: 403
서버 응답: 차단된 파일 형식입니다: .php
--------------------------------------------------
테스트 4: 차단된 사용자 테스트
파일명: normal.jpg, 사용자: hacker
상태 코드: 403
서버 응답: 차단된 사용자입니다: hacker
--------------------------------------------------
테스트 5: 차단된 파일명 테스트
파일명: virus.jpg, 사용자: alice
상태 코드: 403
서버 응답: 업무시간 외에는 파일 업로드가 제한됩니다. (현재: 03:04)
--------------------------------------------------
테스트 6: 특수문자 파일명 테스트
파일명: file@#$.jpg, 사용자: alice
상태 코드: 400
서버 응답: 파일명에 허용되지 않은 문자가 포함되어 있습니다: file@#$.jpg
--------------------------------------------------
차단 우선순위 분석
- 최우선: 시간 제한
- 테스트 1, 5에서 파일 확장자나 파일명 체크 전에 시간 조건이 먼저 적용됨
- 사용자 차단: 테스트 4에서 hacker 사용자는 시간과 관계없이 차단됨
- 파일 확장자 차단: 테스트 2, 3에서 .exe, .php 파일은 무조건 차단
- 파일명 검증: 테스트 6에서 특수문자가 포함된 파일명은 400 에러로 처리
보안 수준별 비교
테스트 항목 기본 서버 보안 서버 normal.jpg + alice ✅ 성공 ⏰ 시간제한 virus.exe + alice ✅ 성공 🚫 확장자 차단 script.php + alice ✅ 성공 🚫 확장자 차단 normal.jpg + hacker ✅ 성공 🚫 사용자 차단 virus.jpg + alice ✅ 성공 ⏰ 시간제한 file@#$.jpg + alice ✅ 성공 🚫 파일명 검증