セキュリティの考慮点 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'
X-XSS-Protection¶
もしもリクエストがなにかJavaScriptのようなものを含み、レスポンスも同じデータを含んでいる場合に、ページを読み込まないようにすることで、ブラウザがreflected XSS攻撃を防ぐよう試みるようにします。 The browser will try to prevent reflected XSS attacks by not loading the page if the request contains something that looks like JavaScript and the response contains the same data. ::
response.headers['X-XSS-Protection'] = '1; mode=block'
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 minutes
response.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'])
def login():
...
session.clear()
session['user_id'] = user.id
session.permanent = True
...
ほかのクッキーの値(もしくは安全に署名する必要があるあらゆる値)の署名と検証にはitsdangerous.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.