本当は怖いスクリプト

何気なく使っているスクリプトですが・・・

どうも、やまもとやまです。
サーバーエンジニアとしてサーバー運用を行っていると、スクリプトで定型処理の実行や自動化を行うことも多いと思います。
でもそのスクリプト、本当に安全ですか?
今回は恐怖の事例とその対策についてご紹介します。

対象のサンプルスクリプト

今回取り上げるのはPerlスクリプトです。
本スクリプトでは、ドメイン解約時に、対象ドメインのメールアカウントを一括削除する処理を行います。
以下、抜粋したスクリプト(スクリプト名は deletedomain.pl とします)。
※本来はログ出力等も行うべきですが、説明のため簡略化しています

#!/usr/bin/perl
# -------------------------------------------------
# DOMAIN
$host_name = $ARGV[0];

$sasldb = "/usr/sbin/sasldblistusers2";
$saslpass = "/usr/sbin/saslpasswd2";

# -------------------------------------------------
# main
# -------------------------------------------------
# 対象ドメインのアカウントを抽出
@ret = `$sasldb | grep "\@$host_name"`;

foreach $ret (@ret) {
# 改行コードを削除
($ret) = split(/\n/ ,$ret);

# メールアドレス部分だけを抽出する
($mail_name,$dust) = split(/:/ , $ret);

# アカウント削除処理
$run = `$saslpass -d $mail_name`;
}
exit;

メールアカウントはsasldbで管理しており、処理としてはそのユーザを削除するようですね。

試してみる

とりあえずはスクリプトの動作を試してみましょう。
現在登録されているアカウントを確認します。

# sasldblistusers2
info@example.jp: userPassword
mayamaya@example.jp: userPassword
moto@example.com: userPassword
test@example.co.jp: userPassword
yamamoto@example.com: userPassword
yamaya@example.co.jp: userPassword
motoyama@example.jp: userPassword
yama@example.com: userPassword

3ドメイン、合計8アカウントが登録されているようです。

では、「example.jp」ドメインのアカウントを削除します。

# ./deletedomain.pl example.jp

# sasldblistusers2
moto@example.com: userPassword
test@example.co.jp: userPassword
yamamoto@example.com: userPassword
yamaya@example.co.jp: userPassword
yama@example.com: userPassword

分かりづらいですが、無事対象アカウントが削除されたようです!
さて、それではこのスクリプト、何が問題でしょうか・・・?

 

分かりましたか?
はい、そうです。
引数なしで実行してみます。

# ./deletedomain.pl

# sasldblistusers2

・・・。
・・・・・。
・・・・・・・・・・・・・・。

うわああああああああああああ。
アカウント ぜんぶ きえた。
大事故です。恐怖でしかないですね。

また、引数を付けた場合でも、以下の2ドメインが含まれているようなレアケースでは悲劇が起こります。

example.com
example.com.tw

# ./deletedomain.pl example.com

ああああああああああああああぁぁ

怖いですね。本当に怖いですね。

・・・さてではこれ、対策としては何かあるでしょうか?
例としてですが、以下のようなものが考えられます。

  1. 入力チェック
    引数のチェックを行います。
    引数がない場合や複数の場合はエラーにしましょう。
    ただしこれでは、似たドメインの場合の悲劇は回避できないですね。。
  2. 抽出方法の改善
    @ret = `$sasldb | grep "\@$host_name"`;
    ↓↓↓
    @ret = `$sasldb | grep "\@$host_name: userPassword"`;
    これで回避はできます!
    しかしこれはもはやバグ修正の領域。事前の対策とはなりません。
  3. 事前テストの徹底
    スクリプト作成時、正常なケース、異常なケース等を可能な限り網羅したチェックを行います。
    (入力チェックができていなかった場合でも)引数なしのテストで不具合が見つかると思われます。
  4. 設計を見直す
    削除処理前に、対象アカウント一覧を表示し、明示的に確認を促すようにします。
    さすがに全削除は気づいて止める、、はず、、
    他にも何かしら被害を小さくする方法は考えられると思います。

などなど。

結局のところ

プログラムにバグはつきもの。
今回のサンプルのような小さいものはともかく、ある程度の規模を超えるとゼロにすることはほぼ不可能ともいわれます。
とはいえ致命的な不具合が発生すると洒落になりません。
エラー時にも大事故にならない作りを心がけ、事前のチェックも十分に行いましょう。

それではまた!