SSRF(Server-Side Request Forgery)は、攻撃者がサーバに意図しないリクエストを送らせる攻撃手法のことである。これにより、サーバ自身が本来アクセスしてはいけないローカルファイルへアクセスしてしまうことがある。

指定されたURLから画像を取得して表示するような、単純なpythonスクリプトを考える(スクリプトは後述)。アプリケーションのfetchパラメータに、画像のURLを指定すると、そのURLで公開されている画像が表示される。

$ python3 app.py
→ アプリケーション(Webサーバと想定)を起動し、次のURLにアクセスする。
http://localhost:5000/?fetch=https://arowanakaitai.com/img/arowana.png

このfetchパラメータに、Webサーバ自身のローカルアドレスを指定すると、Webサーバ内部のファイルが確認できる。

$ echo "hello" > hello.txt
$ python3 -m http.server 8080 --bind 127.0.0.1
$ python3 app.py
→ アプリケーション(Webサーバと想定)を起動し、次のURLにアクセスする。
http://localhost:5000/?fetch=http://127.0.0.1:8080/hello.txt

この例のような単純なケースはほとんどないと考えられるが、SSRFによって、攻撃者が通常アクセスできない攻撃対象のサーバ内のファイルにアクセスできることがある。また、この例のように外部のURLを指定する場合、大容量ファイルを読み込ませるなどしてシステムをダウンさせるといったことも考えられる。なお、この度の検証用のpythonスクリプトは次のとおりである。

from flask import Flask, request
import requests
import base64

app = Flask(__name__)

@app.route("/")
def index():
    url = request.args.get("fetch")

    if not url:
        return """
        <html>
        <body>
            <h2>SSRFのテスト</h2>
            <div style="width:300px;height:300px;border:1px solid #000;
                        display:flex;align-items:center;justify-content:center;">
                <div style="text-align:center;">
                    fetchで画像を指定してください。 <br>
                </div>
            </div>
        </body>
        </html>
        """

    try:
        r = requests.get(url)

        if r.headers.get('Content-Type', '').startswith("text"):
            return f"""
            <html>
            <body>
                <h2>SSRFのテスト</h2>
                <div style="width:300px;height:300px;border:1px solid #000;
                            overflow:auto;white-space:pre-wrap;">
                    {r.text}
                </div>
            </body>
            </html>
            """

        encoded = base64.b64encode(r.content).decode()

        return f"""
        <html>
        <body>
            <h2>SSRFのテスト</h2>
            <div style="width:300px;height:300px;border:1px solid #000;
                        display:flex;align-items:center;justify-content:center;">
                <img src="data:{r.headers.get('Content-Type')};base64,{encoded}"
                     style="max-width:100%;max-height:100%;">
            </div>
        </body>
        </html>
        """
    except:
        return "Error"

app.run(host="0.0.0.0", port=5000)