Azure Functions で、特定のファイルはアップロードさせないようにする方法

以下の手順で実施可能

1:.funcignore を作成する

learn.microsoft.com

.funcignore:(省略可能) Azure に発行しないファイルを宣言します。 通常、このファイルには、エディター設定を無視する場合は .vscode/、テスト ケースを無視する場合は tests/、ローカル アプリの設定を発行しない場合は local.settings.json が含まれます。

以下の感じで記述する。

.git*
.vscode
__azurite_db*__.json
__blobstorage__
__queuestorage__
local.settings.json
tsconfig.json
*.test.ts
*.test.js
*.test.js.map

ディレクトリの指定などは特に不要。 たとえば、*.test.ts と記述しておけば、hoge.test.tsfuga.test.tsTrigger1/piyp.test.ts のすべてがアップロード除外対象になる。

2:(デプロイにAzure/functions-actionを使っている場合) respect-funcignoreオプションを指定する

github.com

respect-funcignore: Allow GitHub Action to parse your .funcignore file and exclude files and folders defined in it. By default, this value is set to false. If your GitHub repo contains .funcignore file and want to exclude certain paths (e.g. text editor configs .vscode/, Python virtual environment .venv/), we recommend setting this to true.

respect-funcignore: true を指定しないと、デプロイ時に .funcignoreを見てくれないので注意。

- name: 'Run Azure Functions Action'
  uses: Azure/functions-action@v1
  with:
    ...
    respect-funcignore: true

GithubActionsワークフローで、特定のジョブをRe-runした場合に、前のジョブの$GITHUB_OUTPUT出力値を参照可能かどうか

概要

表題の通りだが、GithubActionsワークフローで、特定のジョブをRe-runした場合に、前のジョブの$GITHUB_OUTPUT出力値を参照可能かどうか検証する。

結論

参照可能だった。

以下情報を参照すると、ワークフローの最初の実行から30日後までは参照可能っぽい。

ワークフローまたはワークフロー内のジョブは、最初の実行から最大 30 日後まで再実行できます。

前のワークフロー実行で成功したジョブの出力はすべて、再実行に使用されます。

docs.github.com

検証用コード

github.com

LaravelのRequestオブジェクトの動きを追う

PHPフレームワークのLaravelでは、Requestオブジェクトを通じて、リクエスト情報を簡単に取得できる。

PHPならばスーパーグローバル変数である$_POST[$key]とかで逐一取得するところも、LaravelだとRequestオブジェクトのインスタンス$requestから、$request->input($key)とかで取得できる。

この流れについて、Laravel内部ではどのような動きをしているのか、ということについて追ってみる。

Requestオブジェクト取得

ここでは、フォームから送信された値を取得するケースを考える。

PHPだと、フォームから送信されたvalue値をそれぞれ$_POST[$key]で取得するケースである。

フォームからの送信値が、name属性としてnameというkey名を持っていたとすると、Laravelでは下記のような記述で取得することができる。

    public function store(ValidateRequest $request)
    {
        inputName = $request->input('name');
    }

今回は、フォームリクエスValidateRequestから値を取得している。インスタンス化に際しては、サービスコンテナで自動的に解決される。

フォームリクエストの記述を追う

フォームリクエストは、Illuminate\Foundation\Http\FormRequestを継承しているので、次にこちらのソースコードを追ってみる。

$request->input()で値を取得しているので、input()メソッドが存在しないか探してみるが、ここには存在していない。

Illuminate\Foundation\Http\FormRequestは、Illuminate\Http\Requestを継承しているので、次にこちらのソースコードを追ってみる。

Illuminate\Http\Requestを追う

しかし、このファイル内にもinput()が存在しない、、

しかし、Requestクラス内で、Concerns\InteractsWithInputtraitをuseしており、名前的にここにありそうだと踏む。

そこで次に、Concerns\InteractsWithInputソースコードを追ってみる。

Concerns\InteractsWithInputを追う。

