PythonでJSON ~evalでやってみよう

WebAPIで使われるデータ形式JSON。普通はJSON用のパーサーを使うのですけれども、Python組み込み関数のevalで評価できないか考えてみました。

”結構めんどくさい”ので、専用のモジュールを探して使ったほうが悩まなくてすむかも。
evalを安全に使うのくだりは自分用にメモを保存しておきたかったので、一応公開しておきます。

Pythonにて、eval関数を使ってJSONをオブジェクトに変換した場合、容易に思いつく脆弱性は

_jsonstr="__import__(’sys’).stdout.write(’HELLO WORLD’)"
_ #print "HELLO WORLD"と同じと思って頂きたい
_jsondict=eval(jsonstr)

コンソールに文字が表示されます。
これを抑制するために、スコープをからっぽにしてみます。

_>>> eval("__import__(’sys’).stdout.write(dir())")
_['__builtins__', '__doc__', '__file__', '__name__', 'idlelib', 'jsondict', 'jsonstr']
_>>> eval("__import__(’sys’).stdout.write(dir())",{},{})
_[] #

からっぽです。。。。って、おかしいですね?

_>>> eval("__import__(’sys’).stdout.write(’HELLO WORLD’)",{},{})
_HELLO WORLD

二つのからっぽの辞書はglobalsとlocals。これで関数は全て消えたはず。。。
しかし、これでは不十分で文字が表示されてしまいます。なぜ?

リファレンスマニュアル 2.1 組み込み関数によれば
"locals 辞書が存在するが、’__builtins__’ が欠けている場合、 expression を解析する前に現在のグローバル変数を globals にコピーします。このことから、expression は通常標準の __builtin__ モジュールへの完全なアクセスを有し、制限された環境が伝播するようになっています。

_>>> eval("__import__(’sys’).stdout.write(’HELLO WORLD’)",\
_ {’__builtins__’:None},{})
_Traceback (most recent call last): (以下略)

これでeval中の関数は実行されなくなりました。

事前にnull true falseを置換しない場合は、こんな感じに渡してやる

_d=eval(jsontxt,dict(__builtins__=None,\
_null=None,\true=True,false=False,\
_None=None,True=True,False=False),{})

Yahoo!などのメジャーどころのWebAPIへのアクセスならこれで十分かなぁという気もするのですけれども~。。。まだ穴が有るかしらん?
妥当性チェックなども、そんなに要らないような気もしますし。

バックスラッシュ(¥、\)によるエスケープシークエンスは、良い関数が見つからなかったので、文字列 replaceメソッドで、eval前に置換、かな?

このあたりからめんどくさくなってきました。

4.9.2 標準エンコーディング

¥¥uXXXXって表現をユニコードに戻すには

  • unicode_escape
  • raw_unicode_escape
  • string_escape
  • unicode_internal

あたりを使うのかと。でも、JSONに直接
_eval("JSON文字列".decode("unicode_escape"))

したら、パースエラーが。原因がよくわからないので、eval後、文字列を取り出した後でdecode("unicode_escape") してみた。

JSON文字列を作成

辞書をstrで文字列にすると、ダブルクォートでなくシングルクォートでくくられるます。

上記のエスケープシーケンスなども考えると、素直に専用のモジュールを使ったほうが悩まなくてすむかも、という真っ当な結論に。


時間があったら、実際のWebAPIで試してみますー


関連リンク

サンプルデータ

サンプル用のデータに使ってみようかと。

Tags:

Related posts

Tags:

Comments are closed.