New Class of Vulnerability in Perl Web Applicationsざっくりというと以下の様な内容です
- CGI.pmのparamメソッドの返り値をハッシュに突っ込んでいる箇所がある
- クエリ文字列に同名のパラメータを複数セットすると、配列値が返る
- 配列をハッシュに突っ込むことにより、別のキーの値が変更される
PoC
元エントリにもPoCが出ておりますが、少し手をいれたものを以下に示します。
#!/usr/bin/perl
use strict;
use CGI;
my $cgi = new CGI;
my $loginname = 'smith';
my $password = 'a3k!sz9';
my %user = ('login' => $loginname,
'realname' => $cgi->param('realname'),
'password' => $password);
print <<END;
Content-Type: text/plain; charset=utf-8
login = $user{'login'}
realname = $user{'realname'}
password = $user{'password'}
END
ご覧のように、キーとしてlogin、realname、passwordを取るハッシュを作成しています。loginとpasswordは定数(セッション変数などから取得する想定)、realnameはクエリ文字列realnameから取得します。このCGIプログラムをクエリ文字列 realname=John+Smith で呼び出すと結果は下記となります。
次に、以下のクエリ文字列で呼び出します。login = smith realname = John Smith password = a3k!sz9
結果は以下となります。realname=hoge&realname=login&realname=yamada
login = yamada
realname = hoge
password = a3k!sz9
なんと、定数で指定しているはずのloginがyamadaに化けています。なぜでしょうか?CGI.pmは同名パラメータ指定により配列を受け取ることができる
CGI.pmのparamメソッドは、以下のようにすると、配列の形でパラメータを受け取ることができます。my @foo = $cgi->param('foo');例えば、以下のクエリ文字列を指定した場合、
foo=1&foo=2&foo=3@fooは 1, 2, 3を値にもつ配列となります。
ハッシュ生成時に配列を混ぜることでハッシュの別キーをインジェクションできる
PoCの%user の部分は以下のように呼び出されることになります。Perlの場合、=>とカンマ(,)はほぼ同じ意味だそうで、上記は以下のように展開されます。私はPerlの細かい文法には自信が無いため、以下の部分の説明に誤りがあればご指摘ください。my %user = ('login' => 'smith', 'realname' => ('hoge', 'login', 'yamada'), 'password' => 'a3!sz9');
整形すると、以下と同等です。my %user = ('login', 'smith', 'realname', 'hoge', 'login', 'yamada', 'password', 'a3!sz9');
右辺は配列定義ですが、'login' => が2箇所あります。したがって、配列をハッシュに変換する際に、キーloginに対する値は、'yamada'に上書きされます。すなわち、以下と同等です。my %user = ('login' => 'smith', 'realname' => 'hoge', 'login' => 'yamada', 'password' => 'a3!sz9');
以上の手順により、$user{'login'}が本来smithであるところ、外部からの入力により、yamadaに変更させられました。my %user = ('realname' => 'hoge', 'login' => 'yamada', 'password' => 'a3!sz9');
このようなケースはありえるのか?
上記のPoCのようなことが現実にあり得るかというと、ちょっと微妙な気はするものの、ないこともないかなという印象です。たとえば、Web APIなどで、入力はurlencodedだけど出力はJSONであって、そのJSONを作る際にいきなりハッシュ定義に入力値を放り込んでいるケースなどです。
元記事のコメント欄には、ふつーバリデーションするだろというツッコミに対して、著者は、realnameは任意の文字を受け取るのでバリデーションは必要ないと答えています。しかし、制御文字などは弾く必要があるため、アプリケーション要件としても、最低限度のバリデーションは必要と考えます。
対策
この問題を避ける簡便な方法として、元エントリではrealnameの値をスカラーに変換する方法が紹介されています。あるいは、値をいったんスカラー変数で受けるという方法があります。これにより、realnameがスカラであることが保証されます。my %user = ('login' => $loginname, 'realname' => scalar $cgi->param('realname'), 'password' => $password);
my $realname = $cgi->param('realname'); my %user = ('login' => $loginname, 'realname' => $realname, 'password' => $password);
まとめ
前回のエントリでも説明したように、スカラ値を想定している入力が、実は配列やハッシュになっている可能性を想定して、スカラであることのチェック、あるいはスカラに変換する等の処理が必要な場合があります。入力値のバリデーションをすれば脆弱性が顕在化することはありませんが、脆弱性が混入するその箇所で対策することを考えると、ハッシュに突っ込む箇所で、当該変数がスカラーであることが一目瞭然になっているのがよいでしょう。
そういう意味で、バリデーションによる対策をとらず、脆弱性の発生箇所での対策を主張されている元エントリの著者Gerv(Gervase Markham)さんとは、一緒に旨い酒が飲めそうだと感じましたw