Path Traversal
파일 업로드 취약점(File Upload Vulnerability)은 웹 서비스를 통해 이용자의 파일을 서버의 파일 시스템에 업로드하는 과정에서 발생하는 보안 취약점이며, 이용자가 업로드될 파일의 이름을 임의로 정할 수 있을 때 발생합니다. 파일 업로드 취약점은 크게 Path Traversal과 악성 파일 업로드로 분류됩니다. 파일 업로드를 허용하는 대개의 서비스는 보안을 위해 특정 디렉터리에만 업로드를 허용합니다. Path Traversal 취약점은 업로드에 존재하는 이러한 제약을 우회하여, 임의 디렉터리에 파일을 업로드할 수 있는 취약점을 말합니다.
from flask import Flask, request
app = Flask(__name__)
@app.route('/fileUpload', methods = ['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['file']
f.save("./uploads/" + f.filename)
return 'Upload Success'
else:
return """
<form action="/fileUpload" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit"/>
</form>
"""
if __name__ == '__main__':
app.run()
정상적인 요청
파일을 정상적으로 업로드하면, 아래와 같은 HTTP 요청이 전송됩니다
POST /fileUpload HTTP/1.1
Host: storage.dreamhack.io
Origin: https://storage.dreamhack.io
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary20y3eoLVSNf9Ns5i
------WebKitFormBoundary20y3eoLVSNf9Ns5i
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
upload test !
------WebKitFormBoundary20y3eoLVSNf9Ns5i--
$ ls -lR
-rw-r--r-- 1 dreamhack staff 461 1 30 21:52 app.py
drwxr-xr-x 3 dreamhack staff 96 1 30 21:31 uploads
./uploads:
total 8
-rw-r--r-- 1 dreamhack staff 13 1 30 21:31 test.txt
서버 파일 시스템을 확인한다.
악의적인 요청
아래는 filename 필드를 변조해서 Path Traversal을 수행하는 HTTP 요청입니다.
POST /fileUpload HTTP/1.1
Host: storage.dreamhack.io
Origin: https://storage.dreamhack.io
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary20y3eoLVSNf9Ns5i
------WebKitFormBoundary20y3eoLVSNf9Ns5i
Content-Disposition: form-data; name="file"; filename="../hack.py"
Content-Type: text/plain
[malicious file content]
------WebKitFormBoundary20y3eoLVSNf9Ns5i--
요청을 전송하면, 아래와 같이 app.py 파일 위치와 같은 디렉토리에 hack.py가 생성됩니다
$ ls -lR
-rw-r--r-- 1 dreamhack staff 461 1 30 21:52 app.py
-rw-r--r-- 1 dreamhack staff 431 1 30 22:12 hack.py
drwxr-xr-x 3 dreamhack staff 96 1 30 21:31 uploads
./uploads:
total 8
-rw-r--r-- 1 dreamhack staff 13 1 30 21:31 test.txt
악성 파일 업로드
웹 셸
웹 서버는 .php, .jsp, .asp와 같은 확장자의 파일을 Common Gateway Interface(CGI)로 실행하고, 그 결과를 이용자에게 반환합니다.
<FilesMatch ".+\.ph(p[3457]?|t|tml)$">
SetHandler application/x-httpd-php
</FilesMatch>
악의적인 웹 리소스
웹 브라우저는 파일의 확장자나 응답의 Content-Type에 따라 요청을 다양하게 처리합니다. 만약 요청한 파일의 확장자가 .html 이거나, 반환된 Content-Type 헤더가 text/html일 경우 응답은 HTML 엔진으로 처리됩니다.
File Download Vulnerability
파일 다운로드 취약점(File Download Vulnerability)은 웹 서비스를 통해 서버의 파일 시스템에 존재하는 파일을 내려 받는 과정에서 발생하는 보안 취약점이며, 이용자가 다운로드할 파일의 이름을 임의로 정할 수 있을 때 발생합니다.
Q1. 파일 다운로드 취약점(File Download Vulnerability)의 설명으로 올바른 것은?
A. 웹 서비스를 통해 서버의 파일 시스템에 존재하는 파일을 내려 받는 과정에서 발생하는 보안 취약점이며, 이용자가 다운로드할 파일의 이름을 임의로 정할 수 있을 때 발생합니다.
Q2. 악성 파일 업로드를 통해 할 수 있는 행위를 모두 고르시오
A. Cookie Hijacking, Cross Site Scripting, Remote Code Execution, Cross Site Request Forgery
A. ../../../proc/self/maps, ../../proc/self/maps
A. SetHandler, application/x-httpd-php
Exercise: File Vulnerability
인덱스 페이지
<li><a href="/">Home</a></li>
<li><a href="/list.php">List</a></li>
<li><a href="/upload.php">Upload</a></li>
파일 목록 리스팅
<?php
$directory = './uploads/';
$scanned_directory = array_diff(scandir($directory), array('..', '.', 'index.html'));
foreach ($scanned_directory as $key => $value) {
echo "<li><a href='{$directory}{$value}'>".$value."</a></li><br/>";
}
?>
파일 업로드
upload.php는 이용자가 업로드한 파일을 uploads폴더에 복사하며, 이용자는 http://host1.dreamhack.games:[PORT]/uploads/[FILENAME] URL을 통해 접근할 수 있습니다.
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_FILES)) {
$directory = './uploads/';
$file = $_FILES["file"];
$error = $file["error"];
$name = $file["name"];
$tmp_name = $file["tmp_name"];
if ( $error > 0 ) {
echo "Error: " . $error . "<br>";
}else {
if (file_exists($directory . $name)) {
echo $name . " already exists. ";
}else {
if(move_uploaded_file($tmp_name, $directory . $name)){
echo "Stored in: " . $directory . $name;
}
}
}
}else {
echo "Error !";
}
die();
}
?>
Exercise: File Vulnerability-2
엔드포인트 분석: /upload
upload 페이지 코드는 /upload 페이지를 구성하는 코드입니다. 코드를 살펴보면, 파일 업로드를 처리하며 업로드된 파일을 서버에 저장하는 것을 알 수 있습니다. 이 때, 업로드된 파일 이름에 ".."가 포함되어 있는지 검사하여 상위 디렉터리로 이동하는 시도를 방지합니다.
@APP.route('/upload', methods=['GET', 'POST'])
def upload_memo():
if request.method == 'POST':
filename = request.form.get('filename')
content = request.form.get('content').encode('utf-8')
if filename.find('..') != -1: # filename에 '..'이 발견되지 않을 경우에만 if문 탈출
return render_template('upload_result.html', data='bad characters,,')
with open(f'{UPLOAD_DIR}/{filename}', 'wb') as f:
f.write(content)
return redirect('/')
return render_template('upload.html')
엔드포인트 분석: /read
read 페이지 코드는 /read 페이지를 구성하는 코드입니다. 코드를 살펴보면, 사용자가 요청한 파일을 열어서 그 내용을 읽고, 이를 웹 페이지에 표시하는 것을 알 수 있습니다.
@APP.route('/read')
def read_memo():
error = False
data = b''
filename = request.args.get('name', '')
# filename에 대한 특별한 검사 진행하지 않음
try:
with open(f'{UPLOAD_DIR}/{filename}', 'rb') as f:
data = f.read()
except (IsADirectoryError, FileNotFoundError):
error = True
return render_template('read.html',
filename=filename,
content=data.decode('utf-8'),
error=error)