同ファイルの206行目、ついにそれらしき記述を発見した。

    public function input($key = null, $default = null)
    {
        return data_get(
            $this->getInputSource()->all() + $this->query->all(), $key, $default
        );
    }

上記メソッドの説明を見ると、Retrieve an input item from the request.とある。中身を見ると、data_get()を用いているが、これはLaravelのヘルパ関数である。

readouble.com

    function data_get($target, $key, $default = null)
    {
        if (is_null($key)) {
            return $target;
        }

        $key = is_array($key) ? $key : explode('.', $key);

        while (! is_null($segment = array_shift($key))) {
            if ($segment === '*') {
                if ($target instanceof Collection) {
                    $target = $target->all();
                } elseif (! is_array($target)) {
                    return value($default);
                }

                $result = [];

                foreach ($target as $item) {
                    $result[] = data_get($item, $key);
                }

                return in_array('*', $key) ? Arr::collapse($result) : $result;
            }

            if (Arr::accessible($target) && Arr::exists($target, $segment)) {
                $target = $target[$segment];
            } elseif (is_object($target) && isset($target->{$segment})) {
                $target = $target->{$segment};
            } else {
                return value($default);
            }
        }

        return $target;
    }

input()の中身をより詳しくみると、$this->getInputSource()->all() + $this->query->all()の部分は対象の配列であり、配列に存在する$key名のvaluw値を返却している。

$key名として渡されるのは、ここではフォームのname属性である。ということは、$this->getInputSource()->all() + $this->query->all()の部分に、$_POSTのような連想配列が渡されていると考えられる。

そこで、次にこの部分を追ってみる。

$this->getInputSource()->all() + $this->query->all()を追う

まずは、getInputSource()が何かを追う。おそらくこの部分で、配列の実体が返却されることが想定される。getInputSource()がどこにあるのかというと、Illuminate/Http/Requestに定義されている。

    protected function getInputSource()
    {
        if ($this->isJson()) {
            return $this->json();
        }

        return in_array($this->getRealMethod(), ['GET', 'HEAD']) ? $this->query : $this->request;
    }

説明をみると、Get the input source for the request.とある。リクエスト情報を取得していることが予想できる。

関数の中身を詳しくみてみる。内部は3項演算子で構成されていて、条件式はin_array($this->getRealMethod(), ['GET', 'HEAD'])である。決め打ちになってしまうが、HTTPリクエストのメソッドを取得しているものだと考えられる。そして、HTTPメソッドがGET, HEADのどちらかであれば、$this->queryを返却し、それ以外であれば$this->requestを返却していることが分かる。

今回の場合はPOSTメソッドなので、$this->requestが配列の実体だろうと考えられる。

$this->requestを追う

$this->requestだが、クラスプロパティとしてはSymfony\Component\HttpFoundation\Requestに定義されているので、こちらを追ってみる。

    /**
     * Request body parameters ($_POST).
     *
     * @var \Symfony\Component\HttpFoundation\ParameterBag
     */
    public $request;

上記が、$requestのプロパティに該当する。$_POSTのパラメータであることも明記されており、上記で扱っていることが分かる。

中身がどこで渡されているのかだが、まずは__construct()に着目する。

    /**
     * @param array                $query      The GET parameters
     * @param array                $request    The POST parameters
     * @param array                $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
     * @param array                $cookies    The COOKIE parameters
     * @param array                $files      The FILES parameters
     * @param array                $server     The SERVER parameters
     * @param string|resource|null $content    The raw body data
     */
    public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
    {
        $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
    }

配列の引数として、$requestが渡されているが、実際はinitialize()の引数に渡っている。

    /**
     * Sets the parameters for this request.
     *
     * This method also re-initializes all properties.
     *
     * @param array                $query      The GET parameters
     * @param array                $request    The POST parameters
     * @param array                $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
     * @param array                $cookies    The COOKIE parameters
     * @param array                $files      The FILES parameters
     * @param array                $server     The SERVER parameters
     * @param string|resource|null $content    The raw body data
     */
    public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
    {
        $this->request = new ParameterBag($request);
        $this->query = new ParameterBag($query);
        $this->attributes = new ParameterBag($attributes);
        $this->cookies = new ParameterBag($cookies);
        $this->files = new FileBag($files);
        $this->server = new ServerBag($server);
        $this->headers = new HeaderBag($this->server->getHeaders());

        $this->content = $content;
        $this->languages = null;
        $this->charsets = null;
        $this->encodings = null;
        $this->acceptableContentTypes = null;
        $this->pathInfo = null;
        $this->requestUri = null;
        $this->baseUrl = null;
        $this->basePath = null;
        $this->method = null;
        $this->format = null;
    }

