Flash/Flexでsocketを利用するときのセキュリティサンドボックス侵害について

Flashで動くIRCクライアントを作っていたらハマった/(^o^)\のでメモ。



FlashActionScript3でsocketを利用しているとき、FlashDevelopなどのローカル開発環境では動くのに、サーバにswfをアップロードしたら動かない\(^o^)/オワタ


...というときは、セキュリティサンドボックス侵害エラーになっていることがあります。
(他に相対パスが間違ってるとかアフォなミスもよくやりがちなので注意)



まず、これが原因であることを確認するには、Flashplayerのデバッグ版というのを使うと良い。
これはAdobeのサイト

http://www.adobe.com/support/flashplayer/downloads.html

でダウンロード出来る。上記のページでWindows版のところにリストアップされている「Download the Windows Flash Player 10.3 Projector content debugger (EXE, 6.50MB)」というのをダウンロードするとスタンドアロン版のFlashPlayerデバッグ版がダウンロードされるので、これをダブルクリックして実行し、Fileメニューからswfへのパス(htmlへのパスではないよ)を入力するとサーバ上にアップロードされたswfファイルを直接実行出来る。


これで実行してみて30秒から60秒たって「セキュリティサンドボックス侵害によりサーバ****.comのport NNNNからデータを受信できません」的な(´・ω・)??

SecurityError: [SecurityErrorEvent type="securityError" bubbles=false cancelable=false eventPhase=2 text="Error #2048: セキュリティサンドボックス侵害 : http://www.myserver.jp/myflashmovie.swf は www.myserver.jp:6667 からデータを読み込めません。"]


問題の原因の詳細は、
Flash Player 9および10におけるポリシーファイル関連の変更点
http://www.adobe.com/jp/devnet/flashplayer/articles/fplayer9_security_04.html
の「ソケットポリシーファイル」(socket policy file) を参照のこと。


