Skip to content

入力+検査=型安全

公開日:

沖縄県中頭郡西原町琉球大学 理系複合棟で開催された『PHPカンファレンス沖縄2023』でレギュラーセッション(30分)として発表しました。

Download PDF

スライドテキスト

Page 1

入力+検査=型安全

Make PHP type safe by validating input

pixiv Inc.

USAMI Kenta

2023-09-16 PHPカンファレンス沖縄2023

Page 2

お前誰よ

  • うさみけんた (@tadsan) / Zonu.EXE / にゃんだーすわん
  • ピクシブ株式会社 pixiv事業本部 Webエンジニアリングチーム PHPer
  • 2012年末から現職、APIとかCIとかいろいろなところを見つめてきました
  • 最近はピクシブ百科事典(dic.pixiv.net)も開発しています
  • Emacs PHP Modeを開発しています (2017年-)
  • プログラミング言語にちょっとこだわりのある素人 (spcamp2010)

Page 3

Page 4

Page 5

さて

Page 6

型、つけてますか?

Page 7

PHPの進化は型宣言の進化

Page 8

型がついていない関数(PHP5)

function add($a, $b) {return $a + $b;}

Page 9

スカラー型宣言(PHP7)

int + intって本当にintなの?

function add(int $a, int $b): int {return $a + $b;}

Page 10

広い値をとるにはfloatが必要

ひとつの解決策ではあるが… 不必要にfloatを強制するのか

function add(float $a, float $b): float {return $a + $b;}

Page 11

PHPDocの型注釈(アノテーション)

あえて型宣言を省略する

/*** @param int|float $a* @param int|float $b* @return int|float*/function add($a, $b) {return $a + $b;}

Page 12

ユニオン型宣言 (PHP8.0)

function add(int|float $a, int|float $b): int|float {return $a + $b;}

Page 13

いたるところに型が書けるようになった

Page 14

PHP Static Analysis Tool

Page 15

PHPStanとは

  • 2016年から開発されているPHPの静的解析ツール
  • Ondřej Mirtesさんの個人プロジェクト、2021年からフルタイム開発
  • 開発当初は純粋な静的解析ではなく実行時リフレクションを用いることで

高速な解析を実現していた

  • 現在は静的解析がデフォルトで、レガシープロジェクトに導入しやすくなった
  • その他のPHP静的解析ツールにはPsalm, Phan, Qodana(PhpStorm)

Page 16

Page 17

201X年、PHPは型の炎に包まれた!!

Page 18

いまやPHPは静的型付きと言っても過言ではない(本当か…?)

Page 19

だが型なしは滅びていなかった

Page 20

型なしはどこからくるの?

Page 21

今回のお題

Page 22

そうです

Page 23

今回のお題

Page 24

みなさんに覚えておいてほしいこと

Page 25

型なしとは何か

Page 26

Validation

Page 27

Validation

Sanitize

Page 28

Validation

Sanitize

Escape

Page 29

これは気にしなくていい

Validation

Sanitize

Escape

Page 30

自信をもって説明できますか?

Page 31

…ここから本題

Page 32

クエリパラメータからID値をとりたい

Page 33

/** IDから記事を取得 */function getById(int $id): Article{// 中でデータベースに問い合わせ}

Page 34

実際あぶない

$id = $_GET['id'];

$article = getById($id);

Page 35

そもそも何者

$id = $_GET['id'];

$article = getById($id);

Page 36

ここでブラウザ開く

https://phpstan.org/try

Page 37

型なしとはどういう状態か

  • ある式の値の型が静的に定まっていない (mixed, TSのunknown/any)
  • 型付けされた関数に渡したときに要求された文脈に従っているか

(関数呼び出しをしたときにTypeErrorが出ないか安全だと言いきれない)

  • PHPではいろんなところから型なしが吹き出す
  • スーパーグローバル、eval、json_decode()、unserialize ()、

PDOStatement::fetch()

Page 38

// ?id=1$_GET['id'] === '1';

Page 39

// ?id=a$_GET['id'] === 'a';

Page 40

// ?id[]=a$_GET['id'] === ['a'];

Page 41

// ?id[foo]=bar$_GET['id'] ===['foo' => 'bar'];

Page 42

キャストすればいいのか

Page 43

危険ではないがよくはない

$id = (int)$_GET['id'];

Page 44

// ?id=32x$id = (int)$_GET['id'];$id === 32;

Page 45

if (is_numeric($_GET['id'])) {

throw new BadRequestException();

}$id = (int)$_GET['id'];

Page 46

is_numeric()

Page 47

整数 123小数 1.23

指数表記1.844674407371E+1

前後にスペース

Page 48

だいたいのIDは正の整数だけ

Page 49

// ?id=1.23 if (is_numeric($_GET['id'])) {

throw new BadRequestException();

}$id = (int)$_GET['id'];

Page 50

安全に

int|false

$id = filter_var($_GET['id'] ?? '',FILTER_VALIDATE_INT);

Page 51

安全に

int|false

$id = filter_var($_GET['id'] ?? '',FILTER_VALIDATE_INT,['options' => ['min_range' => 1,]]);

Page 52

ここでブラウザ開く

https://phpstan.org/try

https://php-play.dev/

Page 53

filter_var()

Page 54

うまいこと値のバリデーションできる標準関数

Page 55

……話は遡り2007年

Page 56

伝説のプレゼン

Page 57

ホルモンのPHP (前半)

  • Rasmus LerdorfがPHPを作った歴史的経緯を話している
  • 昔のPHPコードとか開発体制とか
  • 構文解析とか苦手なのでPHPの構文がめちゃくちゃだったという自虐
  • 利己的な動機で始めたプロジェクトだが開発者を惹きつけるように工夫して

いる様子

  • 自宅サーバに入れたDrupalが遅いのでプロファイリングした

Page 58

ホルモンのPHP (後半)

  • 世の中にセキュリティの問題が多いウェブサイトが多すぎる
  • IEとかFlashとかAdobe ReaderとかUTF-7とか罠がいっぱい
  • 俺たちのウェブは完全に壊れている。特にXSSがヤバイ。JS実行されちゃう。

Page 59

ホルモンのPHP (後半)

  • 世の中にセキュリティの問題が多いウェブサイトが多すぎる
  • IEとかFlashとかAdobe ReaderとかUTF-7とか罠がいっぱい
  • 俺たちのウェブは完全に壊れている。特にXSSがヤバイ。JS実行されちゃう。
  • だからfilter関数作ったよ! サニタイズすれば絶対にXSS起こらない!

Page 60

フィルタにはサニタイズ機能もある

Page 61

フィルタにはサニタイズ機能もある使わないで!!

Page 62

サニタイズとは何か

Page 63

わからん

Page 64

???

Page 65

今年書いた記事

Page 66

フィルタ関数を使えば解決するのか

Page 67

今年書いた記事

Page 68

なんだよ外部公開してないライブラリの自慢かよ

Page 69

キタカミの里から帰ってきたら公開したい!!

Page 70

対応予定

スーパーグローバル($_XXX)

PSR-7 ServerRequest