前面在总结攻防世界web系列的WP,然后碰到了这题,是16年CSAW CTF Qual的题,原本以为这里的题都会比较基础,但是没想到第二页就碰到了这样一个题,感觉通过这个题目还是能够学到点东西的。

解题过程

flag1

首先打开题目,是一个论坛,有登录、注册、发表、评论等操作,最开始考虑是否能够进行注入但是发现没什么用,扫目录也找不到什么可以利用的文件,源码中也没有提示。

于是考虑多点开几个页面,对于源码和url等多观察,发现在查看别人的post时,url为"http://111.198.29.45:49606/post.wtf?post=NpO4g#1"形式,是否post传输的是一个目录参数,通过目录参数来获取存储的post数据?这也是现在唯一找到的一个可控的参数。

因此考虑修改post参数,看看是否会有什么变化。最后发现,如果输入"…/",则会出现目录遍历,可以查看到网站源码。在搜索出来的源码中搜索,可以发现这样的代码。

对代码进行美化,结果如下:

<html>
<head>
<link rel="stylesheet" type="text/css" href="/css/std.css">
<title></title>
</head>
<body>
$ if contains 'user' ${!URL_PARAMS[@]} &amp;&amp; file_exists "users/${URL_PARAMS['user']}" $ then $ local username=$(head -n 1 users/${URL_PARAMS['user']}); $ echo "
<h3>
${username}'s posts:
</h3>"; $ echo "
<ol>
<li style="list-style: none">"; $ get_users_posts "${username}" | while read -r post; do $ post_slug=$(awk -F/ '{print $2 "#" $3}' &lt;&lt;&lt; "${post}"); $ echo ""; $ done $ echo "
</li>
<li>
<a href="/&quot;/post.wtf?post=${post_slug}/&quot;">$(nth_line 2 "${post}" | htmlentities)</a>
</li>
</ol>"; $ if is_logged_in &amp;&amp; [[ "${COOKIES['USERNAME']}" = 'admin' ]] &amp;&amp; [[ ${username} = 'admin' ]] $ then $ get_flag1 $ fi $ fi
</body>
</html>

主要在于这里:

$ if is_logged_in &amp;&amp; [[ "${COOKIES['USERNAME']}" = 'admin' ]] &amp;&amp; [[ ${username} = 'admin' ]] $ then $ get_flag1 $ fi $ fi

代码的意思为,将username变为admin,并且cookies为admin则可以获取flag1(flag不止一个部分???)

先抓取一个自己创建的用户的包看看:

可以看到,这里面cookie中有一个token,另外username可以直接修改为admin,因此需要获取到admin的token

观察到token貌似为base64编码,但是尝试后并不是…只能看看是否有哪里藏有这一信息,回到刚刚的源码中,查找一些是否有user信息,发现有一个users,将其放到post参数中可以得到admin的token。

得到了admin的token,于是抓包改包,将自己账户设置为admin,多查看几个页面,发现在打开"Profile"页时可以得到flag的一部分(果然flag是分部分的)

flag2

下面考虑如何找到flag的其他部分,这里继续审计刚刚查找出来的源码,看是否能够找到一些其他可以利用的地方,获取flag的其他部分。

这里有两个技巧,一个是看常用的一些关键路径,一个是直接关键词搜索"exec"看看是否有可以命令注入执行的函数,发现有一个"#!/usr/local/bin/bash"文件,对代码进行审计,得到源码如下

max_page_include_depth=64
page_include_depth=0
function include_page {

# include_page pathname
local pathname=$1
local cmd=
[[ ${pathname(-4)} = '.wtf' ]];
local can_execute=$;
page_include_depth=$(($page_include_depth+1))

if [[ $page_include_depth -lt $max_page_include_depth ]]
then
local line;
while read -r line; do

# check if we're in a script line or not ($ at the beginning implies script line)
# also, our extension needs to be .wtf

[[ $ = ${line01} && ${can_execute} = 0 ]];
is_script=$;
# execute the line.

if [[ $is_script = 0 ]]
then
cmd+=$'n'${line#$};

else
if [[ -n $cmd ]]
then
eval $cmd log Error during execution of ${cmd};
cmd=
fi

echo $line
fi

done ${pathname}
else
echo pMax include depth exceeded!
pfi
}

审计源码,可以看出,改代码可以上传wtf文件,并且可以执行这一文件,于是尝试上传wtf文件或者注入wtf命令以达到命令执行获取flag的目的,但是这里并没有给出如何进行上传,需要继续审计代码。看到下面的代码:

function reply {

local post_id=$1;
local username=$2;
local text=$3;
local hashed=$(hash_username "${username}");

curr_id=$(for d in posts/${post_id}/*; do basename $d; done | sort -n | tail -n 1);
next_reply_id=$(awk '{print $1+1}' <<< "${curr_id}");
next_file=(posts/${post_id}/${next_reply_id});

echo "${username}" > "${next_file}";
echo "RE: $(nth_line 2 < "posts/${post_id}/1")" >> "${next_file}";
echo "${text}" >> "${next_file}";

# add post this is in reply to to posts cache
echo "${post_id}/${next_reply_id}" >> "users_lookup/${hashed}/posts";
}

这里发现可以通过post上传文件,另外这里在发post的时候,“echo “${username}” > “${next_file}”;” 这一句会将用户名写入到wtf文件中,于是想到可以将命令构造为用户名注入到wtf文件中,由服务器执行即可获取flag,这里需要注意wtf.sh只运行文件扩展名为.wtf的脚本和前缀为’$'的行。

需要先构造一个payload查询flag文件名,这里创建用户为 ${find,/,-iname,get_flag2} ,因为上面是reply函数,所以抓取reply的包进行改包。另外的话,在改包时将post修改为上传的wtf文件,这里注意需要添加%09,否则不会将这个文件进行解析,而是将其看成是目录
抓包改包发送后,访问wtf文件,可以看到命令执行后的回显


回显中可以看到get_flag2的路径,于是创建用户名为 $/usr/bin/get_flag ,重复上述步骤即可得flag2