SPAセキュリティ超入門

680.4K Views

September 25, 22

スライド概要

SPA(Single Page Application)の普及が一層進んでおり、従来型のMPAを知らないウェブ開発者も生まれつつあるようです。SPA対応のフレームワークでは基本的な脆弱性については対策機能が用意されていますが、それにも関わらず、脆弱性診断等で基本的な脆弱性が指摘されるケースはむしろ増えつつあります。
本セッションでは、LaravelとReactで開発したアプリケーションをモデルとして、SQLインジェクション、クロスサイトスクリプティング、認可制御不備等の脆弱性の実例を紹介しながら、現実的な対策について紹介します。LaravelやReact以外のフレームワーク利用者にも役立つ説明を心がけます。
PHPカンファレンス2022での講演資料です。

PHPカンファレンスでの動画URL
https://www.youtube.com/watch?v=jZ6sWyGxcCs

profile-image

徳丸本の中の人 OWASP Japanアドバイザリーボード EGセキュアソリューションズ取締役CTO IPA非常勤職員 YouTubeチャンネル: 徳丸浩のウェブセキュリティ講座 https://j.mp/web-sec-study

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

SPAセキュリティ超入門 EGセキュアソリューションズ株式会社 徳丸 浩 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru

2.

徳丸浩の自己紹介 • 経歴 – 1985年 京セラ株式会社入社 – 1995年 京セラコミュニケーションシステム株式会社(KCCS)に出向・転籍 – 2008年 KCCS退職、HASHコンサルティング株式会社(現社名:EGセキュアソリューションズ株式会社)設立 • 経験したこと – 京セラ入社当時はCAD、計算幾何学、数値シミュレーションなどを担当 – その後、企業向けパッケージソフトの企画・開発・事業化を担当 – 1999年から、携帯電話向けインフラ、プラットフォームの企画・開発を担当 Webアプリケーションのセキュリティ問題に直面、研究、社内展開、寄稿などを開始 – 2004年にKCCS社内ベンチャーとしてWebアプリケーションセキュリティ事業を立ち上げ • 現在 – – – – EGセキュアソリューションズ株式会社取締役CTO https://www.eg-secure.co.jp/ 独立行政法人情報処理推進機構 非常勤研究員 https://www.ipa.go.jp/security/ 著書「体系的に学ぶ 安全なWebアプリケーションの作り方(第2版)」(2018年6月) YouTubeチャンネル「徳丸浩のウェブセキュリティ講座」 https://j.mp/web-sec-study – 技術士(情報工学部門) 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 2

3.

本日お話したいこと • SPA(Single Page Application)とは • SPAと攻撃経路 • JavaScriptによる検証はバイパスできる • 認可制御不備 • SQLインジェクション • クロスサイトスクリプティング(XSS) • クロスサイトリクエストフォージェリ(CSRF) • まとめ ※ コード例には、ReactとLaravelを用います 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 3

4.

SPA(Single Page Application)とは? 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 4

5.

SPA以前のウェブ=MPA(Multi-Page Application) JavaScriptで代入した値は次のページではリセットされる → セッション管理の機能によりデータを引き継ぐ 処理 徳丸浩のウェブセキュリティ講座 こんにちは 次へどうぞ NEXT NEXT 処理 © 2022 Hiroshi Tokumaru ありがとうござい ました NEXT 処理 5

6.

SPA(Single Page Application)の構造 ページ遷移をしないのでJavaScriptの 変数は保持される。 ただし、ページ遷移、戻る、リロー ドで変数の値はリセットされる SPA → セッション管理あるいは localStorageによりデータを引き継ぐ HTML JS XHR/ Fetch コンテンツ 配信 Webサーバー 徳丸浩のウェブセキュリティ講座 JSON 処理 APIサーバー © 2022 Hiroshi Tokumaru 6

7.