上記関数の$this->request = new ParameterBag($request);にて、中身が渡されているようだ。左記でインスタンス化されている\Symfony\Component\HttpFoundation\ParameterBagは、パラメータを総合的に扱っているファイルのように見受けられる。

ライフサイクル的アプローチにより、リクエストを探る

次に観点を変えて、Laravelのライフサイクル的アプローチによって、どこでリクエスト情報が取得されているのかを調査する。

Laravelのライフサイクルだが、周知の通り、エントリポイントであるpublic/index.phpが処理のすべての入り口である。

<?php

require __DIR__.'/../vendor/autoload.php';

$app = require_once __DIR__.'/../bootstrap/app.php';

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);

上記が、public/index.phpの中身である。(コメントは省略してある。)

この中で、リクエスト情報に関連するのは4行目(空欄を含めると9行目)以降である。

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

上記の、Illuminate\Http\Request::capture()によってRequestが生成され、引数としてhandle()に渡されている。

handle()によって引数のリクエスト情報がルーティングに渡され、リクエスト情報に含まれるパスが判定され、定義したコントローラーのメソッドに処理が移るという流れである。

ということで、Illuminate\Http\Request::capture()でリクエスト情報を取得している実装を追ってみる。

Illuminate\Http\Request::capture()を追う

実際にIlluminate\Http\Requestに定義されたcapture()ソースコードを見ると、下記のような記述である。

    /**
     * Create a new Illuminate HTTP request from server variables.
     *
     * @return static
     */
    public static function capture()
    {
        static::enableHttpMethodParameterOverride();

        return static::createFromBase(SymfonyRequest::createFromGlobals());
    }

return static::createFromBase(SymfonyRequest::createFromGlobals());だが、引数にもメソッドcreateFromGlobals()が呼ばれている。

まずは、上記メソッドを追ってみる。

createFromGlobals()を追う

createFromGlobals()は、Symfony\Component\HttpFoundation\Requestに静的メソッドとして、下記のように定義されている。

    /**
     * Creates a new request with values from PHP's super globals.
     *
     * @return static
     */
    public static function createFromGlobals()
    {
        $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);

        if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
            && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
        ) {
            parse_str($request->getContent(), $data);
            $request->request = new ParameterBag($data);
        }

        return $request;
    }

記述をみると分かるように、createRequestFromFactory()の引数として、スーパーグローバル変数(関心のある$_POST含め)が渡されている。

コメントを見ても、Creates a new request with values from PHP's super globals.とあり、この関数において、スーパーグローバル変数に格納されたリクエスト情報を受け取っていることが分かった。

HTTPリクエスト情報がどこに格納されるのか、ようやく姿を現した次第である。

引数として渡されたスーパーグローバル変数が、どのようにしてRequestオブジェクトに変換されるのかをさらに追いたいため、引き続きcreateRequestFromFactory()ソースコードを調査する。

createRequestFromFactory()を追う

こちらの関数も、同じくSymfony\Component\HttpFoundation\Requestに静的メソッドとして定義されている。

