My Photo

« January 2014 | Main | March 2014 »

February 24, 2014

ファイルダウンロードによるXSS その1

発生箇所 ファイルのアップロード機能、ダウンロード機能
影響を受けるページ アプリケーション全体。特にセッション管理や認証のあるページは影響が大きい
影響の種類 成りすまし
利用者の関与の度合い 必要。リンクのクリックなど

概要

アップロードしたファイルを利用者がダウンロードする際に、ブラウザがファイルタイプを誤認する場合がある。例えばアプリケーションがPNG画像を想定しているにもかかわらず画像のデータ中にHTMLタグが含まれていると、条件によってはブラウザがHTMLファいつと誤認して、画像ファイルに埋め込まれたらJavaScriptを実行する場合がある。これがファイルダウンロードによるXSSである。
この脆弱性を悪用する攻撃者はHTMLやJavaScriptを仕込んだ画像ファイルやPDFファイルをアップロードして公開する。このファイルは通常の参照の仕方ではHTMLとは認識されないが、攻撃者がアプリケーション利用者に罠を仕掛けてアップロードしたファイルがHTMLとして認識されるように仕向ける。ブラウザがこのファイルをHTMLとして認識するとXSS攻撃が成立する。

攻撃手法

攻撃手法を2例示す。いずれもIE以外のブラウザでは必ずしも再現しない。

●画像ファイルによるXSS

画像ファイルに偽装したファイルにHTMLやJavaScriptを仕込んだものをアップロードする方法によってXSS攻撃が可能となる場合がある。画像によるXSSはIE8以降では対策されている。
アップロードファイルによるサーバ側スクリプト実行の記事のスクリプト実行対策版アップローダでsample.pngというファイルをアップロードする。このファイルは拡張子はpngだが内容は以下のスクリプトである。

<script>alert('XSS');</script>

アップロード完了の画面において、img要素の部分は×印となる。リンクをクリックするとIE7では上記のJavaScriptが実行される。IE8ではスクリプトがテキストとして表示される。
実際の攻撃では攻撃用のJavaScriptを仕込んだファイルを画像としてアップロードした上で、その画像を表示するURLを罠サイトに仕込む。ただしimg要素で画像として表示してもJavaScriptは実行されないので、iframe要素などを用いてHTMLとして表示させる。

画像ファイルによるXSSの影響は通常のXSSと同じである。すなわちクッキー値の盗み出しによる成りすまし、Web機能の悪用、画像改変によるフィッシングなどが可能である。

●PDFダウンロード画面によるXSS

次の例はPDFのようなアプリケーションファイルのダウンロードサービスを想定している。ストレージサービスを簡略化したものである。
ファイルをアップロードする画面はアップロードファイルによるサーバ側スクリプト実行の記事のものからform要素のactionだけを変更したものを使用する。
同様にファイルを受け付けるスクリプトとダウンロードスクリプトは受け付けるファイルの拡張子をpdfに変更したものを使用する。
以下にファイルを受け付けるスクリプトupload2.php(抜粋)とダウンロードスクリプトdownload2.phpである。ダウンロードスクリプトでは拡張子のチェック以外に3行目のMIMEタイプの指定部分も変更しており、PDFのContent-Typeとしては不正な値をセットしている

<?php
define('UPLOADPATH', '/var/upload');

