771.6K 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
SPAセキュリティ超入門 EGセキュアソリューションズ株式会社 徳丸 浩 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru
徳丸浩の自己紹介 • 経歴 – 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
本日お話したいこと • SPA(Single Page Application)とは • SPAと攻撃経路 • JavaScriptによる検証はバイパスできる • 認可制御不備 • SQLインジェクション • クロスサイトスクリプティング(XSS) • クロスサイトリクエストフォージェリ(CSRF) • まとめ ※ コード例には、ReactとLaravelを用います 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 3
SPA(Single Page Application)とは? 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 4
SPA以前のウェブ=MPA(Multi-Page Application) JavaScriptで代入した値は次のページではリセットされる → セッション管理の機能によりデータを引き継ぐ 処理 徳丸浩のウェブセキュリティ講座 こんにちは 次へどうぞ NEXT NEXT 処理 © 2022 Hiroshi Tokumaru ありがとうござい ました NEXT 処理 5
SPA(Single Page Application)の構造 ページ遷移をしないのでJavaScriptの 変数は保持される。 ただし、ページ遷移、戻る、リロー ドで変数の値はリセットされる SPA → セッション管理あるいは localStorageによりデータを引き継ぐ HTML JS XHR/ Fetch コンテンツ 配信 Webサーバー 徳丸浩のウェブセキュリティ講座 JSON 処理 APIサーバー © 2022 Hiroshi Tokumaru 6
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
つまり、SPAのクライアント側はほぼ JavaScriptと言っても過言ではない (CSSや画像はあります) 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 8
ブラウザに表示されるHTMLはどこから? 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 9
DOMでHTMLを作ります 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 10
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
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
Reactの表示はJSXが一般的 const SomeApp = () => { return ( <div>{ JavaScriptの式 }</div> JSの中でHTMLタグが使える ) } • JSXはトランスパイラ(Babel)により通常のJavaScriptに変換され る • JSX中では { } の中にJavaScriptの式(文字列)を書くと、式の値 が展開されて表示される。HTMLエスケープは必要ない 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 13
SPAと攻撃経路 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 14
SPAの構造 TOKEN or SESSION ID Front End Store { … } JSON API { … } JSON ビジネスロジック 表示ロジック DB localStorage 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru Session Storage 15
SPAのセッション管理方式 1 セッションID方式 POST /login {"id":"bob","pass":"123456"} SESSID=f8d92b0… API Front End SESSID=f8d92b0… Store ビジネスロジック {"email":"[email protected]"} 表示ロジック DB SESSID=f8d92b0… 5:bob,[email protected] 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru Session Storage f8d92b0…:5,bob 16
SPAのセッション管理方式 2: ステートフルトークン POST /login {"id":"bob","pass":"123456"} token=a94c5d… API Front End token=a94c5d… Store ビジネスロジック {"email":"[email protected]"} 表示ロジック DB localStorage a94c5d…:id=5 5:bob,[email protected] token=a94c5d… 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 17
SPAのセッション管理方式 3 ステートレストークン(JWT等) POST /login {"id":"bob","pass":"123456"} token=eyJhbG… API Front End Store token=eyJhbG… ビジネスロジック {"email":"[email protected]"} 表示ロジック デコード&検証 {"sub":"5","exp":17…} localStorage 5:bob,[email protected] DB token=eyJhbG… 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 18
Laravel APIのセッション管理 方式名 セッションID ステートフルトークン ステートレストークン(JWT) ブラウザ側保管場所 Cookie localStorage localStorage データの保管場所 ファイルやRedis DB トークンに内包 保管できる情報 認証情報 + なんでも 認証情報 + 権限 認証情報 +α スケーラビリティ 小~中 中 大 即時ログアウト 容易 容易 困難 一斉ログアウト 工夫を要する 容易 困難 Laravelでの実装 Sanctum (SPA認証) Sanctum(APIトークン) tymon/jwt-auth CSRF脆弱性の可能性 あり=対策要 なし なし 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 19
クライアント側のロジックやデータは取得・変更可能 TOKEN or SESSION ID Front End Store 表示ロジック { … } ビジネスロジック JSON DB localStorage 徳丸浩のウェブセキュリティ講座 API 取得・変更 © 2022 Hiroshi Tokumaru Session Storage 20
JavaScriptによる検証はバイパスできる 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 21
悪用例1: クライアント側バリデーションの回避 START HTMLやJavaScritに よるチェック <input type="number" min="0" max="100"> ※ 0~100の数値のみ許容 HTMLやJavaScriptによる チェック(バリデーション 等)は攻撃者は回避可能 No OK? Yes 処理実行(API) エラー END 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 22
悪用例2: クライアントによるチェック処理の回避 START APIによる権限 チェック No OK? Yes 処理実行(API) エラー APIによる処理でも、チェッ クと実行(更新等)が別だ と、チェック処理は回避可 能 END 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 23
パスワード変更機能の場合 example.jp パスワード変更 旧パスワード 新パスワード 新パスワード(確認) ***** ***** ***** 変更 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 現在パスワード の確認 確認用の再入力 24
パスワード変更機能の脆弱な実装 利用者 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
再認証をバイパスしてパスワード変更が可能
攻撃者
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
認可制御不備 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 27
認可制御不備の典型的パターン(1) 利用者 http://example.jp/login http://example.jp/logindone ログインしました ID パスワード ログイン 秘密情報をどうぞ 情報リソースのURLを知っていると 認証なしで情報が閲覧できる 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 攻撃者 http://example.jp/secret 秘密情報 ・・・・・・・・・・・・・・・ ・・・・・・・・・・・・・・・ ・・・・・・・・・・・・・・・ ・・・・・・・・・・・・・・・ URL を知っていると 未ログインでも閲覧可能 28
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
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
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
認可制御不備の典型的パターン(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
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
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
ポリシーによる認可制御の実装例(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
認可制御不備の典型的パターン(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
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":"[email protected]"}
HTTP/1.1 200 OK
徳丸浩のウェブセキュリティ講座
© 2022 Hiroshi Tokumaru
管理者がメールア
ドレスを変更する
37
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":"[email protected]"}
HTTP/1.1 200 OK
徳丸浩のウェブセキュリティ講座
© 2022 Hiroshi Tokumaru
権限のないメール
アドレス変更
38
認可制御不備の典型的パターン(4) 正常系 一般ユーザとしてログイン Cookie: USER=normal 管理者としてログイン Cookie: USER=admin 攻撃 投稿 閲覧 一般ユーザとしてログイン Cookie: USER=admin 投稿 閲覧 ユーザ登録 アカウント停止 Cookieに保持した権限情報を変 更しただけで管理者メニューが 表示され、実行もできる 投稿 閲覧 ユーザ登録 アカウント停止 フレームワークのStoreやlocalStorage、クッキーに権限情報を保持している 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 39
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":"[email protected]"}
HTTP/1.1 200 OK
徳丸浩のウェブセキュリティ講座
© 2022 Hiroshi Tokumaru
管理者がメールア
ドレスを変更する
40
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":"[email protected]"}
HTTP/1.1 200 OK
徳丸浩のウェブセキュリティ講座
© 2022 Hiroshi Tokumaru
権限のないメール
アドレス変更
41
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
認可制御の正しい実装 • 認可制御の基本は、APIの中で権限を確認すること – 秘密情報の表示 – 権限の必要な機能実行 – その他、権限を必要とする操作 • 権限を確認する基準は、セッション変数やトークンに紐づいた ログインID • admin等の特権専用ユーザーは作らず、個人IDに権限を割り当てる仕 様が好ましい • 複雑な認可制御が必要な場合は、役割を抽象化したロールを定義する • 時間経過に伴い、権限やロールが変化する場合もあるので、権限や ロールはセッション変数に保存せず、データベースを都度確認する • ロールと権限を「権限マトリックス」としてドキュメント化する 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 43
ロールと権限マトリックスの例 ロール 権限 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 44
SQLインジェクション 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 45
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
Laravel(Eloquent)のwhereRawメソッドの用途と注意点 • SQLのWHERE句を「生で」指定できる • 文字列連結でWHERE句を組み立てると、普通にSQLインジェクション 脆弱となる • 正しくはプレイスホルダを使う(後述) 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 47
脆弱性のあるアプリケーション(正常系)
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
脆弱性のあるアプリケーション(攻撃)
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":"[email protected]",
"memo":"$2y$10$wMgTH4/IDTpzuUtRDvkYj.kDbfdnXJ6odWnJ5BvfZCKeu0bTJpFhG"
},
【以下略】
徳丸浩のウェブセキュリティ講座
© 2022 Hiroshi Tokumaru
49
対策 プレイスホルダ
• O/Rマッパーの機能を正しく使うことでSQLインジェクション対策
• whereRaw等を用いる場合は、文字列連結による条件生成をやめて、
プレイスフォルダを用いること
$tasks = Task::whereRaw("kind = BINARY '$kind'")->get();
$tasks = Task::whereRaw("kind = BINARY ?", [$kind])->get();
徳丸浩のウェブセキュリティ講座
© 2022 Hiroshi Tokumaru
50
クロスサイトスクリプティング(XSS) 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 51
SPAに対するXSS攻撃の概要 SESSION ID JavaScript Front End 表示ロジック { … } JSON TOKEN { … } JSON DB localStorage 徳丸浩のウェブセキュリティ講座 API XSSがあると、攻撃者 はAPIを自由に悪用で き、秘密情報の盗み取 りや不正操作が可能に ビジネスロジック なる © 2022 Hiroshi Tokumaru Session Storage 52
Reactの表示はJSXが一般的(再掲) const SomeApp = () => { return ( <div>{ JavaScriptの式 }</div> JSの中でHTMLタグが使える ) } • JSXはプリプロセッサ(Babel)により通常のJavaScriptに変換され る • JSX中では { } の中にJavaScriptの式(文字列)を書くと、式の値 が展開されて表示される。HTMLエスケープは必要ない 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 53
ここからは、具体的なユースケースとして、 改行混じりのテキスト表示を考えます ~改行を<br>タグに変換する~ 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 54
なぜ改行を<br>タグに変換するのか • HTMLのテキスト上は改行は空白として扱われる <p>AAA BBB</p> AAA BBB と表示される • 改行するには、改行文字を<br>タグに変換する <p>AAA<br>BBB</p> AAA BBB 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 55
PHPの場合 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 56
PHPで改行交じりテキストを表示するのは簡単 • Vanilla PHP(素のPHP)の場合 echo nl2br(htmlspecialchars($text)); まずHTMLエスケープしてから 改行を<br>に変換する • Laravel(Blade)の場合 {!! nl2br(e($text)) !!} まずHTMLエスケープしてから 改行を<br>に変換する {!! !!} を用いてBladeのエスケープを抑止 • SPAの場合は上記は使えないのでJavaScriptで対応する必要がある 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 57
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
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
Reactの場合 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 60
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
定番の <script>alert(1)</script>を試すと… 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 62
ReactアプリのDOM Based XSSの例 innerHTMLでscript要素 を追加しても発火しない 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 63
<img src=/ onerror=alert(1)> を試す 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 64
ReactアプリのDOM Based XSSの例 onerrorイベント等であ れば発火する 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 65
このような声も… https://twitter.com/azu_re/status/1570780991614222337 徳丸浩のウェブセキュリティ講座 66
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
javascriptスキームによるXSS const App = () => { return <div> <a href={url}>リンク</a> </div> urlとしてjavascript:alert(1) をセットすると… } リンクをクリック すると発火する 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 68
【参考】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
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
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
なぜCSPを勧めるか • MPA(ページ遷移型のウェブアプリケーション)では、CSPの設定が やっかいである – インラインスクリプトが使えないのは面倒 • SPAはCSPと相性がよい – webpack等が生成するコードはHTMLとJavaScriptが分離されており、インライ ンスクリプトが元々ない – CSPを利用するサイトが増えている • XSS Auditorは蓄積型XSSおよびDOM Based XSSに効果がないが、CSP は防御効果がある • 開発当初からCSPヘッダを有効にして、確認しながら進めるとよい 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 72
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
クロスサイトリクエストフォージェリ(CSRF) 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 74
まず被害者は正規サイトにログインしている前提
利用者
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
脆弱な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":"[email protected]"}
HTTP/1.1 200 OK
メールアドレス変更
徳丸浩のウェブセキュリティ講座
© 2022 Hiroshi Tokumaru
76
罠サイトから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="[email protected]"> <input type="submit"> </form> HTTP/1.1 200 OK Content-Length: 832 POST /user HTTP/1.1 Cookie: SESSID=xxxx _method=PATCH&[email protected] 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 77
Laravel / Sanctum のCSRFに関するサマリ • CSRF脆弱性が問題になるのはCookieによるセッション管理の場合のみ – Autherizationヘッダにトークンをセットする場合は影響なし • LaravelのCSRF対策はCSRFトークン • SanctumはCookieのSameSite属性を強制的に Lax にセットする • Laravelを普通に使えばCSRFは心配ない – Cookieによるセッション管理を使う場合はCSRF対策を無効化しないこと • 色々理屈をつけて無効化する人がいるがお勧めしない • Laravel以外のフレームワークを使う場合は、フレームワークのCSRF対 策機能を調べて使う 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 78
総まとめ • SPAの代表的な脆弱性を紹介 • 脆弱性診断ではSPAの脆弱性が多く見つかっている – 特に、認可制御不備とSQLインジェクション • ブラウザ側(HTMLとJavaScript)のチェック処理は回避できる • フレームワークのセキュリティ機能を正しく活用する • innerHTML相当の機能(dangerouslySetInnerHTML、v-html等)は原則 使わない – 使う場合は危険な要素や属性を取り除くサニタイズ処理を行う • 開発者ができるセキュリティテストは多いので試してみよう 徳丸浩のウェブセキュリティ講座 © 2022 Hiroshi Tokumaru 79