pico CTF
このCTFは、初心者向けの常設CTFである。
3カ月ほどコツコツやっていたが、その中でもいい感じに解法がまとめられた・まとめる価値があるものをピックアップする。
##logon(web)
やったこと
ユーザ名とパスワードを入力するログインフォームがあった。
SQLインジェクションかなと、インジェクションができそうなものを打ち込んだら、通った。
しかし、flagは表示されなかった。
次に試しに雑なusernameとpasswordを打ち込んでも、同じように認証は通ったが、同じくflagはなかった。
つまり、入力は無関係だと分かった。
ページのスクリプトをみて、"pico"などで検索をかけてもflagは出てこなくて、やり方がわからなかったので調べた。
解き方
Edit this cookieというChromeの拡張機能を用いて、usernameとpasswordの他に、adminという情報が記録されていることが分かったので、これをTrueにして、読み込むとflagが表示された
"Where are the robots"(web)
やったこと
この問題のwebページでは、入力フォームが表示されることはなく、真っ黒な画面に赤文字でwelcome、白文字でWhere are the robots?と書いてあるだけだった。
背景の色と文字が同化している、という可能性を感じて、CSSの色の設定を変えてみたが、何も表示されなかった。
また、コンテンツが再背面になっているかとも考え、順番を入れ替えることも行ってみたが、何も変わらなかった。
解き方
robots.txt ファイルは、クローラがどのページやファイルをサイトからリクエストできるか、またはできないかを検索エンジン クローラに知らせるもの(Google Search Consoleヘルプより)
これを用いて、指定されたページのURL/robots.txtにアクセスした。
そして、そこに書かれていた(リクエスト不可能とされている)ディレクトリにアクセスするち、flagを見つけた。
Client-Side-again(Web)
やったこと
ソースコードを見て、flagの要素が配列に入っているのを確認した。
そのあと、if文を使って判定している。
解き方
javascriptのコード整形ツールを利用して、きれいにする。
var _0x5a46 = ['25df2}', '_again_b', 'this', 'Password Verified', 'Incorrect password', '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'));
}
}
javascriptのアンダーバーは、プライベート変数として定義するために利用することが多いらしい。
いくつか細かいところの疑問
-
_0x2d8f05 = _0x2d8f05 - 0x0;
これはどのような意味があるのか->今後整数として扱うため - 変数名が16進数なのはなぜ->わざと難読化するため
- documentやcheckpassとは何か->documentはブラウザが持つ変数
if文で、細かく区切られた範囲ごとに一致するか調べている。
区切る範囲は、if文の条件である、checkpassの引数()の二つの数値の間である。
writeupを参考にして、上に貼ったコードの後に出力の処理を書くと、
console.log(_0x4b5b('0x3')) // -> picoCTF{
console.log(_0x4b5b('0x4')) // -> not_this
console.log(_0x4b5b('0x5')) // -> 9f266}
console.log(_0x4b5b('0x6')) // -> _again_1
console.log(_0x4b5b('0x7')) // -> this
console.log(_0x4b5b('0x8')) // -> Password Verified
console.log(_0x4b5b('0x9')) // -> Incorrect password
以下のような出力が得られた。
picoCTF{
not_this
9f266}
_again_1
this
Password Verified
Incorrect password
これを、checkpassの引数の数値を参考にして、並べて全体を合わせてみるとflagが得られる。
picobrowser(Web)
user agentの偽装をする。
問題のリンクを踏み、デベロッパツールで(command+option+I)で一番右のさらなるツールを選択する。
more toolsでnetwork conditionを選択し、UAのagentをcustomにして、今回の指定のpicobrowserと設定して更新すると、flagが出てくる。
Irish-Name-Repo 1(Web)
ユーザ名とパスワードが必要なログインページを抜けられるか、という問題。
SQLインジェクションを用いる。
' OR 'A' = 'A'
を入れれば良いと思っていたが、入れなかったので、手元に合った「安全なWebアプリケーションの作り方」(徳丸本)を読んだ。
' OR 'A' = 'A' --
として、以降のSQLをコメントアウトする必要があった。
JPEGファイルから文字列を抽出する
様々な方法がある
- stringsコマンドで、ファイルないの抽出可能な文字列を表示できる
-
hexdump -C
コマンドを用いると16進数とASCII文字を出力することができる - バイナリGUIエディタを用いる
[tips]画像からflagを見つけるとき
バイナリエディタのGhexを使ってみた!
macに以下のコマンドでghexをインストールすることができた。
brew install ghex
そして、開きたいjpegファイルを引数にとってghexコマンドを叩く。
最後にflagがあることはわかっていたので、最後までスクロールした。
文字列検索の機能があったが、使ってみようとするとうまく動作しなかった。
- VSCodeの拡張機能である、hexdunmp for VSCodeを用いると、文字検索ができる上に使いやすかった。(動作が安定している)タブの上で右クリックをすると拡張機能が使える。
Easy1
ヴィジュネル暗号についての問題。キーと暗号化された文字列が教えられている。
ヴィジュネル暗号を解くためのプログラムを作成した。
なお、これはキーと暗号文が同じ長さのもののみ対応している(キーを同じ長さになるように繰り返してもらえれば大丈夫)
import sys
print("input crypted string in CAPITAL LETTER")
crypted = []
key = []
crypted = input()
print("input key in CAPITAL LETTER")
key = input()
# keyの文字の行まで下がる(行列では下方向)
# keyの文字から始まって、cryptedの文字が何列目かを求める(行列で右方向)
# Aから(左から)何列目かわかったら、それが復号した文字(行列で上方向)
if(len(crypted) != len(key)):
print("key and crypted don't match")
sys.exit()
original = []
for i in range(len(crypted)):
c = ord(crypted[i])-64 # Aから何文字目か(Aは65なので一文字目)
k = ord(key[i])-64
if(c >= k):
num = c-k+65
else:
num = 91-k+c # Zは90
original.append(chr(num)) # 一文字ずつ復号した文字を格納
print(''.join(original))