さてこのページを読んでも日本語が下手クソすぎて何を言っているのかわからん。Adobeよ、もうちょっとマシな翻訳家を雇え(#゚Д゚)ゴルァ!!



で試行錯誤の結果、以下の方法で回避できたよー、という報告をいたしますでござる。


まず上記のページを読むと、とにかくソケットポリシーファイルというのを、ActionScriptからconnectしにいく相手のサーバが供給しないといけないことがわかる。これは例えば以下のようなファイルである。これは http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html においてあったサンプル。

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd">
<!-- Policy file for xmlsocket://socks.example.com -->
<cross-domain-policy>
   <!-- This is a master-policy file -->
   <site-control permitted-cross-domain-policies="master-only"/>
   <!-- Instead of setting to-ports="*",
administrators can use ranges and commas -->
   <!-- This will allow access to ports 123, 456, 457, and 458 -->
   <allow-access-from domain="swf.example.com" to-ports="123,456-458" />
</cross-domain-policy>

(swf.sample.comのところは、自分のFlashを置くwebサーバのドメインに)


このファイルを、Flashがconnectしにいく相手のサーバの適当なところに置く。たとえば、

# mkdir /usr/local/flashpolicyd
# vi /usr/local/flashpolicyd/socket_policy_file.xsd

として保存する。拡張子は.xsdで。


さて、このファイル自体は極めて簡単なのですが、問題はこのファイルをTCP/IPポート843でListenしてフィードしてやらねばならんのだ。ここである程度自分でいじれるサーバ(root権限あり)でないと自動的に\(^o^)/オワタになる。

http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html

で、このページのby Peleus Uhleyとかのドヤ顔写真をさておいてよく読むと「ソケットポリシーファイルサーバ」なるプログラムのサンプルソースコードが公開されている。上記のページの右上の「Sample files [http://www.adobe.com/content/dotcom/en/devnet/flashplayer/articles/socket_policy_files/_jcr_content/articlePrerequistes/multiplefiles/node_1277808777771/file.res/flashpolicyd_v0.6[1].zip:title=flashpolicyd_v0.6.zip]」
だよ、わかった?


これをダウンロードして解凍するとPerl版とPython版の2種類のflashpolicyd(フラッシュポリシーデーモン)というファイルが入っている。

  • flashpolicyd.pl (Perl版)
  • flashpolicyd.py (Python版)

いまどきPerlPythonも入ってるでしょ。ということでどちらでもいいんですが、私の環境ではPython版はそのままでは動かんかったよー/(^o^)\

ちなみにPython版を動かしたときに出るエラーは次のとおりで、これはPythonのバージョンが古いためにwith file(path) as f: 形式の書式に対応していないのが原因。よく読んだらソースコード中にPythonのバージョンは2.5以上が必要と書いてありました。

環境によってはPythonのアップグレードが難しい場合もあると思われるので、Python2.4でも動作するように修正したバージョンを下記に貼っておきます。

それから今どきのLinuxサーバはiptablesが動いているでしょうからポート開放も忘れずに。

# vi /etc/sysconfig/iptables

のファイルに

  • A INPUT -p tcp -m tcp --dport 843 -j ACCEPT

と追加したあと、

# /sbin/service iptables restart (reloadはなかった)

とすればおk。
iptablesのコマンドで直接ルールを追加する方法もありますが、チェインの中のフィルタリング順序がわけわからなくなりがちなので上記のように直接/etc/sysconfig/iptablesをいじってからreloadするほうが個人的に趣味なんだからいいだろうるせーよ)


さてこれで準備ができた。ポリシーファイルサーバを実行してみよう。

# cd /usr/local/flashpolicyd
# ./flashpolicyd.py --file=socket_policy_file.xsd
Listening on port 843

おお、聞いてる聞いてる。

さっき動かなかったFlashをもう一度起動してみるとログがコンソールに表示される。

Connection from 192.168.8.4:1462
Valid request received
Sent policy file

みたいに表示されればおk。
動いたー\(^o^)/



あとはこれだとコマンドを終了したら繋がらなくなってしまうので、常にバックグラウンドで動かしておいてやる。

/etc/rc.d/rc.local に以下の1行を追加。

/usr/local/flashpolicyd/flashpolicyd.py --file=/usr/local/flashpolicyd/socket_policy_file.xsd 2>&1 > /dev/null &

以上。

  • -

以下はPython版をPython2.4でも動くように修正したバージョンです。ご参考まで。

#!/usr/bin/env python
#
# flashpolicyd.py
# Simple socket policy file server for Flash
#
# Usage: flashpolicyd.py [--port=N] --file=FILE
#
# Logs to stderr
# Requires Python 2.4 or later

#from __future__ import with_statement

import sys
import optparse
import socket
import thread
import exceptions
# import contextlib

VERSION = 0.1

class policy_server(object):
    def __init__(self, port, path):
        self.port = port
        self.path = path
        self.policy = self.read_policy(path)
        self.log('Listening on port %d\n' % port)
        try:
            self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
        except AttributeError:
            # AttributeError catches Python built without IPv6
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        except socket.error:
            # socket.error catches OS with IPv6 disabled
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind(('', port))
        self.sock.listen(5)
    def read_policy(self, path):
        f = open(path, 'rb')
        policy = f.read(10001)
        if len(policy) > 10000:
            raise exceptions.RuntimeError('File probably too large to be a policy file',
                                              path)
        if 'cross-domain-policy' not in policy:
            raise exceptions.RuntimeError('Not a valid policy file',
                                              path)
        f.close();
        return policy
    def run(self):
        try:
            while True:
                thread.start_new_thread(self.handle, self.sock.accept())
        except socket.error, e:
            self.log('Error accepting connection: %s' % (e[1],))
    def handle(self, conn, addr):
        addrstr = '%s:%s' % (addr[0],addr[1])
        try:
            self.log('Connection from %s' % (addrstr,))
            # with contextlib.closing(conn):
            try:
                # It's possible that we won't get the entire request in
                # a single recv, but very unlikely.
                request = conn.recv(1024).strip()
                if request != '<policy-file-request/>\0':
                    self.log('Unrecognized request from %s: %s' % (addrstr, request))
                    return
                self.log('Valid request received from %s' % (addrstr,))
                conn.sendall(self.policy)
                self.log('Sent policy file to %s' % (addrstr,))
            finally:
                conn.close();
        except socket.error, e:
            self.log('Error handling connection from %s: %s' % (addrstr, e[1]))
        except Exception, e:
            self.log('Error handling connection from %s: %s' % (addrstr, e[1]))
    def log(self, str):
        print >>sys.stderr, str

def main():
    parser = optparse.OptionParser(usage = '%prog [--port=PORT] --file=FILE',
                                   version='%prog ' + str(VERSION))
    parser.add_option('-p', '--port', dest='port', type=int, default=843,
                      help='listen on port PORT', metavar='PORT')
    parser.add_option('-f', '--file', dest='path',
                      help='server policy file FILE', metavar='FILE')
    opts, args = parser.parse_args()
    if args:
        parser.error('No arguments are needed. See help.')
    if not opts.path:
        parser.error('File must be specified. See help.')

    try:
        policy_server(opts.port, opts.path).run()
    except Exception, e:
        print >> sys.stderr, e
        sys.exit(1)
    except KeyboardInterrupt:
        pass

if __name__ == '__main__':
    main()