記述としては、下記である。

    private static function createRequestFromFactory(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
    {
        if (self::$requestFactory) {
            $request = (self::$requestFactory)($query, $request, $attributes, $cookies, $files, $server, $content);

            if (!$request instanceof self) {
                throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.');
            }

            return $request;
        }

        return new static($query, $request, $attributes, $cookies, $files, $server, $content);
    }

第2引数のarray $request = []が、スーパーグローバル変数の$_POSTに該当する。

冒頭のself::$requestFactoryだが、これは同ファイル内の下記に詳しく記述されている。

    /**
     * Sets a callable able to create a Request instance.
     *
     * This is mainly useful when you need to override the Request class
     * to keep BC with an existing system. It should not be used for any
     * other purpose.
     *
     * @param callable|null $callable A PHP callable
     */
    public static function setFactory($callable)
    {
        self::$requestFactory = $callable;
    }

This is mainly useful when you need to override the Request class * to keep BC with an existing system.

記述、コメントを見ると、Requestオブジェクト生成ロジックを拡張したい時に、上記メソッドによってクラスを差し替えるようである。今回は拡張予定はないので一旦スルー。

さて、createRequestFromFactory()の最後に、重要な記述がある。

return new static($query, $request, $attributes, $cookies, $files, $server, $content);

staticなので、自分自身(ここではSymfony\Component\HttpFoundation\Requestクラス)のインスタンスを新たに生成している。そして、引数として渡されている$requestは、スーパーグローバル変数の$_POSTである。ここでもう一度、Symfony\Component\HttpFoundation\Requestクラスの__construct()に戻ってみよう。

    public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
    {
        $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
    }

    public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
    {
        $this->request = new ParameterBag($request);
        $this->query = new ParameterBag($query);
        $this->attributes = new ParameterBag($attributes);
        $this->cookies = new ParameterBag($cookies);
        $this->files = new FileBag($files);
        $this->server = new ServerBag($server);
        $this->headers = new HeaderBag($this->server->getHeaders());

        $this->content = $content;
        $this->languages = null;
        $this->charsets = null;
        $this->encodings = null;
        $this->acceptableContentTypes = null;
        $this->pathInfo = null;
        $this->requestUri = null;
        $this->baseUrl = null;
        $this->basePath = null;
        $this->method = null;
        $this->format = null;
    }

なるほど。一度見た記述だが、上記の引数として渡される$request・ひいてはSymfony\Component\HttpFoundation\Requestクラスの$requestプロパティの実体は、スーパーグローバル変数の$_POSTであることが分かった。

createFromGlobals()の調査に戻る

少し話が逸れてしまったので、再びcreateFromGlobals()の調査に戻る。

分かったこととしては、下記の$requestには、結局Symfony\Component\HttpFoundation\Requestクラスのインスタンスが返却され、プロパティの$requestにはにはスーパーグローバル変数の$_POSTを保有している、ということだ。

    public static function createFromGlobals()
    {
        $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);

        if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
            && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
        ) {
            parse_str($request->getContent(), $data);
            $request->request = new ParameterBag($data);
        }

        return $request;
    }

$request以下の記述では、リクエストヘッダ情報を取得し(すでにスーパーグローバル変数として渡されている)、CONTENT_TYPE: application/x-www-form-urlencodedだった場合にエンコーディング処理を施すなどしている。

parse_str($request->getContent(), $data);

https://www.php.net/manual/ja/function.parse-str.php

ちなみに、リクエストメソッド情報も判定されているが、POSTメソッドは含まれていないようなので一旦スルー。

いずれにせよ、スーパーグローバル変数の情報が含まれた、Symfony\Component\HttpFoundation\Requestクラスのインスタンスが返却されることが分かったところで、Illuminate\Http\Requestのcapture()の記述に戻る。

Illuminate\Http\Request::capture()を再度調査

    public static function capture()
    {
        static::enableHttpMethodParameterOverride();

        return static::createFromBase(SymfonyRequest::createFromGlobals());
    }

さて、次はcreateFromBase()の調査である。同ファイルに定義されているので、こちらのソースコードを追ってみる。

    /**
     * Create an Illuminate request from a Symfony instance.
     *
     * @param  \Symfony\Component\HttpFoundation\Request  $request
     * @return static
     */
    public static function createFromBase(SymfonyRequest $request)
    {
        if ($request instanceof static) {
            return $request;
        }

        $newRequest = (new static)->duplicate(
            $request->query->all(), $request->request->all(), $request->attributes->all(),
            $request->cookies->all(), $request->files->all(), $request->server->all()
        );

        $newRequest->headers->replace($request->headers->all());

        $newRequest->content = $request->content;

        $newRequest->request = $newRequest->getInputSource();

        return $newRequest;
    }