SPAのHTML(いわゆるガワ)はとてもシンプル <!DOCTYPE html> <html lang="en"> <head> クライアントアプリ <link rel="manifest" href="/manifest.json" /> の実体はJavaScript <title>React App</title> <script defer="defer" src="/static/js/main.js"></script> <link href="/static/css/main.css" rel="stylesheet" /> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body> HTMLの実体は div要素一つだけ </html> 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 7

8.

つまり、SPAのクライアント側はほぼ JavaScriptと言っても過言ではない (CSSや画像はあります) 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 8

9.

ブラウザに表示されるHTMLはどこから? 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 9

10.

DOMでHTMLを作ります 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 10

11.

DOMとは? <html> <head> <title>HTMLサンプル</title> Document html テキストノード head </head> <body> <h1>今日(9月25日)は何の日?</h1> 属性ノード title HTMLサンプル <p id="p0925"> 10円カレーの日<br /> 藤ノ木古墳記念日<br /> 山田邦子の日 body h1 今日(8月11日)は何の日? </p> </body> </html> p id 10円カレーの日 br 今日(9月25日)は何の日? 藤ノ木古墳記念日 10円カレーの日 藤ノ木古墳記念日 山田邦子の日 徳丸浩のウェブセキュリティ講座 要素ノード br 山田邦子の日 © 2022 Hiroshi Tokumaru 11

12.

JavaScriptによるDOM操作の基本(Vanilla JS) <html> <body> <div id="div1"></div> </body> <script> const div1 = document.getElementById('div1') // p要素生成 const p = document.createElement('p') // テキストノード生成 const txt = document.createTextNode('バナナ') // テキストノードをp要素にぶら下げる p.appendChild(txt) // p要素をdiv要素にぶら下げる div1.appendChild(p) </script> </html> 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 12

13.

Reactの表示はJSXが一般的 const SomeApp = () => { return ( <div>{ JavaScriptの式 }</div> JSの中でHTMLタグが使える ) } • JSXはトランスパイラ(Babel)により通常のJavaScriptに変換され る • JSX中では { } の中にJavaScriptの式(文字列)を書くと、式の値 が展開されて表示される。HTMLエスケープは必要ない 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 13

14.

SPAと攻撃経路 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 14

15.

SPAの構造 TOKEN or SESSION ID Front End Store { … } JSON API { … } JSON ビジネスロジック 表示ロジック DB localStorage 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru Session Storage 15

16.

SPAのセッション管理方式 1 セッションID方式 POST /login {"id":"bob","pass":"123456"} SESSID=f8d92b0… API Front End SESSID=f8d92b0… Store ビジネスロジック {"email":"bob@example.jp"} 表示ロジック DB SESSID=f8d92b0… 5:bob,bob@example.jp 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru Session Storage f8d92b0…:5,bob 16

17.

SPAのセッション管理方式 2: ステートフルトークン POST /login {"id":"bob","pass":"123456"} token=a94c5d… API Front End token=a94c5d… Store ビジネスロジック {"email":"bob@example.jp"} 表示ロジック DB localStorage a94c5d…:id=5 5:bob,bob@example.jp token=a94c5d… 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 17

18.

SPAのセッション管理方式 3 ステートレストークン(JWT等) POST /login {"id":"bob","pass":"123456"} token=eyJhbG… API Front End Store token=eyJhbG… ビジネスロジック {"email":"bob@example.jp"} 表示ロジック デコード&検証 {"sub":"5","exp":17…} localStorage 5:bob,bob@example.jp DB token=eyJhbG… 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 18

19.

Laravel APIのセッション管理 方式名 セッションID ステートフルトークン ステートレストークン(JWT) ブラウザ側保管場所 Cookie localStorage localStorage データの保管場所 ファイルやRedis DB トークンに内包 保管できる情報 認証情報 + なんでも 認証情報 + 権限 認証情報 +α スケーラビリティ 小~中 中 大 即時ログアウト 容易 容易 困難 一斉ログアウト 工夫を要する 容易 困難 Laravelでの実装 Sanctum (SPA認証) Sanctum(APIトークン) tymon/jwt-auth CSRF脆弱性の可能性 あり=対策要 なし なし 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 19