function get_upload_file_name($tofile) {
  // 拡張子のチェック
  $info = pathinfo($tofile);
  $ext = strtolower($info['extension']);
  if ($ext != 'pdf') {
    die('拡張子はpdfを指定ください');
  }
// 中略
$imgurl = 'download2.php?file=' . basename($tofile);
?>
<body>
<a href="<?php echo htmlspecialchars($imgurl); ?>"><?php
 echo htmlspecialchars($orgfile, ENT_NOQUOTES, 'UTF-8'); ?>
をアップロードしました</a><br>
</body>
<?php
define('UPLOADPATH', '/var/upload');
$mimes = array('pdf' => 'application/x-pdf');

$file = $_GET['file'];
$info = pathinfo($file);       // ファイル情報の取得
$ext = strtolower($info['extension']);     // 拡張子
$content_type = $mimes[$ext]; // Content-Typeの取得
if (! $content_type) {
  die('拡張子はpdfを指定ください');
}
header('Content-Type: ' . $content_type);
readfile(UPLOADPATH . '/' . basename($file));
?>

正常なPDFファイルをアップロードするとリンクが表示され、そのリンクをクリックするとPDFファイルをダウンロードできる(ファイル名の拡張子は.pdfにならないがダウンロードされるファイルの中身はPDFである)。

攻撃の場合、例として以下のようにscript要素のみからなるHTMLファイルpdfという拡張子を持つファイル名にしてアップロードする。

<script>alert('XSS');</script>

この偽装PDFファイルをアップロードすると、正常なPDFファイルをアップロードした時と同様にリンクが表示される。リンクをクリックするとファイルダウンロードのダイアログが表示される。
ここからは、攻撃者の視点で罠のためのURLを作成する手順を説明する。ダウンロード用リンクをマウスで右クリックしてコンテキストメニューを表示し、「ショートカットのコピー」を選択する。コピーした内容は以下。

http://example.jp/download2.php?file=<ランダムな文字列>.pdf

これに下記のように「/a.html」という文字列を挿入する。挿入した文字列はPATHINFOと呼ばれるもので、見かけ上はファイル名のような形でパラメータを埋め込む方法である。a.htmlというファイルは存在しないので、パラメータとしてスクリプトに渡される。
「/a.html」を挿入した意味については次回の記事の「脆弱性の原因」で説明する。

http://example.jp/download2.php/a.html?file=<ランダムな文字列>.pdf

このURLをIEのアドレスバーに入力してEnterキーを押下するとJavaScriptが実行される。
偽装画像の場合と異なりIE8でもJavaScriptが実行される(この記事を書いている時点で最新のIE11ではJavaScriptは実行されず、PDFの中身がテキストとして表示される。

この脆弱性の根本原因はContent-Typeの間違いである。PDFの正しいContent-Typeは「application/pdf」だが、「application/x-pdf」と不正なContent-Typeを指定したことが直接的な原因である。

参考文献:体系的に学ぶWebアプリケーションの作り方 4.12.3 ファイルダウンロードによるクロスサイト・スクリプティング

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践
徳丸 浩

ソフトバンククリエイティブ 2011-03-03
売り上げランキング : 4070

Amazonで詳しく見る
by G-Tools

PATHINFO(PATH_INFO)について参考にしたサイト:
環境変数PATH_INFOとは? - 苔の一念岩をも通す ~目指せいつかはプログラマ~
PATH_INFOって何? - よくきたWiki
PHP: $_SERVER - Manual

February 22, 2014

とんでもない地図の本

本屋でとんでもない本を見つけた。
東京の地図の本なのだが、パラパラとめくっていたら羽田空港近辺のページを見て目を疑った。廃止されてすでに存在しない施設が描いてある。そしてその跡地に作られた道路が描かれていない。その道路は別に出来たばかりの道という訳ではない。
何年も前に出版された本なら分かるが、その本はタイトルに「2014年版」という言葉が入っている。この出版社、かなり前に買った地図データを更新せずにそのまま使って「2014年版」と銘打って売ってるのか。とんでもないな。
やっぱ地図はマップルとかミリオンとか、あるいはJAFが出しているやつとか実績あるのを選ぶべきだなと思った。

February 20, 2014

アップロードファイルによるサーバ側スクリプト実行

発生箇所 ファイルのアップロード機能を提供するページ
影響を受けるページ すべてのページが脆弱性の影響を受ける
影響の種類 秘密情報の漏洩、データの改ざん・削除、外部へのDoS攻撃、システムの停止など
利用者の関与の度合い 不要

概要

アップローダの中には利用者がアップロードしたファイルをWebサーバの公開ディレクトリに保存するものがある。加えてファイル名の拡張子として php, asp, aspx, jsp などスクリプトを表す拡張子が指定できると、アップロードしたファイルをスクリプトとしてWebサーバ上で実行できます。
外部から送り込まれたスクリプトが実行されると、OSコマンド・インジェクションと同様の影響があります。

攻撃手法

例として利用者が画像ファイルをアップロードし、それを公開する機能を考える。
下記はアップロードする画面である(抜粋)。form要素のenctype属性の値"multipart/form-data"によりフォームデータの送信方式を指定しており、ファイルのアップロードが可能となる。

<form action="upload.php" method="post" enctype="multipart/form-data">
ファイル:<input type="file" name="imgfile" size="20"><br>
<input type="submit" value="アップロード">
</form>

下記はファイルを受け取って決まったディレクトリに保存した上で画面に表示するスクリプト(upload.php)である。

<?php
$tmpfile = $_FILES['imgfile']['tmp_name'];	// 一時ファイル名
$tofile = $_FILES['imgfile']['name'];	// 元ファイル名

if (!is_uploaded_file($tmpfile)) {	// ファイルがアップロードされているか
	die('ファイルがアップロードされていません');
} else if (!move_uploaded_file($tmpfile, 'img/' . $tofile)) {	// 画像を移動
	die('ファイルをアップロードできません');
}
$imgurl = 'img/' . urlencode($tofile);
?>
<body>
<a href="<?php echo htmlspecialchars($imgurl); ?>"><?php echo htmlspecialchars($tofile, ENT_NOQUOTES, 'UTF-8'); ?></a>
をアップロードしました。<br>
<img src="<?php echo htmlspecialchars($imgurl); ?>">
</body>

この機能を使用して画像ファイルをアップロードすれば画像が表示される。

攻撃では画像ファイルの変わりにスクリプトをアップロードする。例として以下のPHPスクリプトをアップロードする。

<pre>
<?php
system('/bin/cat /etc/passwd');
?>
</pre>

すると、アップロードされたファイルは画像ではないがimg要素の部分にIneternetExplorerの場合、×マークが表示される。
リンクをクリックするとスクリプトが実行されて /etc/passwd が表示される。

アップロードファイルによるサーバ側スクリプト実行による影響は、OSコマンド・インジェクションと同じである。PHPスクリプトが動作するOSアカウントで実行可能な昨日すべて悪用可能である。

脆弱性の原因

脆弱性が生まれる条件は、以下の両方に該当することである。

  • アップロードしたファイルを公開ディレクトリに保存する。
  • アップロード後のファイル名として "php", "asp" などスクリプトであることを示す拡張子を指定できる。

対策

アップロードされたファイルを公開ディレクトリに保存しないようにする。その場合、ダウンロードスクリプトを使用する。
下記はダウンロードスクリプトを利用する形で upload.php を修正したものである。

<?php
define ('UPLOADPATH', '/var/upload');

function get_upload_file_name($tofile) {
	// 拡張子のチェック
	$info = pathinfo($tofile);
	$ext = strtolower($info['extension']);	// 拡張子(小文字に統一)
	if ($ext != 'gif' && $ext != 'jpg' && $ext != 'png') {
		die('拡張子gif、jpg、pngのいずれかを指定ください');
	}
	// 以下、ユニークなファイル名の生成
	$count = 0;	// ファイル名再生試行の回数
	do {
		// ファイル名の組み立て
		$file = sprintf('%s/%08x.%s', UPLOADPATH, mt_rand(), $ext);
		// ファイルを作成する。既存の場合はエラーになる
		$fp = @fopen($file, 'x');
	} while ($fp === FALSE && ++$count < 10);
	if ($fp === FALSE) {
		die('ファイルが作成できません');
	}
	fclose($fp);
	return $file;
}

$tmpfile = $_FILES["imgfile"]["tmp_name"];
$orgfile = $_FILES["imgfile"]["name"];
if (!is_uploaded_file($orgfile)) {
	die('ファイルがアップロードされていません');
}
$tofile = get_upload_file_name($orgfile);
if (!move_uploaded_file($tmpfile, $tofile)) {
	die('ファイルがアップロードできません');
}
$imgurl = 'download.php?file=' . basename($tofile);
?>
<body>
<a href="<?php echo htmlspecialchars($imgurl); ?>"><?php
 echo htmlspecialchars($orgfile, ENT_NOQUOTES, 'UTF-8'); ?></a>
をアップロードしました<BR>
<img src="<?php echo htmlspecialchars($imgurl); ?>">
</body>

修正点は
・ファイルの格納先を公開ディレクトリから get_upload_file_name() が返すファイル名に変更したこと
・画像のURLをダウンロードスクリプト download.php 経由にしたこと
である。
get_upload_file_name() は拡張子をチェックし、次にユニークなファイル名を生成し、ファイルを作成してクローズし、削除せずにファイルのパスを返す。
その後、move_uploaded_file() によりアップロードされたファイルで返されたパスのファイルを上書きする。もし get_upload_file_name() で作成したファイルを消してしまうとファイル名の一意性が保障されなくなる。

次にダウンロードスクリプト download.php を以下に示す。

<?php
// 注意:このダウンロードスクリプトにはクロスサイト・スクリプティング脆弱性があります。

define('UPLOADPATH', '/var/upload');
$mimes = array('jpg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif');

$file = $_GET['file'];
$info = pathinfo($file);		// ファイル情報の取得
$ext = strtolower($info['extension']);	// 拡張子
$content_type = $mimes[$ext];	// Content-Typeの取得
if (!$content_type) {
  die('拡張子はpng、gif、jpgのいずれかを指定ください');
}
header('Content-Type: ' . $content_type);
readfile(UPLOADPATH . '/' . basename($file));
?>

このスクリプトはクエリー文字列fileでファイル名を指定する。まず拡張子をチェックし、その後それぞれの拡張子に対応したContent-Typeを出力し、ファイル本体をreadfile()で出力する。
ファイル名をbasename()に通しているのはディレクトリ・トラバーサル脆弱性対策である。

脆弱性発生の原因にアップロードファイル名にスクリプトを示す拡張を指定できることがあったが、それに対する確実な対策は容易ではない。どの拡張子がスクリプトを示すのかは自明ではないためである。
よって、脆弱性の対策としては上記に示したアップロードファイルを公開ディレクトリに保存しない方法を用いる。

参考文献:体系的に学ぶWebアプリケーションの作り方 4.12.2 アップロードファイルによるサーバー側スクリプト実行

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践
徳丸 浩

ソフトバンククリエイティブ 2011-03-03
売り上げランキング : 4070

Amazonで詳しく見る
by G-Tools

February 16, 2014

PL/pgSQL

PL/pgSQLはPostgreSQLで使える手続き型言語である。OracleにおけるPL/SQLに当たる。PL/pgSQLはPL/SQLを参考に実装したらしい。
PL/pgSQLの構造は下記のようなブロック構造になっている。

[ <<label>> ]
[ DECLARE
    declarations ]
BEGIN
    statements
END [ label ];

コメントは2種類ある
・"--" からその行の終わりまで。
・"/*" で始まり "*/" で終わる。

例1:2012/01/01~2013/01/31までの日付をINSERTする。値は何も返さない。

CREATE OR REPLACE FUNCTION test_func_01()
RETURNS void AS '
DECLARE
  tmp date;
BEGIN
  tmp := ''2012-01-01''::date;
  FOR i IN 1..731 LOOP
    INSERT INTO t_result (id, type, date) VALUES (i, ''A'', tmp);
    tmp := tmp + interval ''1 day'';
  END LOOP;
END
' LANGUAGE plpgsql;
実行結果
dbtest=> SELECT test_func_01();
 test_func_01
--------------

(1 行)

dbtest=> SELECT * FROM t_result ORDER BY id;
 id  | type |        date
-----+------+---------------------
   1 | A    | 2012-01-01 00:00:00
   2 | A    | 2012-01-02 00:00:00
…
 730 | A    | 2013-12-30 00:00:00
 731 | A    | 2013-12-31 00:00:00
(731 行)

dbtest=>

例2:SELECTを3回実行し、その結果をまとめて返す。

CREATE OR REPLACE FUNCTION test_func_02(OUT a integer, OUT b char(1), OUT c timestamp)
RETURNS SETOF RECORD AS '
BEGIN
  FOR i IN 1..3 LOOP
    SELECT * from t_result WHERE id = i INTO a, b, c;
    RETURN NEXT;
  END LOOP;
  RETURN;
END
' LANGUAGE plpgsql;
実行結果
dbtest=> SELECT * FROM test_func_02();
 a | b |          c
---+---+---------------------
 1 | A | 2012-01-01 00:00:00
 2 | A | 2012-01-02 00:00:00
 3 | A | 2012-01-03 00:00:00
(3 行)

dbtest=>

例3:カーソルを使う。

CREATE OR REPLACE FUNCTION test_func_03(OUT a timestamp, OUT dummy integer)
RETURNS SETOF RECORD AS '
DECLARE
  cur CURSOR FOR SELECT * FROM t_result ORDER BY id;
  r t_result;
BEGIN
  OPEN cur;
  LOOP
    FETCH cur INTO r;
    SELECT r.date INTO a;
    RETURN NEXT;
    IF r.id = 3 THEN
      EXIT;
    END IF;
  END LOOP;
  RETURN;
END
' LANGUAGE plpgsql;
実行結果
dbtest=> SELECT * FROM test_func_03();
          a          | dummy
---------------------+-------
 2012-01-01 00:00:00 |
 2012-01-02 00:00:00 |
 2012-01-03 00:00:00 |
(3 行)

dbtest=>

複数のクエリーの結果を返す場合で、よくわからないな現象があった。
2列を返す下記の関数は特に問題なく定義できて実行もできた。

CREATE OR REPLACE FUNCTION test1(OUT a integer, OUT b integer)
RETURNS SETOF RECORD AS '
BEGIN
  FOR i IN 1..10 LOOP
    SELECT i, i+1 INTO a, b;
    RETURN NEXT;
  END LOOP;
  RETURN;
END
' LANGUAGE plpgsql;

しかし、返す値を1列にすると定義自体がエラーになる。

CREATE OR REPLACE FUNCTION test2(OUT a integer)
RETURNS SETOF RECORD AS '
BEGIN
  FOR i IN 1..10 LOOP
    SELECT i INTO a;
    RETURN NEXT;
  END LOOP;
  RETURN;
END
' LANGUAGE plpgsql;

エラーメッセージは以下。

ERROR:  OUTパラメータのため、関数の戻り値型はintegerでなければなりません。

"SELECT i" の i に "::integer" と付けても同じエラー。意味がわからない。
例3のOUTパラメータdummyはこのエラーを避けるために付けた。

参考サイト:
PostgreSQL 8.4.4文書 第38章 PL/pgSQL - SQL手続き言語
複数行複数列を返すPL/pgSQL関数 - iakioの日記 - postgresqlグループ
PostgreSQL初心者の悪戦苦闘ぶりをメモする pl/pgsql篇 | 読書と技術となんか色々のログ - 楽天ブログ

February 15, 2014

PostgreSQLで自作の関数を定義する

PostgreSQLの関数は CREATE FUNCTION で定義する。
以下、例を示す。例2、例3については説明どおりに動くか自信はないが^^;

例1:加算
IMMUTABLE はデータベースに変更をしないことを示す。
RETURNS NULL ON NULL INPUT は引数にNULLがある場合NULLを返すことを示す。

CREATE FUNCTION add(integer, integer) RETURNS integer
    AS 'select $1 + $2;'
    LANGUAGE SQL
    IMMUTABLE
    RETURNS NULL ON NULL INPUT;

例2:指定された日付の月の日数を返す関数
double precision は倍精度浮動小数点。extract() の返り値がこの型なのでこの関数の返り値の型も double precision にしている。
処理の部分はBEGIN~END で処理を記述している。これはSQLではなくPL/pgSQLなので、LANGUAGEは plpgsql としている。
PL/pgSQLについては次の記事を参照。

CREATE OR REPLACE FUNCTION get_month_last_day(date) RETURNS double precision AS '
BEGIN
  RETURN extract(DAY FROM date_trunc(''month'', $1 + interval ''1 month'') - interval ''1 day'');
END;
' LANGUAGE plpgsql;

例3:指定された日付が月の最終日かどうかを返す関数

CREATE OR REPLACE FUNCTION is_month_last_day(date) RETURNS boolean AS '
BEGIN
  RETURN date_trunc(''day'', $1) = date_trunc(''month'', $1 + interval ''1 month'') - interval ''1 day'';
END;
' LANGUAGE plpgsql;

参考サイト: PostgreSQL 8.4.4文書 Ⅳ.リファレンス Ⅰ.SQLコマンド CREATE FUNCTION

PostgreSQLのPREPARE

PREPAREは事前に準備されたSQL文を作成する。SQLの一時的な関数化と考えることができる。
準備されたSQL文はセッションが切れるか割り当てを解除するまで有効である。

PREPARE name [ ( datatype [, ...] ) ] AS statement

下記の例は指定した日付以降のレコードを取得するSQLをPREPAREで準備する。

PREPARE fooplan(date) AS
  SELECT * FROM sales WHERE date >= $1;

実行には EXCEUTE を使用する。

EXECUTE fooplan('2012-10-02'::date);

準備されたSQLを破棄するには DEALLOCATE を使用する。

DEALLOCATE fooplan;

参考サイト:
PostgreSQL 8.4.4文書 Ⅳ.リファレンス Ⅰ.SQLコマンド PREPARE
Studio ODIN - blog風小ネタ集 > PostgreSQL の PREPARE

February 14, 2014

共通表式(共通テーブル式)

共通表式はSQL:1999で標準化された機能である。PostgreSQLの文書では「その問い合わせのみに存在する一時テーブルを定義するもの」とある。

以下のようなテーブルを考える。
supは供給業者、areaは地域、amountは売り上げ金額である。

supareaamount
A東京70
B東京30
C東京80
D東京20
E大阪83
F大阪36
G大阪29
H大阪52

各業者について、地域、売り上げ金額に加えて地域における金額ベースのシェアを表示したい場合、共通表式を使うとSQLは以下のようになる。

WITH sum_item AS (
       SELECT area, SUM(amount) AS sum_amount
       FROM suppliers
       GROUP BY area
     )
SELECT t1.*, CAST(t1.amount AS real) / t2.sum_amount * 100 AS share
FROM suppliers t1
INNER JOIN sum_item t2
ON t1.area = t2.area
ORDER BY area, share DESC
結果
supareaamountshare
E大阪8341.5
H大阪5226
F大阪3618
G大阪2914.5
C東京8040
A東京7035
B東京3015
D東京2010

上記の場合WITHを使わなくても同じ結果を得るSQL書くことはできる。
WITHを使うことによるメリットは以下が考えられる。

  • 副問い合わせが複雑な場合にはWITHを使った方がSQLが理解しやすくなる場合があることである。
  • 同じ副問い合わせが複数出現するような場合はSQLの記述量の削減になる。

"WITH RECURSIVE" という構文で再帰的な問い合わせができる。これはツリー構造のデータに対処するのに使用される。
ということらしいが、よくわからない^^;

下記のSQLのように、先に定義したWITH問い合わせを後のWITH問い合わせ内で使うこともできる。

WITH regional_sales AS (
        SELECT region, SUM(amount) AS total_sales
        FROM orders
        GROUP BY region
     ), top_regions AS (
        SELECT region
        FROM regional_sales
        WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)
     )
SELECT region,
       product,
       SUM(quantity) AS product_units,
       SUM(amount) AS product_sales
FROM orders
WHERE region IN (SELECT region FROM top_regions)
GROUP BY region, product;

2014/02/26追記
PostgreSQL 8.4.18 では共通表式を使ったSELECTはできたが、UPDATEはできなかった。
9.2.4のドキュメントにはINSERTとUPDATEもできると書いてあった。

参考文献:WEB+DB PRESS Vol.66 SQL緊急救命室 第5回

参考サイト:PostgreSQL 8.4.4文書 7.8 WITH問い合わせ

February 13, 2014

PostgreSQLの"::"はキャスト

PostgreSQLのことをググっていると、よくSQLに "::" というのが出てくる。なんじゃこりゃと思っていたが、リファレンスを見たらこれはキャストの記号だった。
以下の2つは同じこと。

CAST ( expression AS type )
expression::type

演算子の優先順位の表に載っていたからキャスト演算子なんだろうか?

キャストなしのSQL

SELECT now()

結果

now
timestamp with timezone
2014-02-13 20:35:45.678+09

キャストを付けたSQL

SELECT now()::date

結果

now
date
2014-02-13

また、次のようなテーブルがあるとする。

CREATE TABLE tbl1 (
  id Integer PRIMARY KEY, 
  value varchar(20)
)

キャストなしのSQL

SELECT 1, 'abc'

結果

?column?
integer
?column?
unknown
1 abc

キャストを付けたSQL

SELECT ((1, 'abc')::tbl1).*

結果

id
integer
value
character varying(20)
1 abc

tbl1型にキャストすると、単なる1と'abc'ではなくtbl1を検索した結果という扱いになるようだ。

参考文献:WEB+DB PRESS Vol.66 SQL緊急救命室 第5回

参考サイト:PostgreSQL 8.4.4文書 4.2.9. 型キャスト

February 11, 2014

アップロード機能に対するDoS攻撃

概要

Webアプリケーションのアップロード機能に対して、巨大なファイルを連続して送信することによりWebサイトに過大な負荷をかけるDoS攻撃(Denial of Service Attack、サービス妨害攻撃)を仕掛けられる可能性がある。
影響は応答速度の低下、最悪の場合はサーバの停止などがある。

対策

対策にはアップロードファイルの容量制限が有効である。PHPの場合は php.ini で設定することができる。アプリケーションの要求を満たす範囲でできるだけ小さな値にしておくことを推奨する。

設定項目 意味 デフォルト値
file_uploads ファイルアップロード機能が利用可能か On
uplad_max_filesize ファイルあたりの最大容量 2Mバイト
max_file_uploads 送信できるファイル数の上限 20
post_max_size POSTリクエストのボディサイズの上限 8Mバイト
memory_limit スクリプトが確保できる最大メモリのバイト数 128Mバイト

Apacheの httpd.conf でリクエストボディサイズを制限することもできる。PHP以外でも利用できることと早期のチェックでリクエストをエラーにすることでDoS攻撃耐性を高める上で有効である。下記はリクエストのボディサイズを100K日とに制限する場合の設定である。

LimitRequestBody 102400

参考文献:体系的に学ぶWebアプリケーションの作り方 4.12.1 ファイルアップロードの問題の概要

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践
徳丸 浩

ソフトバンククリエイティブ 2011-03-03
売り上げランキング : 4070

Amazonで詳しく見る
by G-Tools

February 09, 2014

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

発生箇所 シェルを呼び出す機能のある関数を実行している箇所
影響を受けるページ すべてのページが脆弱性の影響を受ける
影響の種類 秘密情報の漏洩、データの改ざん・削除、外部への攻撃(踏み台)、システムの停止など
利用者の関与の度合い 不要

概要

Webアプリケーションの開発に用いる言語の多くはシェル経由でOSコマンドを呼び出す機能を提供している。この機能の使い方に問題があると、意図しないOSコマンドが実行可能となる場合がある。これをOSコマンド・インジェクション脆弱性と呼ぶ。
OSコマンド・インジェクション脆弱性は影響が大きく非常に危険な脆弱性である。以下は典型的な攻撃シナリオである。

  1. 攻撃用ツールを外部からダウンロードする。
  2. ダウンロードしたツールに実行権限を与える。
  3. OSの脆弱性を内部から攻撃による権限昇格で管理者権限を得る。(Local Exploit)
  4. Webサーバは攻撃者の自由になる。

攻撃手法

例として下記の問い合わせフォームを用いる。

問い合わせフォーム
<form action="inquiry.php" method="post">
お問い合わせをどうぞ<br>
メールアドレス<input type="text" name="mail><br>
お問い合わせ<textarea name="inqu" cols="20" rows="3">
</textarea><br>
<input type="submit" value="送信">
</form>
受付画面のスクリプト
<?php
$mail = $_POST['mail'];
system("/user/sbin/sendmail -i <template.txt $mail");
//以下略
?>
<body>
お問い合わせを受け付けました。
</body>

template.txt はメールのヘッダ(From、MIMEエンコードされたSubject、Content-Type)と本文である。
正常系ではフォームのメールアドレス欄にメールアドレスを入力すれば、template.txtの内容のメールがそのメールアドレスに送られる。

攻撃では例えばメールアドレス欄に以下を入力する。

bob@example; cat /etc/passwd

送信ボタンを押すと /etc/passwd が表示される。
この例では表示するだけであるが、OSコマンド・インジェクション攻撃によりWebアプリケーションが稼動するユーザ権限で実行できるコマンドはすべて悪用が可能である。具体的にはファイルの削除、変更、外部からのファイルのダウンロード、ダウンロードしたツールの悪用などである。
攻撃の例として、概要で述べたような手順によるサーバの管理者権限奪取があげられる。

脆弱性の原因

シェルはコマンドラインによりOSを利用するためのインタフェースプログラムであり、Windowsでは cmd.exe、Unix系では sh などがある。
シェルには複数のコマンドを起動するための構文があるため、外部からパラメータを操作することにより元のコマンドに加えて別のコマンドを起動させられる場合がある。これがOSコマンド・インジェクションである。
また、開発者がOSコマンドを呼び出す意図がなくても、無意識にシェル起動のできる関数を使っている場合もある。
すなわち、OSコマンド・インジェクション脆弱性が発生するケースは以下の2通りある。

  • シェル経由でOSコマンドを呼び出す際に、シェルのメタ文字がエスケープされていない。
  • シェル機能を呼び出せる関数を使用している。

Unix系のシェルにおいて1行で複数のプログラムを起動する場合は以下の記法が使用できる。

$ echo aaa; echo bbb
aaa
bbb
$

この複数のプログラムを起動できるシェルの機能の悪用がOSコマンド・インジェクション攻撃である。
OSコマンドのパラメータとして指定する文字列にシェルのメタ文字を混入させることで、開発者の意図とは異なるOSコマンドが実行可能となることOSコマンド・インジェクション脆弱性の原因である。

OSコマンド・インジェクション脆弱性が生まれる条件は、以下の3つのすべてを満たすことである。

  • シェルを呼び出す機能のある関数を利用している。
  • シェル呼び出しの機能のある関数にパラメータを渡している。
  • パラメータ内に含まれるシェルのメタ文字をエスケープしていない。

対策

対策は、採用すべき順に以下がある。

  • OSコマンド呼び出しを使わない実装方法を選択する。
  • シェル呼び出し機能のある関数の利用を避ける。
  • 外部から入力された文字列をコマンドラインのパラメータに渡さない。
  • OSコマンドに渡すパラメータを安全な関数によりエスケープする。

どの対策を選択するかは設計段階で決めておくべきである。設計の各フェーズでは以下の検討を推奨する。

基本設計フェーズ
  実装方式設計として以下を検討する。
・主要な機能の実装方針を決定する。
・その際に極力ライブラリを利用するが、やむを得ない場合はOSコマンドを利用する。

以下、具体的な実装方法について説明する。

●OSコマンド呼び出しを使わない実装方法を選択する

OSコマンドを呼び出さない(シェルを呼び出せる機能を利用しない)ことにより、OSコマンド・インジェクション脆弱性が混入する可能性がなくなり、またOSコマンド呼び出しのオーバーヘッドがないことから多くの場合に性能も向上する。
先の問い合わせに対してメールを送信する受付スクリプトをライブラリを使用して書き換えた例を示す。

<?php
$mail = $_POST['mail'];
mb_language('Japanese');
mb_send_mail($mail, "受け付けました",
             "お問い合わせを受け付けました。",
             "From: webmaster@example.jp");
?>
<body>
お問い合わせを受け付けました
</body>

ただし、メール送信機能についてはメールヘッダ・インジェクション脆弱性が混入する可能性がある(メールヘッダ・インジェクション脆弱性)。

●外部から入力された文字列をコマンドラインのパラメータに渡さない

例として、sendmailコマンドには -t というオプションを指定すると、宛先のメールアドレスをコマンドラインで指定する代わりに、メール内容のTo、CC、Bccの各ヘッダから読み取る。この機能を使うと、外部から入力された文字列をコマンドラインに指定せずに済むのでOSコマンド・インジェクション脆弱性の混入する可能性がなくなる。
サンプルコードは以下。

<?php
$mail = $_POST['mail'];
$h = popen('/usr/sbin/sendmail -t -i', 'w');
if ($h === FALSE) {
	die('ただいま混みあっています。しばらくたってから...');
}
fwrite($h, <<< EndOfMail
To: $mail
From: webmaster@example.jp
Subject: =?UTF-8?B?5Y+X44GR5LuY44GR44G+44GX44Gf?=
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: 8bit

お問い合わせを受け付けました。
EndOfMail;
);
pclose($h);
?>
<body>
お問い合わせを受け付けました。
</body>

ただし、メール送信機能についてはメールヘッダ・インジェクション脆弱性が混入する可能性がある(メールヘッダ・インジェクション脆弱性)。

●OSコマンドに渡すパラメータを安全な関数によりエスケープする

今までのパターンでOSコマンド・インジェクション脆弱性を解消できない場合にはシェル経由でOSコマンドを呼ぶことになる。
OSコマンドに渡すパラメータをエスケープする必要があるがシェルのエスケープルールは複雑なので、安全なエスケープが行えるライブラリ関数を用いるべきである。PHPの場合は excapeshellarg() が該当する。
system() でsendmailコマンドを呼ぶ場合は以下のようになる。

system('/usr/sbin/sendmail <template.txt' . escapeshellarg($mail));

PHPには同種の関数として escapeshellcmd() があるが、使い方によっては脆弱性の原因となるので推奨しない。
また、シェルエスケープルールの複雑さと環境依存性から escapeshellarg() を使っても脆弱性が混入する可能性はありえる。よって、保険的対策としてパラメータの検証を行うことを推奨する。

●保険的な対策

対策に漏れがあった場合のために、いかの保険的対策を推奨する。

  • パラメータを検証する。
  • アプリケーションの稼動する権限を最小限にする。
  • WebサーバのOSやミドルウェアのパッチ適用

OSコマンド・インジェクション攻撃でもっと被害が大きくなるのは、サーバ内部からOSの脆弱性を突いた攻撃(Local Exploit)を受ける場合である。このためにパッチ適用を推奨する。

参考文献:体系的に学ぶWebアプリケーションの作り方 4.11.1 OSコマンド・インジェクション

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践
徳丸 浩

ソフトバンククリエイティブ 2011-03-03
売り上げランキング : 4070

Amazonで詳しく見る
by G-Tools

February 08, 2014

意図しないファイル公開

発生箇所 Webサイト全体
影響を受けるページ 公開されたファイルのみ
影響の種類 重要情報の漏洩
利用者の関与の度合い 不要

概要

外部から閲覧されると困るファイルをWebサーバの公開ディレクトリに配置していると、ファイルのURLがわかれば外部から閲覧が可能となる。
これによって重要情報の漏洩が発生する。

脆弱性の原因

原因は非公開のファイルを公開ディレクトリに置くことである。
公開ディレクトリに置いたファイルが外部から閲覧できる条件は以下である。

  • ファイルが公開ディレクトリに置かれている。
  • ファイルに対するURLを知る手段がある。
  • ファイルに対するアクセス制限が掛かっていない。

ファイルに対するURLを知る手段にはいかがある。

  • ディレクトリ・リスティングが有効である。
  • ファイル名が日付やユーザ名、連番など類推可能である。
  • user.dat、data.txtなどありがちな名前である。
  • エラーメッセージや他の脆弱性によりファイル名がわかる。
  • 外部サイトからリンクされるなどして検索エンジンに登録される。

URLでディレクトリ名を指定した場合にファイル一覧を表示する機能をファイル・リスティングと呼ぶ。

ファイルへのアクセス制限のみで対処するのは危険である。うっかりミスやサーバ移転の際の設定変更などアクセス制限が外れてしまう懸念がある。

対策

非公開ファイルを効果ディレクトリに置かないことである。そのためには以下が推奨される。

  • アプリケーションの設計時にファイルの安全な格納場所を決める。
  • レンタルサーバを契約する場合に非公開ディレクトリが利用できることを確認する。

また保険的にディレクト・リスティングを無効にする。Apacheの場合は httpd.conf で以下のように設定する。

<Directory パス指定>
  Options -Indexes その他オプション
  その他設定
</Directory>

レンタルサーバなどで httpd.conf の設定が変更できない場合は、.htaccess で以下のように設定する。

Options -Indexes

公開ディレクトリに非公開ファイルを置かざるを得ない場合(既存のサイトでファイルの移動が難しい場合など)、.htaccess に記述してファイルの外部からの閲覧を禁止する設定をして暫定的に対処することができる。

<Files "*.txt">
  deny from all
</Files>

参考文献:体系的に学ぶWebアプリケーションの作り方 4.10.2 意図しないファイル公開

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践
徳丸 浩

ソフトバンククリエイティブ 2011-03-03
売り上げランキング : 4070

Amazonで詳しく見る
by G-Tools

February 06, 2014

ディレクトリ・トラバーサル脆弱性

発生箇所 ファイル名を外部から指定できるページ
影響を受けるページ すべてのページが脆弱性の影響を受ける。
影響の種類 秘密情報の漏洩、データの改ざん・削除、任意のスクリプトの実行、アプリケーションの機能停止
利用者の関与の度合い 不要

概要

外部からパラメータでサーバ上のファイル名を指定できるWebアプリケーションでは、ファイル名に対するチェックが不十分であるとアプリケーションの意図しないファイルに対して閲覧や改ざん、削除ができる場合がある。これをディレクトリ・トラバーサル脆弱性と呼ぶ。
影響は以下。

  • Webサーバ内のファイルの閲覧
    →重要情報の漏洩
  • Webサーバ内のファイルの改ざん、削除
    →Webコンテンツ改ざん
    →マルウェアのサイトなどに誘導する仕組みの書き込み
    →スクリプトファイルや設定ファイルの削除による機能停止
    →スクリプトファイル改ざんによる任意のスクリプト実行

攻撃手法

以下は画面のテンプレートファイルを指定できるスクリプトである。

<?php
define('TMPLDIR', '/var/www/tmpl/');
$tmpl = $_GET['template'];
?>
<body>
<?php readfile(TMPLDIR . $tmpl . '.html'); ?>
以下略
</body>

このスクリプトの正常系の実行例を示す。

http://example.jp/sample.php?template=spring

上記のURLでは
/var/www/tmpl/spring.html
が表示される。

次は攻撃の例を示す。

http://example.jp/sample.php?template=../../../etx/hosts%00

この場合、ファイルパスは次のように組み立てられる。
/var/www/tmpl/../../../etc/hosts[NULL].html
"[NULL]"はヌルバイトを表す。ヌルバイトによりファイル名が終端され、正規化後のパスは
/etc/hosts
となり、hostsファイルが表示される。
このように、ディレクトリトラバーサル・トラバーサル脆弱性があるとWebサーバ上の任意のファイルにアクセスが可能となる。アプリケーションの実装によっては書き込みや削除が可能となる場合もある。
さらにディレクトリ・トラバーサルを用いてPHPなどのスクリプトファイルに書き込みができると、Webサーバ上で実行することにより任意のスクリプトを実行できる場合があります。この際の影響はOSコマンド・インジェクションと同じで、外部から不正プログラムをダウンロードされたりシステムに対する不正操作が可能となる。

脆弱性の原因

  • ファイル名を外部から指定することができる。
  • ファイル名として絶対パスや相対パスの形で異なるディレクトリを指定できる。
  • 組み立てたファイル名に対するアクセスの可否をチェックしていない。

上記3つをすべて満たす場合にディレクトリ・トラバーサル脆弱性が発生する。

対策

上記、であげた原因のうちいずれか1つでも対応すれば脆弱性は解消する。
以下、対策を説明する。

●外部からファイル名を指定できる仕様を避ける

ファイル名を外部から指定できなくすれば、ディレクトリ・トラバーサル脆弱性を根本的に解決できる。以下の方法で可能である。
・ファイル名を固定にする。
・ファイル名を直接指定するのではなく番号などで間接的に指定する。

●ファイル名にディレクトリが含まれないようにする

ファイル名にディレクトリ名が含まれないようにすれば、アプリケーションの想定したディレクトリのみにアクセスすることになりディレクトリ・トラバーサル脆弱性が解消する。
ディレクトリを示す文字記号はOSにより異なるので、OSによる違いを吸収できるライブラリを使用するべきである。PHPの場合はbasename()が使用できる。
basename()を用いた対策例を以下に示す。

<?php
define('TMPDIR', '/var/www/tmpl');
$tmpl = basename($_GET['template']);
?>
<body>
<?php readfile(TMPDIR . $tmpl . '.html'); ?>
</body>

basename() はヌルバイトがあっても削除しないので、basename() を使用しても拡張子を変更される場合があるので注意が必要である。
例えば外部から a.php%00 というファイル名(パーセントエンコーディングされている)が指定された場合、後にスクリプトで拡張子を付加してもヌルバイトで終端されて a.php というファイル名ができる。

●ファイル名を英数字に限定する

ファイル名の文字種の仕様を英数字に限定すればディレクトリ・トラバーサル脆弱性への攻撃に用いる記号文字が使えなくなる。

参考文献:体系的に学ぶWebアプリケーションの作り方 4.10.1 ディレクトリ・トラバーサル脆弱性

体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践
徳丸 浩

ソフトバンククリエイティブ 2011-03-03
売り上げランキング : 4070

Amazonで詳しく見る
by G-Tools

February 02, 2014

PHPメモ036:グローバル変数のクリア

PHPのグローバル変数のクリアについて、基本的なことなのにわかってなかったのでまとめた。
まあグローバル変数を多用すること自体どうかと思うが、大人の事情でグローバル変数を使わざるを得ないので。

関数内でグローバル変数をunset() した場合、そのローカル変数のみが破棄される。呼び出し側の変数は前と同じ値を保持する。

コードでの例をあげる。PHPのバージョンは5.2.17。古いな。
下記 global_func.php のグローバル変数を参照・変更する関数が定義されている。

<?php
function setGVar() {
	global $gVar;
	
	$gVar = 1;
}

function unsetGVar() {
	global $gVar;
	
	unset($gVar);
}

function printGVar() {
	global $gVar;
	
	print "gVar=" . var_export($gVar, TRUE);
}

例1: コード

<?php
include 'global_func.php';

func1();
print ":gVar=" . var_export($gVar, TRUE) . "\n";
exit;


function func1() {
	global $gVar;
	
	setGVar();
	
	$gVar = NULL;
	
	printGVar();
	print "func1:gVar=" . var_export($gVar, TRUE) . "\n";
}

結果

printGVar:gVar=NULL
func1:gVar=NULL
:gVar=NULL

例2: コード

<?php
include 'global_func.php';

func2();
print ":gVar=" . var_export($gVar, TRUE) . "\n";
exit;


function func2() {
	global $gVar;
	
	setGVar();
	
	unset($gVar);
	
	printGVar();
	print "func2:gVar=" . var_export($gVar, TRUE) . "\n";
}

結果

printGVar:gVar=1
func2:gVar=NULL
:gVar=1

例3: コード

<?php
include 'global_func.php';

func3();
print ":gVar=" . var_export($gVar, TRUE) . "\n";
exit;


function func3() {
	global $gVar;
	
	setGVar();
	
	unset($GLOBALS['gVar']);
	
	printGVar();
	print "func3:gVar=" . var_export($gVar, TRUE) . "\n";
}

結果

printGVar:gVar=NULL
func3:gVar=1
:gVar=NULL

例4: コード

<?php
include 'global_func.php';

func4();
print ":gVar=" . var_export($gVar, TRUE) . "\n";
exit;


function func4() {
	global $gVar;
	
	setGVar();
	
	unsetGVar();
	
	printGVar();
	print "func4:gVar=" . var_export($gVar, TRUE) . "\n";
}

結果

printGVar:gVar=1
func4:gVar=1
:gVar=1

結構複雑だ。
例1の "=" で代入した結果はどのスコープに対しても有効。
例3のunset($GLOBALS['<グローバル変数名>']) は例1と同じかと思っていたが、unset() を実行したスコープでは効いていなかった。
例2と例3は真逆の結果だ。

参考サイト:
PHP: unset - Manual グローバル変数の扱い

February 01, 2014

WordPressでsession_start()など初期化処理をしたい

WordPressやっつけ対応の第2弾。

とある共通処理があり、内部でsession_start()を呼んでいる。それはWordPressを使っている画面と使っていない画面の両方から呼ばれる。
しかし、WordPressの方からだけWarningが出力される。
WordPressの方ではその共通処理を呼んだ時点で既に出力を始めているようで、そのために

[nn-Jan-2014 nn:nn:nn] PHP Warning:  session_start(): Cannot send session cache limiter - headers already sent (output started at /****/xxx.php:99) in /***/xxx.inc on line nnnn

のようなWarningが出る。「もうHTTPヘッダ出力してるから」ということらしい。

解決策は2つ。
1つは wp-config.php に session_start() を書く方法。
これはWordPress全体に対して有効。

もう1つは functions.php に書く方法。このファイルは

<WordPressのフォルダ?>/wp-content/themes/<テーマ>/functions.php

にある。
テーマ毎に設定できるようだ。
#WordPressはよくわかっていない。 具体的には functions.php に以下のコードを書く。

function onetoone_session_start() {
	session_name('hoge_sid');
	session_start();
}
add_action('init', 'onetoone_session_start');

上記の2つのやり方は session_start() に限らず、出力を開始する前に何か処理をしたい場合に使える。

関連記事:
WordPressから呼ばれているかどうかを判定する

参考サイト:
WordPressにおけるPHPセッション管理 | Yama's Memorandum
PHP:WordPressのプラグインでsession_start() | 自転車で通勤しましょ♪ブログ

« January 2014 | Main | March 2014 »

May 2017
Sun Mon Tue Wed Thu Fri Sat
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
無料ブログはココログ

日本blog村

  • にほんブログ村 IT技術ブログへ
  • にほんブログ村 アニメブログへ
  • にほんブログ村 サッカーブログ アルビレックス新潟へ

好きな音楽家

メモ

XI-Prof