2014年10月25日土曜日

New Class of Vulnerability in Perl Web Applicationsの紹介

Redditを眺めておりましたら以下の記事が目に止まりました。
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 の部分は以下のように呼び出されることになります。
my %user = ('login' => 'smith',
            'realname' => ('hoge', 'login', 'yamada'),
            'password' => 'a3!sz9');
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 = ('realname' => 'hoge', 
            'login' => 'yamada',
            'password' => 'a3!sz9');
以上の手順により、$user{'login'}が本来smithであるところ、外部からの入力により、yamadaに変更させられました。

このようなケースはありえるのか?

上記のPoCのようなことが現実にあり得るかというと、ちょっと微妙な気はするものの、ないこともないかなという印象です。
たとえば、Web APIなどで、入力はurlencodedだけど出力はJSONであって、そのJSONを作る際にいきなりハッシュ定義に入力値を放り込んでいるケースなどです。
元記事のコメント欄には、ふつーバリデーションするだろというツッコミに対して、著者は、realnameは任意の文字を受け取るのでバリデーションは必要ないと答えています。しかし、制御文字などは弾く必要があるため、アプリケーション要件としても、最低限度のバリデーションは必要と考えます。

対策

この問題を避ける簡便な方法として、元エントリではrealnameの値をスカラーに変換する方法が紹介されています。
my %user = ('login' => $loginname,
            'realname' => scalar $cgi->param('realname'),
            'password' => $password);
あるいは、値をいったんスカラー変数で受けるという方法があります。これにより、realnameがスカラであることが保証されます。
my $realname = $cgi->param('realname');
my %user = ('login' => $loginname,
            'realname' => $realname,
            'password' => $password);

まとめ

前回のエントリでも説明したように、スカラ値を想定している入力が、実は配列やハッシュになっている可能性を想定して、スカラであることのチェック、あるいはスカラに変換する等の処理が必要な場合があります。
入力値のバリデーションをすれば脆弱性が顕在化することはありませんが、脆弱性が混入するその箇所で対策することを考えると、ハッシュに突っ込む箇所で、当該変数がスカラーであることが一目瞭然になっているのがよいでしょう。
そういう意味で、バリデーションによる対策をとらず、脆弱性の発生箇所での対策を主張されている元エントリの著者Gerv(Gervase Markham)さんとは、一緒に旨い酒が飲めそうだと感じましたw

0 件のコメント:

コメントを投稿

フォロワー