20.

クライアント側のロジックやデータは取得・変更可能 TOKEN or SESSION ID Front End Store 表示ロジック { … } ビジネスロジック JSON DB localStorage 徳丸浩のウェブセキュリティ講座 API 取得・変更 © 2022 Hiroshi Tokumaru Session Storage 20

21.

JavaScriptによる検証はバイパスできる 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 21

22.

悪用例1: クライアント側バリデーションの回避 START HTMLやJavaScritに よるチェック <input type="number" min="0" max="100"> ※ 0~100の数値のみ許容 HTMLやJavaScriptによる チェック(バリデーション 等)は攻撃者は回避可能 No OK? Yes 処理実行(API) エラー END 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 22

23.

悪用例2: クライアントによるチェック処理の回避 START APIによる権限 チェック No OK? Yes 処理実行(API) エラー APIによる処理でも、チェッ クと実行(更新等)が別だ と、チェック処理は回避可 能 END 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 23

24.

パスワード変更機能の場合 example.jp パスワード変更 旧パスワード 新パスワード 新パスワード(確認) ***** ***** ***** 変更 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 現在パスワード の確認 確認用の再入力 24

25.

パスワード変更機能の脆弱な実装 利用者 www.example.com POST /auth HTTP/1.1 {"currentPass":"P@assword"} example.jp // 再認証に成功したら // パスワードを変更する if (authCheck()) { changePassword() } else { error("Auth Error") } HTTP/1.1 200 OK POST /changepassword HTTP/1.1 {"newPass":"123456"} HTTP/1.1 200 OK 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru パスワード変更 25

26.
[beta]
再認証をバイパスしてパスワード変更が可能
攻撃者
www.example.com

POST /auth HTTP/1.1
{"currentPass":"P@assword"}

HTTP/1.1 200 OK

$ curl -X POST -H "Content-Type:
application/json" -d '{"newPass":"123456"}
https://www.example.com/changepassword

POST /changepassword HTTP/1.1
{"newPass":"123456"}
HTTP/1.1 200 OK

徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

26

27.

認可制御不備 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 27

28.

認可制御不備の典型的パターン(1) 利用者 http://example.jp/login http://example.jp/logindone ログインしました ID パスワード ログイン 秘密情報をどうぞ 情報リソースのURLを知っていると 認証なしで情報が閲覧できる 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 攻撃者 http://example.jp/secret 秘密情報 ・・・・・・・・・・・・・・・ ・・・・・・・・・・・・・・・ ・・・・・・・・・・・・・・・ ・・・・・・・・・・・・・・・ URL を知っていると 未ログインでも閲覧可能 28

29.
[beta]
APIの認可制御不備(正常系)
利用者

example.jp

POST /login HTTP/1.1
example.jp

<script>
xhr = new XMLHttpRequest()
xhr.open('POST', '/user')
xhr.send(…)
</script>

HTTP/1.1 200 OK
Set-Cookie: SESSID=xxxx
GET /secret HTTP/1.1
Cookie: SESSID=xxxx

HTTP/1.1 200 OK
{"text":"Secret Information…"}
徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

29

30.
[beta]
APIの認可制御不備(攻撃)
攻撃者

example.jp

POST /login HTTP/1.1
HTTP/1.1 200 OK
Set-Cookie: SESSID=xxxx

$ curl https://example.jp/secret
{"text":"Secret Information…"}

GET /secret HTTP/1.1
<Cookie なし>

HTTP/1.1 200 OK
{"text":"Secret Information…"}
徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

認証チェック
なしに秘密情
報を返す
30

31.
[beta]
Laravel/Sanctum の認証設定
// routes/api.php

脆弱なルーティング設定

Route::get('secret', [SecretController::class, 'secret']);

// Laravel/Sanctumで認証を要求するルーティング設定例(routes/api.php)

