セキュリティの考慮点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:

<inputvalue="{{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+#"#security-csp">コンテンツのセキュリティポリシー(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'

Set-CookieオプションSet-Cookie options

以下のオプションはセキュリティを改善するために、Set-cookieヘッダへ追加することができます。Flaskには、これらをセッションのクッキー(訳注: Flaskがsessionオブジェクトでアクセス可能にしているデータを格納しており、FlaskのSESSION_COOKIE_NAME設定で指定されているcookie)へ設定させる構成オプションがあります。これらは、他のクッキーへも設定可能です。These options can be added to a ``Set-Cookie`` header to improve their security. Flask has configuration options to set these on the session cookie. They can be set on other cookies too.

  • secureはクッキー(の送受信を)をHTTPS通信だけに限定します。``Secure`` limits cookies to HTTPS traffic only.

  • HttpOnlyは、クッキーの内容をJavaScriptで読み取られることを防ぎます。``HttpOnly`` protects the contents of cookies from being read with JavaScript.

  • SameSiteは、外部サイトからのリクエストからどのようにクッキーが送信されるかについて制限します。'Lax'(推奨)または'Strict'に設定可能です。Laxは、formを提出してくるような、外部サイトからのCSRFがありがちなリクエストで一緒にクッキーを(ブラウザが)送信することを防ぎます。Strictは、通常のリンクをフォローすることを含む、全ての外部リクエストで一緒にクッキーを(ブラウザが)送信することを防ぎます。``SameSite`` restricts how cookies are sent with requests from external sites. Can be set to ``'Lax'`` (recommended) or ``'Strict'``. ``Lax`` prevents sending cookies with CSRF-prone requests from external sites, such as submitting a form. ``Strict`` prevents sending cookies with all external requests, including following regular links.

app.config.update(SESSION_COOKIE_SECURE=True,SESSION_COOKIE_HTTPONLY=True,SESSION_COOKIE_SAMESITE='Lax',)response.set_cookie('username','flask',secure=True,httponly=True,samesite='Lax')

ExpiresまたはMax-Ageオプションを指定すると、Expiresでは与えられた時刻、Max-Ageでは現在時刻にageを加えた時刻、を過ぎた後にそれぞれクッキーを削除します。もしどちらのオプションも設定されていない場合は、ブラウザが閉じられたときにクッキーは削除されまれます。:Specifying ``Expires`` or ``Max-Age`` options, will remove the cookie after the given time, or the current time plus the age, respectively. If neither option is set, the cookie will be removed when the browser is closed. ::

# cookie expires after 10 minutesresponse.set_cookie('snakes','3',max_age=600)

セッションのクッキーについては、もしsession.permanentが設定されていた場合には、PERMANENT_SESSION_LIFETIMEが消去(のタイミング)の設定に使用されます。Flaskの標準設定でのクッキーの実装は、暗号学的に施された署名が、この値より古くなっていないことを検証します。この値を小さくするほど、横取りされたクッキーを後で送信できるようにする、replay攻撃の抑制への手助けになることでしょう。:For the session cookie, if :attr:`session.permanent <flask.session.permanent>` is set, then :data:`PERMANENT_SESSION_LIFETIME` is used to set the expiration. Flask's default cookie implementation validates that the cryptographic signature is not older than this value. Lowering this value may help mitigate replay attacks, where intercepted cookies can be sent at a later time. ::

app.config.update(PERMANENT_SESSION_LIFETIME=600)@app.route('/login',methods=['POST'])deflogin():...session.clear()session['user_id']=user.idsession.permanent=True...

ほかのクッキーの値(もしくは安全に署名する必要があるあらゆる値)の署名と検証にはitsdangerous.timed.TimedSerializerを使用してください。Use :class:`itsdangerous.TimedSerializer` to sign and validate other cookie values (or any values that need secure signatures).

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>`__.

例えば、importy\bose\bm\bi\bt\be\bはHTMLではimportyosemiteと変換表示されますが、端末に貼り付けられたときはバックスペースが適用され、importosになります。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.