そーくのつれづれぶろぐ

web系エンジニアの勉強したことなど

【PHP】文字列から数値への変換法則をわかりやすく表してみた(PHPコードwith正規表現)

概要

PHPの本家に書いてあるけれども、文字列→数値の変換ルールを文字だけで説明されてるとすぐに頭に入らないので、擬似コードにした。 https://www.php.net/manual/ja/language.types.type-juggling.php

動機

直接的な動機ではないけれどもヒヤリハットとして頭に入れておいて損はしないだろうと思い記事を書くことにした。

脆弱性ツールによって開発中の画面にXSSが見つけられたので、その対応をすることになった。修正前に一度XSSを発生させよーとXSSを発生させる手順を踏んだはずが、なぜか発生しない事象に出会った。

少し掘り下げて書くと、整数を想定しているhiddenパラメータに悪意のある文字列(alert();だとか)を入れてsubmitすると実行されるという内容だった。そのためXSSを発生させるような入力値をいれてさあ脆弱性でてきてくれ!とsubmitをするが発生しない。なぜだ。といろいろ入力値を変えていたら発生する場合と発生しない場合があるというのが明らかになった。

発生しない場合というのが、その入力値がalert(123);というように数値が途中で入っていたときだった。おそらくフレームワークがフォーム値にバリデートをかけるときに数値が入っていれば数値に変換し、入っていなければ数値変換しない処理を入れているらしくXSSが発生しない、という内容だった。(逆に文字列onlyだったら通るのはフレームワークの不具合ではと思ったがとりあえずここでは割愛する)

PHP特有の「あいまいな表現でもOK」が仇となる場合もままあることと、webアプリの特性として「フォーム値(文字列)を数値として読む」ことは避けられないため、今後実装をする場合に考慮に入れることはそうとして一度整理していざというときにすぐに分かる形にまとめたいと思ったためここに記載する。

文字列から数値への変換法則をPHPプログラムで表現する

前置きが長くなったが、文字列からの数値への変換法則を書いてみる。

$VALID_NUM_PATTERNがいかんせんややこしい正規表現書いているけれどここで本家サイトの引用をば。以下の文章を正規表現で表した次第。

有効な数値データは符号(オプション)の後に、 1 つ以上の数字 (オプションとして小数点を 1 つ含む)、 オプションとして指数部が続きます。指数部は 'e' または 'E' の後に 1 つ以上の数字が続く形式です。

<?php

function convertStr2Num(string $str) {
  $VALID_NUM_PATTERN = '[-+]?[0-9]+([\.][0-9]+)?([e|E][0-9]+)?';
  $pattern = '/^(' . $VALID_NUM_PATTERN . ')/';
  
  // 1. 有効な数値データが文字列の0文字目から存在するか
  if (preg_match($pattern , $str, $matches)) {
    // 2. 小数点がない、指数部がない、integerの最大値より小さい 
    if ((strpos($matches[0], '.') === false) && 
        (strpos($matches[0], 'e') === false) &&
        (strpos($matches[0], 'E') === false) &&
          strlen($matches[0]) < PHP_INT_MAX) {
      return (integer)$matches[0];
    } else {
      return (float)$matches[0];
    }
  } else {
    return (int)0;
  }
}

終わりに

まとめるとこう↓

「整数じゃない値が文字列の最初に来ていたらfloat, 整数が文字列の最初に来ていたらinteger、それ以外は全部(integer)0」

書いている途中からこの1文がすべてと気づいてしまってちょっと消え去りたくなった。 私の正規表現記述能力が向上したのと、コード書かないと書くスピードが上がらないことを実感したから個人としては、有益、だったと...いうことで...