Route::middleware('auth:sanctum')->group(function(){
Route::get('secret', [SecretController::class, 'secret']);
});

徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

31

32.

認可制御不備の典型的パターン(2) ID:yamadaでログイン ユーザ情報 http://example.jp/profile.php?id=100246 ようこそ yamadaさん ユーザID:yamada メールアドレス:xx 氏名: 山田〇〇 電話: 03-xxxx-xxxx 情報リソースのIDを変更する と権限外の情報が参照できる http://example.jp/profile.php?id=100247 ようこそ yamadaさん ユーザID:sato メールアドレス:xx 氏名: 佐藤△△ 電話: 06-xxxx-xxxx 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 内部ID 外部ID 100246 yamada 100247 sato ※アプリケーション内部で は表示用の外部IDではな く、内部ID(一連番号)で 管理されている 32

33.

APIの認可制御不備(正常系) 利用者 example.jp GET /tasks HTTP/1.1 Cookie: SESSID=xxxx example.jp タスク一覧 • 3 • 5 • 7 • 10 {"tasks":[3,5,7,10]} GET /tasks/10 HTTP/1.1 Cookie: SESSID=xxxx HTTP/1.1 200 OK {"id":10,"user_id":5,"subject":"Secret Task"} 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 33

34.

APIの認可制御不備(攻撃) 攻撃者 example.jp GET /tasks HTTP/1.1 Cookie: SESSID=zzzz example.jp タスク一覧 • 2 • 4 • 6 {"tasks":[2,4,6]} GET /tasks/10 HTTP/1.1 Cookie: SESSID=zzzz HTTP/1.1 200 OK 権限がない情 報が盗まれる {"id":10,"user_id":5,"subject":"Secret Task"} 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 34

35.
[beta]
ポリシーによる認可制御の実装例(Laravel)
// app/Policies/TaskPolicy.php
class TaskPolicy
{
use HandlesAuthorization;
public function view(User $user, Task $task)

// showコントローラに対応するポリシー

{
return $user->id === $task->user_id;

// タスクの持ち主のみがアクセスできる

}

}
// コントローラの定義
class TaskController extends Controller
{
public function show(Task $task)
{
$this->authorize($task); // ポリシーの確認
return $task;
}

}
徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

35

36.

認可制御不備の典型的パターン(3) 正常系 一般向けメニュー http://example.jp/a001 管理者としてログイン http://example.jp/menu 投稿 閲覧 プロフィール 攻撃 一般ユーザとしてログイン http://example.jp/menu 一般機能 一般機能 管理者機能 管理者用メニュー http://example.jp/b001 ユーザ登録 アカウント停止 ロック解除 問い合わせ対応 URLを直接指定すると 管理者用メニューが 表示され実行もできる 一般向けメニュー http://example.jp/a001 投稿 閲覧 プロフィール 管理者用メニュー http://example.jp/b001 ユーザ登録 アカウント停止 ロック解除 問い合わせ対応 URL 書き換え メニューの表示・非表示のみで制御している 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 36

37.
[beta]
APIの認可制御不備(正常系)
管理者

example.jp

POST /login HTTP/1.1
example.jp

<script>
xhr = new XMLHttpRequest()
xhr.open('POST', '/user')
xhr.send(…)
</script>

HTTP/1.1 200 OK
Set-Cookie: SESSID=xxxx
{"id":1}
PATCH /users/10 HTTP/1.1
Cookie: SESSID=xxxx
{"email":"carol@example.jp"}
HTTP/1.1 200 OK

徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

管理者がメールア
ドレスを変更する
37

38.
[beta]
APIの認可制御不備(攻撃)
攻撃者(一般ユーザー)

example.jp

POST /login HTTP/1.1
example.jp

<script>
xhr = new XMLHttpRequest()
xhr.open('POST', '/user')
xhr.send(…)
</script>

HTTP/1.1 200 OK
Set-Cookie: SESSID=zzzz
{"id":6}
PATCH /users/10 HTTP/1.1
Cookie: SESSID=zzzz

