pico CTFでweb問題を解いてみる #1
前書き
最近Webまわりの勉強をしていて、HTML, CSS, JavaScript, Ruby, Rails, PHP, SQL, NodeJSあたりを(一部は復習しつつ)一通り触ってみました。とりあえず次のステップとして、学んだことを活かして簡単なものを作ってみる...のもよいと思ったのですが、学んだことを使ってそのまま自力で実装したところでどうしても"汚い"ものが出来そうだと思ったので、まずはいろんなコードに触れてみることを優先することにしました。
色々考えたあげく、WebまわりのCTFを解いてみるという発送に至りました。よくある脆弱性をしっかりと把握しておくことはサービスを開発する上でも非常に重要なことだと思いますし、周辺知識を固めたり、いろんなコードを読むという意味でもCTFは非常に良い教材だと思います。
で、どの常設CTFがいいかなぁと思ってぱぱっとググってみたところ、picoCTFあたりが求めているものに近そうだったので (それに教材をじっくり選ぶ時間の方が無駄かなと思うので) こちらをやってみようと思います。とりあえず分野は"Web exploitation"に絞って網羅的にやっていくことにします。
なお、writeupについては散々ネットに転がっていますし、本記事はあくまでも個人的な備忘録として残すための目的で書くつもりです。
Insp3ct0r
https://play.picoctf.org/practice/challenge/18?category=1&page=1
指定されているサイトに飛んでみたら、「このページはHTML、CSS、Javascriptで書かれています」という記述があったので、とりあえずソースを表示してこれらを確認してみる。
するとHTMLには
<!-- Html is neat. Anyways have 1/3 of the flag: picoCTF{tru3_d3 -->
CSSには、
/* You need CSS to make pretty pages. Here's part 2/3 of the flag: t3ct1ve_0r_ju5t */
/* Javascript sure is neat. Anyways part 3/3 of the flag: _lucky?2e7b23e3} */
という感じで、それぞれコメントとしてflagが載っていたので、連結すると
picoCTF{tru3_d3t3ct1ve_0r_ju5t_lucky?2e7b23e3}
logon
https://play.picoctf.org/practice/challenge/46?category=1&page=1
指定サイトに飛んだところ、ログインフォームが表示された。とりあえずIDとパスワードに適当な文字列を入力してみたところ、「Success: You logged in! Not sure you'll be able to see the flag though」というメッセージが表示された(その後、IDの部分をadminに変更したり、全て空白でログインを試みても同様のメッセージが表示された)
最初はSQLインジェクションとか使うのかなぁと思っていたけどなんか違うっぽいので、とりあえずいろいろ見てみていたら、Cookieの項目の中に"admin"というものがあって"False"が設定されていた。ここをTrueに書き換えてみてページを再読み込みしてみるとFlagを得ることができた。
picoCTF{th3_c0nsp1r4cy_l1v3s_0c98aacc}
where are the robots
https://play.picoctf.org/practice/challenge/4?category=1&page=1
指定サイトに飛ぶと、「Welcome Where are the robots?」という文字だけが表示されたシンプルなサイトに飛んだ。ざっくりソースとかcookieとか見てみたけど役に立ちそうなものは何もない。そもそもrobotsってなんだろうって思ってとりあえず「web robots」とggってみた。すると検索候補にrobots.txtとか出てきたので、これだ!と思ってその辺りを色々見てみた。
「あぁ、robotってウェブのクローラーのことか〜」とかいろいろ思いながら読んで、とりあえずrobots.txtというファイルがどっかにあるんだろうということはわかった。さらに調べていくとrobots.txtはサイトのルートディレクトリに設置されているということだったので、指定サイトに *****/robots.txtと入力すると、
User-agent: * Disallow: /1bb4c.html
と表示された。 これをもとに、*****/1bb4c.html と入力すると、flagが記載されたページに飛んだ。
picoCTF{ca1cu1at1ng_Mach1n3s_1bb4c}
dont-use-client-side
https://play.picoctf.org/practice/challenge/66?category=1&page=1
サイトに飛んだら、「This is the secure login portal Enter valid credentials to proceed」と書いてあって、小さい入力ホームが添えられていた。とりあえずいつものようにページのソースとか覗いてみたら、
<script type="text/javascript"> function verify() { checkpass = document.getElementById("pass").value; split = 4; if (checkpass.substring(0, split) == 'pico') { if (checkpass.substring(split*6, split*7) == '706c') { if (checkpass.substring(split, split*2) == 'CTF{') { if (checkpass.substring(split*4, split*5) == 'ts_p') { if (checkpass.substring(split*3, split*4) == 'lien') { if (checkpass.substring(split*5, split*6) == 'lz_b') { if (checkpass.substring(split*2, split*3) == 'no_c') { if (checkpass.substring(split*7, split*8) == '5}') { alert("Password Verified") } } } } } } } } else { alert("Incorrect password"); } } </script>
というjsのコードがあった。flagは、文字列を並び替えれば得られる。
picoCTF{no_clients_plz_b706c5}
Client-side-again
https://play.picoctf.org/practice/challenge/69?category=1&page=1
とりあえず問題のタイトルがclient-sideということなのでソースを見てみた。
<script type="text/javascript"> var _0x5a46=['f49bf}','_again_e','this','Password\x20Verified','Incorrect\x20password','getElementById','value','substring','picoCTF{','not_this'];(function(_0x4bd822,_0x2bd6f7){var _0xb4bdb3=function(_0x1d68f6){while(--_0x1d68f6){_0x4bd822['push'](_0x4bd822['shift']());}};_0xb4bdb3(++_0x2bd6f7);}(_0x5a46,0x1b3));var _0x4b5b=function(_0x2d8f05,_0x4b81bb){_0x2d8f05=_0x2d8f05-0x0;var _0x4d74cb=_0x5a46[_0x2d8f05];return _0x4d74cb;};function verify(){checkpass=document[_0x4b5b('0x0')]('pass')[_0x4b5b('0x1')];split=0x4;if(checkpass[_0x4b5b('0x2')](0x0,split*0x2)==_0x4b5b('0x3')){if(checkpass[_0x4b5b('0x2')](0x7,0x9)=='{n'){if(checkpass[_0x4b5b('0x2')](split*0x2,split*0x2*0x2)==_0x4b5b('0x4')){if(checkpass[_0x4b5b('0x2')](0x3,0x6)=='oCT'){if(checkpass[_0x4b5b('0x2')](split*0x3*0x2,split*0x4*0x2)==_0x4b5b('0x5')){if(checkpass['substring'](0x6,0xb)=='F{not'){if(checkpass[_0x4b5b('0x2')](split*0x2*0x2,split*0x3*0x2)==_0x4b5b('0x6')){if(checkpass[_0x4b5b('0x2')](0xc,0x10)==_0x4b5b('0x7')){alert(_0x4b5b('0x8'));}}}}}}}}else{alert(_0x4b5b('0x9'));}} </script>
javascriptがだらだらと書いてあったが...前問と違って読みにくい。調べてみたところ、google chromeにはコードを整形してくれる機能がついてるっぽいので、とりあえずそれを使ってみた。
<script type="text/javascript"> var _0x5a46 = ['f49bf}', '_again_e', 'this', 'Password\x20Verified', 'Incorrect\x20password', 'getElementById', 'value', 'substring', 'picoCTF{', 'not_this']; (function(_0x4bd822, _0x2bd6f7) { var _0xb4bdb3 = function(_0x1d68f6) { while (--_0x1d68f6) { _0x4bd822['push'](_0x4bd822['shift']()); } }; _0xb4bdb3(++_0x2bd6f7); }(_0x5a46, 0x1b3)); var _0x4b5b = function(_0x2d8f05, _0x4b81bb) { _0x2d8f05 = _0x2d8f05 - 0x0; var _0x4d74cb = _0x5a46[_0x2d8f05]; return _0x4d74cb; }; function verify() { checkpass = document[_0x4b5b('0x0')]('pass')[_0x4b5b('0x1')]; split = 0x4; if (checkpass[_0x4b5b('0x2')](0x0, split * 0x2) == _0x4b5b('0x3')) { if (checkpass[_0x4b5b('0x2')](0x7, 0x9) == '{n') { if (checkpass[_0x4b5b('0x2')](split * 0x2, split * 0x2 * 0x2) == _0x4b5b('0x4')) { if (checkpass[_0x4b5b('0x2')](0x3, 0x6) == 'oCT') { if (checkpass[_0x4b5b('0x2')](split * 0x3 * 0x2, split * 0x4 * 0x2) == _0x4b5b('0x5')) { if (checkpass['substring'](0x6, 0xb) == 'F{not') { if (checkpass[_0x4b5b('0x2')](split * 0x2 * 0x2, split * 0x3 * 0x2) == _0x4b5b('0x6')) { if (checkpass[_0x4b5b('0x2')](0xc, 0x10) == _0x4b5b('0x7')) { alert(_0x4b5b('0x8')); } } } } } } } } else { alert(_0x4b5b('0x9')); } } </script>
かなり読みやすくなった。
この形は前問と同じ構造っぽくて、function_verify()でパスワードのチェックをしてるらしい。ただjavascriptの難読化が厄介で、2-30分ほど格闘したけど変数名とか綺麗に書き換えるのは(少なくとも今の自分のレベルだと)厳しかった。
まぁ前問の構造を踏まえて考えると _0x4b5b('0x**') という部分を見ればflagは得られそうだったため、console上でconsole.logを用いてひとつづつ出力し、並べてみたら正解にたどり着くことは可能だった。
picoCTF{not_this_again_ef49bf}
javascriptの難読化についていろいろ調べながらわかったけど、難読化はあくまでも”読みにくくしている”だけであって、セキュリティ対策としては不十分らしい(とはいえ難読化にはそのほかにもメリットとかはあるっぽい)
こちらのスライドがよかったので共有しておきます↓
JavaScript難読化読経
とりあえず続きはまた。