着目したいのは、$newRequestが生成される箇所である。引数として渡されている、Symfony\Component\HttpFoundation\Requestインスタンス$requestが内部で使われている。

そこで、duplicate()を追ってみたところ、どうやらこれも、Symfony\Component\HttpFoundation\Requestに処理の本体が定義されているようである。

    /**
     * Clones a request and overrides some of its parameters.
     *
     * @param array $query      The GET parameters
     * @param array $request    The POST parameters
     * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
     * @param array $cookies    The COOKIE parameters
     * @param array $files      The FILES parameters
     * @param array $server     The SERVER parameters
     *
     * @return static
     */
    public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
    {
        $dup = clone $this;
        if (null !== $query) {
            $dup->query = new ParameterBag($query);
        }
        if (null !== $request) {
            $dup->request = new ParameterBag($request);
        }
        if (null !== $attributes) {
            $dup->attributes = new ParameterBag($attributes);
        }
        if (null !== $cookies) {
            $dup->cookies = new ParameterBag($cookies);
        }
        if (null !== $files) {
            $dup->files = new FileBag($files);
        }
        if (null !== $server) {
            $dup->server = new ServerBag($server);
            $dup->headers = new HeaderBag($dup->server->getHeaders());
        }
        $dup->languages = null;
        $dup->charsets = null;
        $dup->encodings = null;
        $dup->acceptableContentTypes = null;
        $dup->pathInfo = null;
        $dup->requestUri = null;
        $dup->baseUrl = null;
        $dup->basePath = null;
        $dup->method = null;
        $dup->format = null;

        if (!$dup->get('_format') && $this->get('_format')) {
            $dup->attributes->set('_format', $this->get('_format'));
        }

        if (!$dup->getRequestFormat(null)) {
            $dup->setRequestFormat($this->getRequestFormat(null));
        }

        return $dup;
    }

処理としては、各種情報をnew ParameterBag($request);などに置き換えているようだ。

ここまで追ってきたが、どうやらnew ParameterBag($request);が、Requestオブジェクトを生成している本体らしいことが見えてきた。

最後に、上記を調査する。

ParameterBagを追ってみる

調査中

認証機能を実装した際のステータスコードについて

認証機能は色々あるが、ここでは単純なパスワード認証を考える。

Webページのログイン画面から、メールアドレスとパスワードを入力して、該当サイトにログインしたいとする。

この時、認証に失敗した際には、Webページに「パスワードが違います」というメッセージを表示するだけでなく、返却するステータスコードの側でも、ステータスコード401番を返却しなければならない。

また、どのリクエストに対して401番のステータスコードを付与するかだが、これは認証情報を保持し、POSTメソッドにて送信されているリクエストが好ましい。

上記のリクエストに401を付与した上で、再度画面表示を行うGETメソッドのリクエストに対して、ステータスコード200番を付与してやれば良い。

<img src="">属性によるリクエストについて

画像を表示するとき、属性を指定することにより、記述したパスに該当する画像ファイルがページ上に存在する。

この時、画像に関しては別途、HTTPリクエストが送られ、画像が表示される。

HTMLファイルとしては、同一ファイル内に記述されているのだが、リクエストは画像ごとに別々でリクエストが送信される。

リクエストが送信される過程としては、WebブラウザがHTMLを解析する中でimgタグを認識し、srcタグで指定されたパスでリクエストが送信される。

パスは、絶対パスでも相対パスでも動作する。

ただし、相対パスで指定する場合、http(s)://〜のホスト部ドメイン部分は明示的に省略しなければいけない。

リクエストを切り分けることで生じる事象

例えば、画像ファイルの指定パスを誤って記述した場合。

この場合にも、HTML部分についてはブラウザに正常表示され、画像の部分だけ、ステータスコード404が返却され表示されないということになる。