※一般ユーザーからは呼び出されないエンドポイント

{"email":"carol@example.jp"}

HTTP/1.1 200 OK
徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

権限のないメール
アドレス変更
38

39.

認可制御不備の典型的パターン(4) 正常系 一般ユーザとしてログイン Cookie: USER=normal 管理者としてログイン Cookie: USER=admin 攻撃 投稿 閲覧 一般ユーザとしてログイン Cookie: USER=admin 投稿 閲覧 ユーザ登録 アカウント停止 Cookieに保持した権限情報を変 更しただけで管理者メニューが 表示され、実行もできる 投稿 閲覧 ユーザ登録 アカウント停止 フレームワークのStoreやlocalStorage、クッキーに権限情報を保持している 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 39

40.
[beta]
APIの認可制御不備(正常系)
管理者

example.jp

POST /login HTTP/1.1

localStorage

HTTP/1.1 200 OK
Set-Cookie: SESSID=xxxx
{"id":1, "role":1}

role:1

PATCH /users/10 HTTP/1.1
Cookie: SESSID=xxxx
{"role":1, "email":"carol@example.jp"}
HTTP/1.1 200 OK

徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

管理者がメールア
ドレスを変更する
40

41.
[beta]
APIの認可制御不備(攻撃)
攻撃者(一般ユーザー)

example.jp

POST /login HTTP/1.1

localStorage

HTTP/1.1 200 OK
Set-Cookie: SESSID=zzzz
{"id":6, "role":0}

role:0
role:1

PATCH /users/10 HTTP/1.1
Cookie: SESSID=zzzz
{"role":1, "email":"carol@example.jp"}
HTTP/1.1 200 OK

徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

権限のないメール
アドレス変更
41

42.
[beta]
Gateファサードによる認可実装例(Laravel)
// app/Providers/AuthServiceProvider.php
class AuthServiceProvider extends ServiceProvider
{
/* */
public function boot()
{
$this->registerPolicies();
Gate::define('admin', function ($user) { // adminポリシーの定義
return $user->role >= 1; // ユーザのroleが1以上をadminとする
});

}
}

// routes/api.php

ルーティング設定

// ...
Route::group(['middleware' => ['auth', 'can:admin']], function () {

// adminのみupdateEmail を許可する

Route::patch('user/update_email/{user}', [UserController::class, 'updateEmail']);
});

徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

42

43.

認可制御の正しい実装 • 認可制御の基本は、APIの中で権限を確認すること – 秘密情報の表示 – 権限の必要な機能実行 – その他、権限を必要とする操作 • 権限を確認する基準は、セッション変数やトークンに紐づいた ログインID • admin等の特権専用ユーザーは作らず、個人IDに権限を割り当てる仕 様が好ましい • 複雑な認可制御が必要な場合は、役割を抽象化したロールを定義する • 時間経過に伴い、権限やロールが変化する場合もあるので、権限や ロールはセッション変数に保存せず、データベースを都度確認する • ロールと権限を「権限マトリックス」としてドキュメント化する 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 43

44.

ロールと権限マトリックスの例 ロール 権限 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 44

45.

SQLインジェクション 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 45

46.

LaravelのEloquent / クエリビルダー は基本的に堅牢 • LaravelのORマッパ(Eloquent)やクエリビルダーは通常何もしなくても SQLインジェクション対策されている – $tasks = Task::where('kind', '=', $kind)->get(); … WHERE kind=? プレースホルダでSQLイン ジェクション対策される – $tasks = Task::where('kind = 1 or 1=1#', '=', $kind)->get(); Column not found とい うエラーになる – $tasks = Task::where('kind', '=1 OR true# ', $kind)->get(); kind = '=1 OR true#' と解釈される 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru … WHERE kind='=1 OR true#' 46

47.

