セキュリティの考慮点 Security Considerations

webアプリケーションは普通さまざまな種類のセキュリティ上の問題に直面し、全てを正しくするのは非常に困難です。Flaskはこれらのいくつかを、あなたにとって良いように解決しようと試みますが、自分自身で注意を払わなければならないことが、まだまだあります。 Web applications usually face all kinds of security problems and it's very hard to get everything right. Flask tries to solve a few of these things for you, but there are a couple more you have to take care of yourself.

クロスサイトスクリプティング(Cross-Site Scripting)(XSS) Cross-Site Scripting (XSS)

クロスサイトスクリプティングは任意のHTML(さらにそれを使うJavaScript)をwebサイトの実行環境に挿入しようとする考え方です。対策するためには、任意のHTMLタグが含まれないようにするために、開発者はテキストを適切にエスケープする必要があります。これに関するさらなる情報については、Wikipediaの記事Cross-Site Scriptingを確認してください。 Cross site scripting is the concept of injecting arbitrary HTML (and with it JavaScript) into the context of a website. To remedy this, developers have to properly escape text so that it cannot include arbitrary HTML tags. For more information on that have a look at the Wikipedia article on `Cross-Site Scripting <https://en.wikipedia.org/wiki/Cross-site_scripting>`_.

Flaskでは、明示的にそうしないと指示されていないかぎり、Jinja2が自動的に全ての値をエスケープするように設定しています。これによってテンプレートが原因となるXSSの問題は全て排除されるはずですが、それでも他に注意しなければならない場所がいくらか残っています: Flask configures Jinja2 to automatically escape all values unless explicitly told otherwise. This should rule out all XSS problems caused in templates, but there are still other places where you have to be careful:

  • Jinja2を利用せずにHTMLを生成 generating HTML without the help of Jinja2

  • ユーザが提出したデータへMarkupを呼び出し calling :class:`~flask.Markup` on data submitted by users

  • ユーザがアップロードしたファイルからHTMLを送出、これは決して行うべきではなく、問題を防ぐためにContent-Disposition: attachmentを使うようにしてください。 sending out HTML from uploaded files, never do that, use the ``Content-Disposition: attachment`` header to prevent that problem.

  • アップロードされたファイルからテキストファイルを送出。いくつかのブラウザはcontent-typeの推測を、データの最初の数バイトに基づいて行うため、ブラウザをだましてHTMLを実行をさせることが、ブラウザのユーザにとって可能な場合があります。 sending out textfiles from uploaded files. Some browsers are using content-type guessing based on the first few bytes so users could trick a browser to execute HTML.

