最近、Python の文字列の書式設定の脆弱性に注目しました。今日は、Python で導入された文字列の書式設定の新しい構文のセキュリティの脆弱性について説明します。
信頼できないユーザー入力に str.format を使用すると、セキュリティ上のリスクが生じます。私は実際にこの問題については長い間知っていましたが、今日までその深刻さに気づきませんでした。攻撃者はこれを使用して Jinja2 サンドボックスをバイパスできるため、深刻な情報漏洩の問題が発生します。それまでの間、この記事の最後に str.format の新しい安全なバージョンを提供します。
これは非常に深刻なセキュリティ リスクであることを思い出していただく必要があります。私がここで記事を書いている理由は、おそらくほとんどの人がそれが悪用されやすいことを知らないからです。
Python 2.6 以降、Python では、.NET からインスピレーションを得て文字列をフォーマットするための新しい構文が導入されました。もちろん、Python に加えて、Rust や他のいくつかのプログラミング言語もこの構文をサポートしています。 .format() メソッドを使用すると、この構文はバイト文字列と Unicode 文字列の両方 (Python 3 では Unicode 文字列のみ) に適用でき、よりカスタマイズ可能な文字列にもマッピングできます。
この構文の特徴は、文字列形式の位置パラメーターとキーワード パラメーターを決定し、いつでもデータ項目を明示的に並べ替えることができることです。さらに、オブジェクトのプロパティやデータ項目にアクセスすることもできます。これがセキュリティ問題の根本原因です。
全体として、これを悪用して次のことを行うことができます:
>>> 'class of {0} is {0.__class__}'.format(42) "class of 42 is "
基本的に、書式文字列を制御できる人は誰でも、オブジェクトのさまざまな内部プロパティにアクセスできる可能性があります。
最初の質問は、書式文字列を制御する方法です。次の場所から開始できます:
1. 文字列ファイルの信頼できないトランスレーター。複数の言語に翻訳された多くのアプリケーションがこの新しい Python 文字列フォーマット方法を使用しているため、これらは問題なく実行できる可能性が高いですが、誰もが入力されたすべての文字列を徹底的にレビューするわけではありません。
2. ユーザー公開設定。 一部のシステム ユーザーは特定の動作を構成できるため、これらの構成はフォーマット文字列の形式で公開される可能性があります。特別な注意として、一部のユーザーが Web アプリケーションを通じて通知電子メール、ログ メッセージ形式、またはその他の基本的なテンプレートを構成しているのを目にしました。
C インタプリタ オブジェクトを書式文字列に渡すだけの場合は、多くても整数などの一部のものを公開するだけなので、それほど危険はありません。
しかし、この書式文字列にPythonオブジェクトを渡すと面倒になります。これは、Python 関数から公開できるものの量が非常に膨大であるためです。 以下は、キーを漏洩する可能性がある仮想 Web アプリケーションのシナリオです:
CONFIG = { 'SECRET_KEY': 'super secret key' } class Event(object): def __init__(self, id, level, message): self.id = id self.level = level self.message = message def format_event(format_string, event): return format_string.format(event=event)
ユーザーがここに format_string を挿入できる場合、次のように秘密の文字列を見つけることができます:
{event.__init__.__globals__[CONFIG][SECRET_KEY]}
それで、何をすべきか他の人にフォーマット文字列を提供してもらう必要がある場合はそうしますか? 実際、文書化されていない内部メカニズムの一部を使用して、文字列の書式設定の動作を変更することができます。
from string import Formatter from collections import Mapping class MagicFormatMapping(Mapping): """This class implements a dummy wrapper to fix a bug in the Python standard library for string formatting. See http://bugs.python.org/issue13598 for information about why this is necessary. """ def __init__(self, args, kwargs): self._args = args self._kwargs = kwargs self._last_index = 0 def __getitem__(self, key): if key == '': idx = self._last_index self._last_index += 1 try: return self._args[idx] except LookupError: pass key = str(idx) return self._kwargs[key] def __iter__(self): return iter(self._kwargs) def __len__(self): return len(self._kwargs) # This is a necessary API but it's undocumented and moved around # between Python releases try: from _string import formatter_field_name_split except ImportError: formatter_field_name_split = lambda \ x: x._formatter_field_name_split() {C} class SafeFormatter(Formatter): def get_field(self, field_name, args, kwargs): first, rest = formatter_field_name_split(field_name) obj = self.get_value(first, args, kwargs) for is_attr, i in rest: if is_attr: obj = safe_getattr(obj, i) else: obj = obj[i] return obj, first def safe_getattr(obj, attr): # Expand the logic here. For instance on 2.x you will also need # to disallow func_globals, on 3.x you will also need to hide # things like cr_frame and others. So ideally have a list of # objects that are entirely unsafe to access. if attr[:1] == '_': raise AttributeError(attr) return getattr(obj, attr) def safe_format(_string, *args, **kwargs): formatter = SafeFormatter() kwargs = MagicFormatMapping(args, kwargs) return formatter.vformat(_string, args, kwargs)
ここで、safe_format メソッドを使用して str.format を置き換えることができます:
>>> '{0.__class__}'.format(42) "" >>> safe_format('{0.__class__}', 42) Traceback (most recent call last): File "", line 1, in AttributeError: __class__
プログラム開発には、「いかなるときもユーザー入力を信用してはいけない」という格言があります。今では、この文が完全に意味をなしているように思えます。ですので、学生の皆さんはこのことを心に留めておいてください!
以上がPython の新しい文字列形式の脆弱性の分析と解決策の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。