Laravel(Eloquent)のwhereRawメソッドの用途と注意点 • SQLのWHERE句を「生で」指定できる • 文字列連結でWHERE句を組み立てると、普通にSQLインジェクション 脆弱となる • 正しくはプレイスホルダを使う(後述) 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 47

48.
[beta]
脆弱性のあるアプリケーション(正常系)
class TaskController extends Controller
{
public function getTaks(Request $request)
{
kind=Shopping
$kind = $request->kind;
$tasks = Task::whereRaw("kind = BINARY '$kind'")->get();
return $tasks;
大文字・小文字
}
を区別して検索
[
}
{

select * from `tasks` where kind = BINARY 'Shopping'

"id":2,
"kind":"Shopping",
"title":"Remember the milk",
"memo":"Get one milk. If the eggs are there, get six."
},

【以下略】

徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

48

49.
[beta]
脆弱性のあるアプリケーション(攻撃)
class TaskController extends Controller
{
public function getTaks(Request $request)
kind=' UNION SELECT id, name, email, password, FROM users #
{
$kind = $request->kind;
$tasks = Task::whereRaw("kind = BINARY '$kind'")->get();
return $tasks;
}
select * from `tasks` where kind = BINARY '' UNION SELECT id, name, email, password FROM users #'
}
[

{

パスワードハッシュ値を
"id":2,
含む個人情報が漏洩する
"kind":"takahashi",
"title":"takahashi@example.jp",
"memo":"$2y$10$wMgTH4/IDTpzuUtRDvkYj.kDbfdnXJ6odWnJ5BvfZCKeu0bTJpFhG"
},

【以下略】
徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

49

50.
[beta]
対策 プレイスホルダ
• O/Rマッパーの機能を正しく使うことでSQLインジェクション対策
• whereRaw等を用いる場合は、文字列連結による条件生成をやめて、
プレイスフォルダを用いること
$tasks = Task::whereRaw("kind = BINARY '$kind'")->get();

$tasks = Task::whereRaw("kind = BINARY ?", [$kind])->get();

徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

50

51.

クロスサイトスクリプティング(XSS) 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 51

52.

SPAに対するXSS攻撃の概要 SESSION ID JavaScript Front End 表示ロジック { … } JSON TOKEN { … } JSON DB localStorage 徳丸浩のウェブセキュリティ講座 API XSSがあると、攻撃者 はAPIを自由に悪用で き、秘密情報の盗み取 りや不正操作が可能に ビジネスロジック なる © 2022 Hiroshi Tokumaru Session Storage 52

53.

Reactの表示はJSXが一般的(再掲) const SomeApp = () => { return ( <div>{ JavaScriptの式 }</div> JSの中でHTMLタグが使える ) } • JSXはプリプロセッサ(Babel)により通常のJavaScriptに変換され る • JSX中では { } の中にJavaScriptの式(文字列)を書くと、式の値 が展開されて表示される。HTMLエスケープは必要ない 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 53

54.

ここからは、具体的なユースケースとして、 改行混じりのテキスト表示を考えます ~改行を<br>タグに変換する~ 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 54

55.

なぜ改行を<br>タグに変換するのか • HTMLのテキスト上は改行は空白として扱われる <p>AAA BBB</p> AAA BBB と表示される • 改行するには、改行文字を<br>タグに変換する <p>AAA<br>BBB</p> AAA BBB 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 55

56.

PHPの場合 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 56

57.

PHPで改行交じりテキストを表示するのは簡単 • Vanilla PHP(素のPHP)の場合 echo nl2br(htmlspecialchars($text)); まずHTMLエスケープしてから 改行を<br>に変換する • Laravel(Blade)の場合 {!! nl2br(e($text)) !!} まずHTMLエスケープしてから 改行を<br>に変換する {!! !!} を用いてBladeのエスケープを抑止 • SPAの場合は上記は使えないのでJavaScriptで対応する必要がある 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 57

58.

Vanilla JSでinnerHTMLを使う(危険) <div> <span id="span1></span> </div> <script> const span1 = document.getElementById('span1') span1.innerHTML = text.replace('¥n', '<br>') </script> • innerHTMLを使うと改行→<br>の変換は簡単だが、DOM Base XSS脆弱 性が混入する • それでも使ってしまう開発者はいる 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 58

59.

innerHTMLによるXSS <html> <body> <div id="div1"></div> </body> <script> const div1 = document.getElementById('div1') div1.innerHTML = '<img src=1 onerror=alert(1)>' </script> </html> 徳丸浩のウェブセキュリティ講座 Copyright © Hiroshi Tokumaru 59

60.

Reactの場合 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 60

61.
[beta]
ReactでinnerHTML相当の機能を使う
const App = () => {
return <span dangerouslySetInnerHTML={{__html : nl2br(param)}}></span>
}
const newlineRegex = /(¥n)/g
const nl2br = str => str.replace(newlineRegex, '<br/>')

// 改行を<br>に変換

• Reactの場合、innerHTML相当の機能はdangerouslySetInnerHTMLと
いう見るからに危険そうな名前のディレクティブ
• それでも使ってしまう開発者はいる

徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

61

62.

定番の <script>alert(1)</script>を試すと… 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 62

63.

ReactアプリのDOM Based XSSの例 innerHTMLでscript要素 を追加しても発火しない 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 63

64.

<img src=/ onerror=alert(1)> を試す 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 64

65.

ReactアプリのDOM Based XSSの例 onerrorイベント等であ れば発火する 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 65

66.

このような声も… https://twitter.com/azu_re/status/1570780991614222337 徳丸浩のウェブセキュリティ講座 66

67.
[beta]
JSXを活用して改行表示
const App = () => {
return <span>{ nl2br(param) }</span>
}
const newlineRegex = /(¥r¥n|¥r|¥n)/g
const nl2br = (str) =>
str.split(newlineRegex).map((line, index) =>
line.match(newlineRegex) ?
<br key={index}/> :
line
)

徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

文字列 ABC¥nDEF

配列 ABC

配列 "ABC"

¥n

DEF

<br key=0/>

"DEF"

DOM span
ABC
br
DEF

67

68.

javascriptスキームによるXSS const App = () => { return <div> <a href={url}>リンク</a> </div> urlとしてjavascript:alert(1) をセットすると… } リンクをクリック すると発火する 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 68

69.
[beta]
【参考】AngularでDOM Based XSSに対してサニタイズが入る
app.component.html
<div><span [innerHTML]="text"></span></div>
import { Component } from '@angular/core'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
text = '<img src=/ onerror=alert(1)>';
}

app.component.ts

onerrorイベントが
削除されている

<img src="/">

徳丸浩のウェブセキュリティ講座

Copyright © Hiroshi Tokumaru

69

70.

XSSの緩和策の変遷 • X-XSS-Protection(XSS Auditor) – – – – – IE8にて初めて導入され、その後Google Chrome、Safariに導入される Firefoxは一貫して導入しなかった Google Chrome 78にて無効化(2019年10月) Safari 15.4にて無効化(2022年3月) 現在X-XSS-Protectionに対応しているブラウザはない • Content Security Policy(CSP) – X-XSS-Protectionに代わるXSS緩和策としてMozillaにより提唱され、その後標 準化された – Google Chrome 25(2013年2月)、Firefox 23(2013年 8月)、Safari 7(2013 年10月)にて正式導入 – 現在および今後のXSS緩和策はこちら 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 70

71.
[beta]
Content Security Policy入門
• 以下は、JavaScriptのソースを自分自身のオリジンと api.example.com に限定する
– Content-Security-Policy: script-src 'self' api.example.com

• 同時にインラインスクリプトも禁止される
– <script> … </script> や <body onload="…"> は禁止される

• インラインスクリプトを書きたい場合は、ナンスまたはハッシュを指定
<script nonce=EDNnf03nceIOfn39fn3e9h3sdfa>
…
</script>
に対して下記のCSPヘッダ
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

あるいは
<script>alert('Hello, world.');</script>
に対しては下記のCSPヘッダ
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='

参考: https://developers.google.com/web/fundamentals/security/csp/?hl=ja

徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

71

72.

なぜCSPを勧めるか • MPA(ページ遷移型のウェブアプリケーション)では、CSPの設定が やっかいである – インラインスクリプトが使えないのは面倒 • SPAはCSPと相性がよい – webpack等が生成するコードはHTMLとJavaScriptが分離されており、インライ ンスクリプトが元々ない – CSPを利用するサイトが増えている • XSS Auditorは蓄積型XSSおよびDOM Based XSSに効果がないが、CSP は防御効果がある • 開発当初からCSPヘッダを有効にして、確認しながら進めるとよい 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 72

73.

SPAのXSSまとめ • SPAのフレームワークはXSS対策がなされている • innerHTML相当の機能やjavascriptスキームによるXSSの可能性 • XSSの検査する時はscriptタグではなくイベントハンドラを使う ○ <img src=/ onerror=alert(1)> × <script>alert(1)</script> • X-XSS-Protection は今は有効ではない • CSPのすすめ • XSSやばい(APIの悪用)ので気をつけよう 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 73

74.

クロスサイトリクエストフォージェリ(CSRF) 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 74

75.
[beta]
まず被害者は正規サイトにログインしている前提
利用者

example.jp

GET /loginform HTTP/1.1
example.jp

<script>
xhr = new XMLHttpRequest()
xhr.open('POST', '/user')
xhr.send(…)
</script>

HTTP/1.1 200 OK
Content-Length: 1056
POST /login HTTP/1.1
{"id":"bob","password":"123456"}

HTTP/1.1 200 OK
Set-Cookie: SESSID=xxxx

徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

75

76.
[beta]
脆弱なAPI(メールアドレス変更)の正常系
利用者

example.jp

<script>
xhr = new XMLHttpRequest()
xhr.open('PATCH', '/user')
xhr.send(…)
</script>

example.jp

PATCH /user HTTP/1.1
Cookie: SESSID=xxxx
{"email":"bob@example.jp"}

HTTP/1.1 200 OK
メールアドレス変更

徳丸浩のウェブセキュリティ講座

© 2022 Hiroshi Tokumaru

76

77.

罠サイトからCSRF攻撃する模様 利用者 攻撃者 evil.example.com example.jp GET /trap HTTP/1.1 evil.example.com <form action= "https://example.jp/user" method="post"> <input "_method"="patch" <input name="email" value="evil@example.com"> <input type="submit"> </form> HTTP/1.1 200 OK Content-Length: 832 POST /user HTTP/1.1 Cookie: SESSID=xxxx _method=PATCH&email=evil@example.com 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 77

78.

Laravel / Sanctum のCSRFに関するサマリ • CSRF脆弱性が問題になるのはCookieによるセッション管理の場合のみ – Autherizationヘッダにトークンをセットする場合は影響なし • LaravelのCSRF対策はCSRFトークン • SanctumはCookieのSameSite属性を強制的に Lax にセットする • Laravelを普通に使えばCSRFは心配ない – Cookieによるセッション管理を使う場合はCSRF対策を無効化しないこと • 色々理屈をつけて無効化する人がいるがお勧めしない • Laravel以外のフレームワークを使う場合は、フレームワークのCSRF対 策機能を調べて使う 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 78

79.

総まとめ • SPAの代表的な脆弱性を紹介 • 脆弱性診断ではSPAの脆弱性が多く見つかっている – 特に、認可制御不備とSQLインジェクション • ブラウザ側(HTMLとJavaScript)のチェック処理は回避できる • フレームワークのセキュリティ機能を正しく活用する • innerHTML相当の機能(dangerouslySetInnerHTML、v-html等)は原則 使わない – 使う場合は危険な要素や属性を取り除くサニタイズ処理を行う • 開発者ができるセキュリティテストは多いので試してみよう 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 79