また、alt属性を指定していると、画像の取得に失敗した場合に代替テキストが表示される。(ただしこの場合においても、画像ファイルを取得するリクエストのステータスコードは404である。

画像の送信を含む際のHTTP通信の接続について

HTTP通信では、リクエストのたびに接続を確率し、レスポンスが返却されれば接続を切ることを繰り返している。

たとえば、画像ファイルを多く含むWebページの場合、上記を繰り返すため、非常に冗長に思えるかもしれない。

しかし、HTTP1.1においては、リクエストヘッダに下記が付与されることによって、レスポンスを返した後に接続を維持し、次のリクエストを送るときは、保持している接続をそのまま再利用することができるようになっている。

Connection: keep-alive

これによって、画像ファイルにおけるやりとりが頻発しても、1つの接続内で行うことができるようになっている。

Webの脆弱性についてまとめる

Webの脆弱性について、まとめてみます。 こちらも勉強中のため、随時更新。

クロスサイトスクリプティングXSS脆弱性

  • 主に、エンドユーザーから悪意のある入力値を送信される・悪意のあるスクリプトを実行されるなどの脆弱性
  • 対策としては、フォームからの入力値にエスケープ処理を施すなどが必要。

SQLインジェクション

  • SQL命令に不正なパラメータが渡されることによって、意図しないデータ操作が行われてしまうこと。
  • データ操作はライブラリ経由で行うこと。またそもそも、外部からの入力値をそのまま使ってSQLを構築しないこと。

OSコマンドインジェクション

  • OSコマンドに対して、外部からの入力値などにより、不正な命令などがコマンド上で実行されること。
  • そもそも、スクリプトからコマンド操作を行う命令(関数)を使うべきではない

クロスサイトリクエストフォージェリCSRF

  • 例えばフォーム送信などで、クライアントユーザーが意図しないうちに、入力値が送信されたりする脆弱性
  • 「ユーザーが意図しない操作」

対策

  • ワンタイムトークン方式を用いる
  • あらかじめ、任意のトークン値を作成しておき、これを正規のフォームにhidden属性として埋め込んでおく。
<input type="hidden" name="token" value="{{ トークン値 }}">
  • 一方、上記のトークン値をセッション値としても持たせておく。
  • フォーム送信時、送信先において「トークンが送信されているか」「送信されたトークン値は正しいか」を判定し、これらに該当しない場合は不正な送信として、403とかで弾く。
<?php
session_start();
$token = $_POST['token'];

if (! isset($token)) {
    http_response_code(403);
}

if ($token !== $_SESSION['token']) {
    http_response_code(403);
}
?>

パストラバーサル脆弱性

  • 入力値などからファイルのパスを受け取った際、パスを遡られるなどして、ファイルの情報を書き換えられるなどの脆弱性
  • 対策としては、リクエスト情報として直接パスの受け渡しをしないこと

メールヘッダインジェクション

対策

  • 特にメールの(送受信先の)アドレス部分に関しては、入力値をそのまま使わないこと
  • 必ず、正規表現などによって、意図するアドレスの場合にのみ送信されるようにする。
  • その他、inputタグの隠しフィールドとして、アドレス情報を持たせておくのも避けるべき(inputタグは簡単に書き換えられるから)

ファイルアップロード攻撃

  • 不正なファイルがアップロードされ、実行されてしまう脆弱性
  • 対策としては、ファイルアップロード時に、アップロードできるファイルの種類を限定的にするなど。

全体感

全体として、リクエスト情報全般について、妥当性を検証する必要がある。 リクエスト情報というと、フォームからの入力値などを想像しやすいが、それだけではない。 セッション情報やサーバー情報、HTTPリクエストヘッダなどからも、不正な値が送信されないかチェックする必要がある。

要は「バリデーションを強める」ということなのだが、下記などはおろそかになりやすい。

  • 文字エンコーディングは意図したものか(XSS脆弱性の原因となる)
  • nullバイトはチェックされているか
  • 必須検証は適切か(isset()は空文字列であってもtrueを返すので不適切)
  • 数値検証は適切か(is_int()だと、スーパーグローバル変数を適切にバリデートできない)

以下、コード例にて検証してみる(PHP

「チーズはどこへ消えた?」を読んで、感じたことをまとめる

チーズはどこへ消えた?」という有名な本がある。 これを先日買って、今読んでいるところだ。 ページ数は少なく、サクサク読めるが、書かれている内容は深く、考えされられる点も多い。 ここでは、感じたこと・考えたことなどを雑多にメモしていく。

まずは変化を知ること

まずは変化を知ること。「変化する」という事象を悟ること。これが重要だと思った。 僕たちは、物事を不変なものだと思いがちだ。 特に、置かれている状況が幸福なものであれば、その傾向は強くなる。

  • 上手くいっている現状は当たり前だ
  • 今の状況が直線上に続いていく

こうしたことを考える。

だが、周囲は常に変化に晒されている。 世界は常に変わり続けている。 これは事実だし「変わり続ける」ということだけに関しては、この先も不変だろう。 とすれば「変化は起きるもの」と思うことがまずは重要だ。 その上で、今起きている変化を知る努力をすること。 現在起きている変化を正確に知ることは相当難しいだろう。 完璧な予測は、コンピュータでもできない。 だからこそ、まずはできることから始める。 「知ろう」とする。

一番最悪なのは、未来に一切の希望を持てず、何もしないこと

未来に一切の希望がないのが、一番最悪だ。 自分の力ではどうすることもできない、ということだ。 未来に希望がないと、行動する意味もないから、何もできなくなる。 だけど、何もしないのだから、自体が好転するはずもない。

未来には、本当に希望がないのか? 行動するのが怖いから、「未来に希望はない」と思い込んでいるだけではないのだろうか。 その見極めが重要だ。 そして、ほとんどの場合において、未来に希望がないとする原因は後者だ。 行動するのが怖いから、思い込むのだ。

「行動するのが怖い」を乗り越えるには

とにかくやってみることだ。 やってみる。行動してみる。 すると、心に抱いていた恐怖の大半が、幻想だったことに気付かされる。

「何もしない」 < 行動して失敗する

ということが分かる。 分かることで、さらに行動できるようになる。 行動することで、成功する確率を高めることもできる。

未来(=チーズ)を作る必要性

未来に希望がないのが最悪だ、と言った。 そして、そのほとんどは、恐怖による思い込みだと書いた。 だが、本当に未来がないのであれば、どうしようもない。 たとえ恐怖がなくても、想像しうる良い未来が無いのであれば、結局は行動することができない。

だから、思い描くことのできる未来(=チーズ)を作ることが必要だ。 それは、状況を好転させるための「ビジョン」という言葉に置き換えることもできる。 ではどうやって、ビジョン(=チーズ)を持つことができるのだろうか?

物語における、未来(=チーズ)を抱くまでの過程

実は、物語の中では、確証を持って「チーズはある!」という未来を描いていない。 「チームはないのかもしれない」という不安と、「チーズを見つけた時」の期待感とのせめぎ合いの中で、葛藤しながら行動する様子が描かれている。 しかし、次第に行動自体に意味や喜びを見出していく。 「チーズがないままでいるより、迷路に出て探した方が安全だ」 という結論に至り、行動という道に進んでいくのだ。

これは、現実世界にも当てはまるのかもしれない。 もの物語に沿うと、確証性のある未来(=チーズ)を最初から抱く必要性はないように思える。 それよりも、とにかく今いる場所から一歩踏み出すことの方が、重要に思える。 その、ぼんやりとした未来(=チーズ)に向かうまでの過程を、楽しむことができれば最高だ。

まとめ「変化に適応するには」

  • 「変化するのは当たり前」と思うこと。その上で、変化に対するアンテナを張っておくこと
  • 実際に変化が起きれば、それに合わせて既存の形を変えること。
  • その際に生まれる恐怖心を見つめること。「それは実体を伴った恐怖なのか?」
  • その上で、行動すること