もうひとつの非常に重要なものは、引用符で囲まれない属性です。Jinja2はHTMLをエスケープすることでXSS問題から守ることはできても、それでもJinja2が防げないものがあります: 属性の挿入によるXSS(XSS by attribute injection)です。この方面から攻撃される可能性へ対抗するには、属性をJinjaの式で使用するときには、確実に二重引用符(")または一重引用符(')でそれらの属性を囲むようにします: Another thing that is very important are unquoted attributes. While Jinja2 can protect you from XSS issues by escaping HTML, there is one thing it cannot protect you from: XSS by attribute injection. To counter this possible attack vector, be sure to always quote your attributes with either double or single quotes when using Jinja expressions in them:

<input value="{{ value }}">

なぜこれが必要なのでしょうか?もしそれをしなかった場合、攻撃者が容易に独自のJavaScript処理を挿入できるようになるためです。例えば、攻撃者はここ(上記のinputタグ)へ以下のようなHTML+JavaScriptを挿入できます: Why is this necessary? Because if you would not be doing that, an attacker could easily inject custom JavaScript handlers. For example an attacker could inject this piece of HTML+JavaScript:

onmouseover=alert(document.cookie)

それからユーザが、上記のinputタグの場所へマウスを移動させた場合、クッキーが表示されているalertウィンドがユーザに表示されるでしょう。しかし、ユーザにクッキーを表示させる代わりに、できる攻撃者は、別のあらゆるJavaScriptコードも実行させるかもしれません。CSSの挿入(CSS injections)と組み合わせると、その要素(訳注:上記のinputタグ)がページ全体を埋め尽くすようにして、ユーザがその攻撃を引き起こすためにページ上のどこかにマウスを移動させることしかできないようにさえ、攻撃者がしてくるかもしれません。 When the user would then move with the mouse over the input, the cookie would be presented to the user in an alert window. But instead of showing the cookie to the user, a good attacker might also execute any other JavaScript code. In combination with CSS injections the attacker might even make the element fill out the entire page so that the user would just have to have the mouse anywhere on the page to trigger the attack.

XSS問題のひとつで、Jinjaのエスケープ機能では防げないものがあります。aタグのhref属性はjavascript:で始まるURIを含むことができ、適切に守られていないブラウザはそれをクリックするとそのJavaScriptを実行します。 There is one class of XSS issues that Jinja's escaping does not protect against. The ``a`` tag's ``href`` attribute can contain a `javascript:` URI, which the browser will execute when clicked if not secured properly.

<a href="{{ value }}">click here</a>
<a href="javascript:alert('unsafe');">click here</a>

これを防ぐためには、コンテンツのセキュリティポリシー(CSP)(Content Security Policy)レスポンスヘッダを確認する必要があるでしょう。 To prevent this, you'll need to set the :ref:`security-csp` response header.

クロスサイトリクエストフォージェリ(Cross-Site Request Forgery)(CSRF) Cross-Site Request Forgery (CSRF)

もう一つの大きな問題がCSRFです。これはとても複雑な話題であり、ここでは詳細を整理はせず、それが何であり理論上はどう防ぐのかに言及だけします。 Another big problem is CSRF. This is a very complex topic and I won't outline it here in detail just mention what it is and how to theoretically prevent it.

もし認証情報がクッキーに格納されていた場合、状態管理をすることが暗黙的に必要となります。「ログインしている」という状態はクッキーによって制御され、そのクッキーはリクエストごとにページへ送信されます。残念ながらそのようなリクエストは第三者のサイトが引き金になる場合も含みます。もしそのことを考えておかないと、あなたのアプリケーションのユーザをだまして、ソーシャルエンジニアリングも使いながら、当事者に知られることなく、愚かな点を突いてくるようなことを、誰かができるようになっているかもしれません。 If your authentication information is stored in cookies, you have implicit state management. The state of "being logged in" is controlled by a cookie, and that cookie is sent with each request to a page. Unfortunately that includes requests triggered by 3rd party sites. If you don't keep that in mind, some people might be able to trick your application's users with social engineering to do stupid things without them knowing.

例えば、POSTリクエストを送ったときはユーザのプロフィールを消去する特別なURLがあるとします(http://example.com/user/deleteとします)。もしも攻撃者がそのとき、そのページへpostリクエストを送信するページをなにかしらのJavaScriptを使いながら作成した場合、あるユーザがその(攻撃者が作成した)ページをロードするようにだましさえすれば、そのユーザのプロフィールは消去される結果になります。 Say you have a specific URL that, when you sent ``POST`` requests to will delete a user's profile (say ``http://example.com/user/delete``). If an attacker now creates a page that sends a post request to that page with some JavaScript they just have to trick some users to load that page and their profiles will end up being deleted.

自分が何百万ものユーザがいるFacebookを走らせていて、誰かが子猫の画像へのリンクを送ることを想像してみてください。ユーザがそのページへ行ったとき、もふもふした猫の画像を見ている間に、自分のプロフィールが削除されるかもしれません。 Imagine you were to run Facebook with millions of concurrent users and someone would send out links to images of little kittens. When users would go to that page, their profiles would get deleted while they are looking at images of fluffy cats.

これはどのように防ぐのでしょうか?基本的には、サーバ上の内容を変更するリクエストではそれぞれ、一度限りのトークン(訳注:認証などに使用する、大抵はでたらめな文字列)を使用して、それをクッキーに格納して、さらにそれをformデータとも一緒に送信します(訳注: サーバがformを含んだページのレスポンスをWebブラウザなどへ送信するとき、そのレスポンスのヘッダの中で、クッキーにそのレスポンス固有のトークンを設定するような処理を指していると思います)。サーバで再びデータを受信した後は、そのときに2つのトークンを比較して、確実に同一であるかを確かめる必要があるでしょう。 How can you prevent that? Basically for each request that modifies content on the server you would have to either use a one-time token and store that in the cookie **and** also transmit it with the form data. After receiving the data on the server again, you would then have to compare the two tokens and ensure they are equal.

なぜFlaskは、あなにとって良いように、それをしてくれないのでしょうか?それを起こす理想的な場所はformを検証するフレームワークであって、それはFlaskの外側に位置するのです。 Why does Flask not do that for you? The ideal place for this to happen is the form validation framework, which does not exist in Flask.

JSONのセキュリティ JSON Security

Flask 0.10とそれ以前では、jsonify()は最上位レベルの配列(top-level arrays)をJSONへシリアライズ(訳注:ネットワークで送受信できるデータ形式へ変換するような処理)しませんでした。これはECMAScript 4にあるセキュリティ上の脆弱性によるものでした。 In Flask 0.10 and lower, :func:`~flask.jsonify` did not serialize top-level arrays to JSON. This was because of a security vulnerability in ECMAScript 4.

ECMAScript 5ではこの脆弱性は解決しており、従っていまだに脆弱性があるのは非常に古いブラウザだけです。そのようなブラウザはすべて別のより深刻な脆弱性があり、従ってこの振る舞いは変更されていて、jsonify()は今では配列のシリアライズをサポートしています。 ECMAScript 5 closed this vulnerability, so only extremely old browsers are still vulnerable. All of these browsers have `other more serious vulnerabilities <https://github.com/pallets/flask/issues/248#issuecomment-59934857>`_, so this behavior was changed and :func:`~flask.jsonify` now supports serializing arrays.

セキュリティに関するヘッダ Security Headers

ブラウザは、セキュリティを制御するために様々なレスポンスのヘッダを認識します。自分のアプリケーションで使うように、以下のヘッダをそれぞれ確認しておくことをお勧めします。Flask-TalismanというFlask拡張は、HTTPSとセキュリティに関するヘッダを管理するために使用できます。 Browsers recognize various response headers in order to control security. We recommend reviewing each of the headers below for use in your application. The `Flask-Talisman`_ extension can be used to manage HTTPS and the security headers for you.

HTTPの厳格なトランスポートのセキュリティ(HSTS)(HTTP Strict Transport Security) HTTP Strict Transport Security (HSTS)

全てのHTTPリクエストをHTTPSへ変換するようブラウザに指示して、man-in-the-middle(MITM)攻撃を防ぎます。: Tells the browser to convert all HTTP requests to HTTPS, preventing man-in-the-middle (MITM) attacks. ::

response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'

コンテンツのセキュリティポリシー(CSP)(Content Security Policy) Content Security Policy (CSP)

様々な種類のリソースについて、どこから読み込めるかをブラウザに指示します。このヘッダは可能な限り使用するべきですが、自分のサイト用に適切なポリシーを定義するためにいくらかの作業が必要になります。非常に厳格なポリシーは以下のようになるでしょう: Tell the browser where it can load various types of resource from. This header should be used whenever possible, but requires some work to define the correct policy for your site. A very strict policy would be::

response.headers['Content-Security-Policy'] = "default-src 'self'"

X-Content-Type-Options

レスポンスが示すcontent type(訳注:データの種類・形式を示すヘッダ)を尊重するようブラウザに強要し、クロスサイトスクリプティング(XSS)攻撃の生成に悪用される可能性がある、データ形式の推測機能を優先させないようにします。: Forces the browser to honor the response content type instead of trying to detect it, which can be abused to generate a cross-site scripting (XSS) attack. ::

response.headers['X-Content-Type-Options'] = 'nosniff'

X-Frame-Options

外部サイトがiframeの中に、あなたのサイトを埋め込むことを防ぎます。これは、外側のフレームでのクリックがあなたのページの要素をクリックすることへと、目に見えないやり方で変換できるようにする攻撃の類を防ぎます。これは「clickjacking」としても知られています。: Prevents external sites from embedding your site in an ``iframe``. This prevents a class of attacks where clicks in the outer frame can be translated invisibly to clicks on your page's elements. This is also known as "clickjacking". ::

response.headers['X-Frame-Options'] = 'SAMEORIGIN'

HTTP公開鍵ピン止め(HPKP)(HTTP Public Key Pinning) HTTP Public Key Pinning (HPKP)

これは、MITM攻撃を防ぐために、特定の証明書の鍵だけを使用してサーバを認証するよう、ブラウザに指示します。 This tells the browser to authenticate with the server using only the specific certificate key to prevent MITM attacks.

警告

もしも鍵を不適当に設定もしくは更新してしまった場合は、元に戻すことが非常に難しいため、これを有効にするときは注意してください。 Be careful when enabling this, as it is very difficult to undo if you set up or upgrade your key incorrectly.

端末へのコピー/貼り付け(Copy/Paste to Terminal) Copy/Paste to Terminal

バックスペース文字(\b^H)のように見えない文字は、端末に貼り付けた場合の解釈のされ方とは違うようにHTMLでは変換されるテキストの原因になります。 Hidden characters such as the backspace character (``\b``, ``^H``) can cause text to render differently in HTML than how it is interpreted if `pasted into a terminal <https://security.stackexchange.com/q/39118>`__.

例えば、import y\bose\bm\bi\bt\be\bはHTMLではimport yosemiteと変換表示されますが、端末に貼り付けられたときはバックスペースが適用され、import osになります。 For example, ``import y\bose\bm\bi\bt\be\b`` renders as ``import yosemite`` in HTML, but the backspaces are applied when pasted into a terminal, and it becomes ``import os``.

もしユーザが、例えば技術ブログでユーザが投稿したコメントのように、あなたのサイトから信頼できないコードをコピーして貼り付けることが予想される場合、全ての\b文字を置き換えるような、追加のフィルタリングを適用することを検討してください。 If you expect users to copy and paste untrusted code from your site, such as from comments posted by users on a technical blog, consider applying extra filtering, such as replacing all ``\b`` characters.

body = body.replace("\b", "")

殆どの最近の端末は、貼り付けるときに見えない文字列を警告して削除するであろうために、これは厳密には必須ではありません。フィルタリングできない他のやり方で危険なコマンドを欺くことも可能です。自分のサイトのユースケースによりますが、一般的にコードのコピーに対して警告を表示するのは良いことでしょう。 Most modern terminals will warn about and remove hidden characters when pasting, so this isn't strictly necessary. It's also possible to craft dangerous commands in other ways that aren't possible to filter. Depending on your site's use case, it may be good to show a warning about copying code in general.