アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

Webアプリケーション向けの自動セキュリティスキャナ「Skipfish」を試してみました

こんにちは、中川です。

先日、GoogleからWebアプリケーション向けの自動セキュリティスキャナ「Skipfish」が公開されたので、社内で利用しているCakePHPのアプリで試してみました。

Skipfish( http://code.google.com/p/skipfish/ )は、Webアプリケーションの脆弱性SQLインジェクションクロスサイトスクリプティング等を自動的に検出してくれるApache License 2.0のライセンスで公開されているオープンソースのツールです。


必要なライブラリは以下とのこと。



    * GNU C Compiler
    * GNU Make
    * GNU C Library (including development headers)
    * zlib (including development headers)
    * OpenSSL (including development headers)
    * libidn (including development headers) 

KnownIssues - skipfish -参照。

私の環境では、libidnがなかったので、yumで入れました。

さて、skipffish本体のインストールを行いましょう。

※最新のダウンロードはこちらのページを参照ください。
http://code.google.com/p/skipfish/



# wget http://skipfish.googlecode.com/files/skipfish-1.29b.tgz
# tar xvzf skipfish-1.18b.tgz
# cd skipfish
# make


インストールが完了したら、実際に動かしてみましょう。
いろいろとオプションがありますので、


./skipfish --help

で確認できます。

今回は、以下のオプションを追加した条件で実行しています。



・-W dictionaries/minimal.wl
・・辞書ファイルは用意されている、dictionaries/minimal.wl を利用。

・-C MYSESSIONNAME=e3mvctp2b46u312b5a5b9krop5
・・ログインが必須のアプリをテストしたため、クッキーの値をセット。
・・値は適当なブラウザでログインしてクッキーの情報をコピペ。

・-X logout,login
・・logout / login を含むURLはセッションが切れてしまうのでまずは除外する。

・-g 1
・・開発サーバのスペックが低いため同時接続は1で。

・-o log/
・・結果の出力先を、log/ と指定

・http://192.168.1.236:8080/
・・検査URL


そして、実行!



$ ./skipfish -W dictionaries/minimal.wl -C MYSESSIONNAME=e3mvctp2b46u312b5a5b9krop5 -X logout,login -g 1 -o log/ http://192.168.1.236:8080/


が、終わらない、、、30分経っても、がんがん実行中で終わらない。。。
サーバの負荷も高い高い。。これは外部に対しては絶対に行わないようにしましょう!

あきらめてCtl+Cで終了しました。。
途中で止めても結果ファイル自体は出力してくれるようです。

log/ ディレクトリ以下のindex.htmlファイルを適当なブラウザで開けば結果を確認できます。

こんな感じです。
※細かい結果の部分は今回はお見せしないようにしますが、各項目をクリックで詳細の確認ができます。


途中で止めたせいもあるのか(それでも30分くらいは動かしていた、、、)、今回は特に大きな問題などは見つからなかったようです。
XSRFと表示されていますが、GETでアクセスする表示だけのページでこのアプリでは問題ないです。)
が、アプリケーションの想定外の動作を発見できたりしました。。


余談ですが、今回はCakePHPのアプリで試したのですが、
CakePHPのセッションのチェックには、UserAgentのチェックもされているようで、
core.phpの以下の設定を変更する必要がありました。
なぜセッション指定してもログアウトするのか結構悩みました。。。


Configure::write('Session.checkAgent', false);



検査には時間がかかるようですが、簡単に試すことができますので、脆弱性チェックの一つとして利用してみてはいかがでしょう。

■参考にさせていただいたサイト
・Skipfishで定義されている Webアプリに対するテスト一覧
http://www.yokada.net/blog/1456
・使ってみました。 - はじめに。
http://d.hatena.ne.jp/hajimeni/20100324/1269394599

symfony1.2のCSRF対策について

こんにちは、小川です。
symfony1.2ではsfFormクラスを用いてフォームのレンダリングや入力項目のバリデーションを行います。このsfFormクラスにはCSRF対策も実装されているのはご存じでしょうか。
今回はこのCSRF対策が具体的にどのように行われているかをお話ししたいと思います。

先にどのような手法で対策を行うかですが、フォームごとに異なるトークンをHTML上に埋め込み、その値をバリデーション時にチェックするという方法で対策を行っています。
具体的にどのようにトークンが生成され、どのようにチェックを行っているかは後ほど詳しく説明します。

CSRF対策を有効にするためにはどうすれば良いでしょうか。Jobeetなどでsymfony1.2について学んだ方はご存じかと思います。
CSRF対策は各アプリケーションごとに設定可能で、アプリケーション作成時に以下のようにすることで有効になります。



$ symfony generate:app --escaping-strategy=on --csrf-secret=myUniqueSecret frontend


通常symfonyコマンドのgenerate:appタスクを用いてアプリケーションのスケルトンを生成しますが、タスク実行時にcsrf-secretというオプションを指定します。最初の説明でトークンを埋め込むと説明しましたが、このトークンを生成するときのソルト値として使用する値を指定します。上記の例ではmyUniqueSecretがソルト値になります。

実際にcsrf-secretというオプションを指定してアプリケーションを生成した場合、アプリケーションのconfigディレクトリ内にあるsettings.ymlという設定ファイルの内容が変化します。



all:
  .settings:
    csrf_secret:            myUniqueSecret


allのcsrf_secretという項目に、先ほどのcsrf-secretオプションの値が入っていると思います。もしgenerate:appタスク実行時にcsrf-secretを指定しなかった場合はfalseとなり、CSRF対策が有効になりません。もし有効に設定しなかった場合はsettings.ymlを直接変更してキャッシュをクリアすれば有効になります。

有効にするだけであればとてもシンプルですが、フォームクラスを利用するときにはどのような注意が必要なのでしょうか。通常フォームクラスには「ウィジェット」と「バリデータ」を指定することでフォーム要素の作成とバリデーションを行っていきます。CSRF対策のトークンはどのようにして扱えば良いのでしょうか。実際にコードを書いて説明していきます。



// lib/form/doctrine/UserForm.class.php
<?php
class UserForm extends BaseUserForm
{
  public function configure()
  {
    $this->setWidgets(array(
      'email' => new sfWidgetFormInput(),
    ));
    $this->setValudators(array(
      'email' => new sfValidatorEmail(array(), array(
        'required' => 'メールアドレスを入力してください',
        'invalid'  => 'メールアドレスを正しく入力してください',
      )),
    ));
  }
}




// apps/frontend/modules/user/actions/actions.class.php
<?php 
class userActions extends sfActions
{
  public function executeNew(sfWebRequest $request)
  {
    $this->form = new UserForm();
  }

  public function executeCreate(sfWebRequest $request)
  {
    $this->forward404Unless($request->isMethod('post'));

    $this->form = new UserForm();
    $this->form->bind($request->getParameter($this->form->getName()));
    if ($this->form->isValid()) {
      $this->form->save();
      $this->redirect('user/show?id='.$this->form->getObject()->getId());
    }

    $this->setTemplate('new');
  }
}




// apps/frontend/modules/user/templates/newSuccess.php
<?php echo $form->renderFormTag() ?>
  <?php echo $form->renderGlobalErrors() ?>
  <?php echo $form->renderHiddenFields() ?>

  <div>
    メールアドレス:<?php echo $form['email'] ?>
    <?php echo $form['email']->renderError() ?>
  </div>

  <p>
    <input type="submit" value="Save!" />
  </p>
</form>


簡単なフォームクラス、アクション、テンプレートを書いてみました。実はこれだけでも既にCSRF対策は行われています。
はじめにHTML上にトークンを埋め込むと言いましたが、テンプレート上のどこにあるのでしょうか?トークンのレンダリングは$form->renderHiddenFields()で行われています。settings.yml上でcsrf-secretが指定されている場合、各フォームにデフォルトで_csrf_tokenという名前のhiddenフィールドが自動的に作成されます。
renderHiddenFieldsメソッドは全てのhiddenフィールドをレンダリングするためのメソッドなので、このメソッドをテンプレート内に記述していることでトークンがHTML上に埋め込まれるようになります。

基本的にこのrenderHiddenFieldsメソッドはhiddenフィールドを設定していない場合でも必ずテンプレートに記述するようにしましょう。そうしないとCSRF対策のトークンが送られず、CSRFだと判断してしまいます。

リクエストがCSRFによるものかはバリデーション時に判定しています。$form->isValid()というのがそれです。UserFormの定義ではemailに対してsfValidatorEmailバリデータを指定しています。基本的にバリデータはウィジェットとセットで定義を行いますが、先ほどの_csrf_tokenにもバリデータが定義されています。このトークンにはsfValidatorCSRFTokenという専用のクラスが用意され、送られてきたトークンが正しいものかのチェックを行っています。

ですのでテンプレート側でrenderHiddenFieldsメソッドを実行してさえいれば、バリデーション時に必ずチェックしてくれるので特に意識することなくCSRF対策が行われるような仕組みになっています。


ではこのトークンはどのようなロジックで生成されているのでしょうか。このトークンはワンタイムトークンと呼ばれているものでは厳密にはありません。sfFormクラスにそのロジックがありますので実際にみてみましょう。sfFormのコンストラクタでaddCSRFProtectionというメソッドが呼ばれており、そこに記述されています。



<?php
public function addCSRFProtection($secret)
{
  if (false === $secret || (is_null($secret)  & & !self::$CSRFProtection))
  {
    return;
  }

  if (is_null($secret))
  {
    if (is_null(self::$CSRFSecret))
    {
      self::$CSRFSecret = md5(__FILE__.php_uname());
    }

    $secret = self::$CSRFSecret;
  }

  $token = $this->getCSRFToken($secret);

  $this->validatorSchema[self::$CSRFFieldName] = new sfValidatorCSRFToken(array('token' => $token));
  $this->widgetSchema[self::$CSRFFieldName] = new sfWidgetFormInputHidden();
  $this->setDefault(self::$CSRFFieldName, $token);
}

public function getCSRFToken($secret = null)
{
  if (is_null($secret))
  {
    $secret = self::$CSRFSecret;
  }

  return md5($secret.session_id().get_class($this));
}


addCSRFProtectionの引数は通常nullが来ます。これはsfFormのコンストラクタの第3引数に渡した値が使用され、通常指定することはありませんのでコンストラクタのデフォルト値であるnullが使用されます。
引数はnullですが、self::$CSRFProtectionにsettings.ymlで記述しているcsrf_secretの値が既に入っている状態なので、このソルト値を元にgetCSRFTokenメソッドから実際にレンダリングに使用されるトークンの値の取得を行っています。

getCSRFTokenメソッドのロジックをみるとわかるとおり、ソルト値・セッションID・クラス名をくっつけた値のMD5ハッシュ値が利用されています。ソルト値とクラス名はアプリケーションのソースコードに手を入れない限り変更されないので、セッションIDが変わらない限りはこのトークンの値は変わらない仕様になっています。これは、例えばタイムスタンプなどを利用してトークンを生成する方法ではそのトークンを別のところに格納しておく必要があり、フォームクラスのみで実装することが難しいからだと思われます。
またget_class関数でクラス名を取得していますが、これは__CLASS__定数とは違い実行時に評価されますので、フォームクラスが違えばそのたびにトークンの値も違う値が使用されるようになっています。

上記までで大体の実装方法はおわかりいただけたでしょうか。覚えておく必要があるのは、アプリケーション作成時にcsrf-secretの指定を行うことと、必ずrenderHiddenFieldsメソッドをテンプレート側で呼び出すことの2つ程度で、後は特に意識せずに行うことが可能です。トークンのフィールドを自動的に作られるようになっていますが、embedForm時などには削除も行われたりしますので自動でつくことに対する副作用は特にないのではないでしょうか。


symfony1.0のころにもCSRF対策用のプラグインは存在していました。フィルターを用いてHTML内のmethod=postなform要素を探してtoken用のhiddenを埋め込み、POSTでリクエストが来たときに同じフィルターでチェックをするということをやるものでした。ただしこれは、link_toのオプションでmethod=>postを指定した場合にトークンを仕込むことができなかったり、CSRFアタックを検知した場合に例外になったりと画面ごとに制御ができず、どうしたものかと悩むことも多々ありました。

ですが今回、フォームがオブジェクトになったことによりフォーム単位で指定が変えられ、バリデーション時に一緒にCSRFアタックの検知もしてくれるので非常に使い勝手が良くなりました。
プラグインの頃はできなかったlink_to($name, $url, array('method' => 'post'))のようなときにも現在は対応しています。その場合にonclick属性に指定されるJavaScriptを生成しているのがUrlHelperの_method_javascript_functionになります。




<?php
function _method_javascript_function($method)
{
  $function = "var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'post'; f.action = this.href;";

  if ('post' != strtolower($method))
  {
    $function .= "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); ";
    $function .= sprintf("m.setAttribute('name', 'sf_method'); m.setAttribute('value', '%s'); f.appendChild(m);", strtolower($method));
  }

  // CSRF protection
  $form = new sfForm();
  if ($form->isCSRFProtected())
  {
    $function .= "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); ";
    $function .= sprintf("m.setAttribute('name', '%s'); m.setAttribute('value', '%s'); f.appendChild(m);", $form->getCSRFFieldName(), $form->getCSRFToken());
  }

  $function .= "f.submit();";

  return $function;
}


上記のように空のsfFormを作成してCSRF対策が有効になっているか(settings.ymlでcsrf_secretが設定されているか)を判定して自動的にトークンを含めるということを行ってくれています。
これをアクション側で判断するのも簡単です。sfWebRequestにcheckCSRFProtectionメソッドが実装されていますので基本的にはこちらを使用します。



<?php
class sfWebRequest extends sfRequest
{
  public function checkCSRFProtection()
  {
    $form = new sfForm();
    $form->bind($form->isCSRFProtected() ? array($form->getCSRFFieldName() => $this->getParameter($form->getCSRFFieldName())) : array());

    if (!$form->isValid())
    {
      throw $form->getErrorSchema();
    }
  }
}


エラーが起こった場合には上記の通り、バリデーションエラーをまとめたsfValidatorErrorSchemaクラスをスローしますので、アクション側でcheckCSRFProtectionメソッドを呼び出して適切にハンドリングしてあげることでCSRF対策が可能です。


symfony1.2では1.0に比べて、あまり目立たないかもしれませんがこういったセキュリティ面でも進化しています。XSS対策用のsfOutputEscaperオブジェクトまわりも1.0にはない機能がいくつか追加されていたり、内部を見ていくと本当に様々なところで進化を実感できます。
1.3や1.4で大きな変更がないことを考えると、symfony1.xとしての形はだいぶ固まってきた感じがしますね。symfonyのコードもだいぶみてきましたが、より高度な情報を配信できるようにもっと隅々まで探っていくつもりなので今後ともよろしくお願いいたします。

ratproxyとwapitiを組み合わせたセキュリティスキャン

以前フリーで使えるセキュリティスキャナ・ツールまとめというエントリを書いたのですが、その続きとしてコマンドラインから実行するタイプのツールとプロキシ型のツールを組み合わせる方法を紹介します。くれぐれも悪用はしないでくださいね!

一つ注意しておかなければならないのは、特定のページに対して詳細なテストを行うのであれば、手動でテストした方が、精度は高いです(テストする人のスキルの問題もありますが)。

今回紹介するような方法だと、個人のスキルに依存することなく、ある程度のセキュリティテストを実行することができるようになりますよー、ということが主題です。

それでは、早速本題です。

ratproxy と wapiti の説明に関しては、フリーで使えるセキュリティスキャナ・ツールまとめを参照してください。ともにバージョンが上がっていますが、それほど変更はないと思います。

ratproxy 1.5.1-beta と wapiti 2.0.0-beta を使用して、セキュリティスキャンを実行します。

まずは、ratproxyを起動します



$ /path/to/ratproxy -v /tmp/ratproxy -w /tmp/ratproxy.log -lextifscgjm


オプションは ratproxy に含まれる詳細なレポートにいいよ、と書いてあるオプションです。

そして、次にwapitiで攻撃をしかけます。



$ python /path/to/wapiti.py http://example.com/ -p http://localhost:8080/


この example.com が対象となるURLです。あとはしばらく放置しておけば、wapitiの実行が終わります。

大量のURLがある場合は -x オプションを使用してチェック不要なURLを省くなどしておくとよいでしょう。もしくはディレクトリごとに実行を分けるなどしたほうがよいかもしれません(http://example.com/blog/ のように)。

また、きちんとアクセスされているかどうか不安な場合は、以下のようにすればよいでしょう。



$ python /path/to/wapiti.py http://example.com/ -p http://localhost:8080/ -v 2


実行が終わると、wapiti 2.0.0-beta では generated_report ディレクトリが生成されて、レポートが作成されます。ただ、GETに日本語が含まれるURL(?submit=検索 がふくまれるようなURL)でエラーが起きていると、生成途中で落ちてしまうようです。

ratproxy の実行結果についてもどうようにHTML作成を行うことができます。



$ /path/to/ratproxy-report.sh /tmp/ratproxy.log > ratproxy_report.html


HTMLあとはHTMLを見て、どんなエラーが発生しているのかをじっくり見ていけば、凡ミスは防げるでしょう。

また、ほぼすべてのURLに対してアクセスし、通常ではありえないようなパラメータを投げてくれるので、GET値やPOST値のチェック漏れでPHPエラーが発生していないか、といったことも調べることができます(まさに一石二鳥)

ratproxy と wapiti を一緒に使う大きな利点はありませんが、2つのツールでチェックすれば片方でしか検知できない問題もわかるので、より安心できます。

フリーで使えるセキュリティスキャナ・ツールまとめ

森川です

セキュリティスキャナについて色々と調べたことがあったので、それについての備忘録の意味も含めたまとめ+アルファです。

セキュリティスキャナといっても色々とあって、ネットワークを対象にしたもの、アプリケーションを対象にしたものなど色々とありますが、今回はアプリケーションに関するものが対象です。

さらに、セキュリティをチェックする方法として、コマンドラインで実行するタイプと、プロキシを使用するものの2種類に大別されます。

プロキシ型としては、先日Googleが新しくRatProxyというセキュリティツールを公開しているので、まずはRatProxyをプロキシ型のメインとして簡単に触れてみたいと思います。


RatProxy


RatProxyは意外と簡単に使うことができました。手順は単純で、Linux上でソースコードのダウンロード、makeでプログラムが出来上がるので、あとはratproxyという実行ファイルを実行します。



./ratproxy -w hoge.log -r -lfscmXC  &


これで、インストールしたマシンの8080番ポートをプロキシとして指定して、チェックしたいサイトをブラウジングするだけです。

絶対に自分で管理していないサイトに対して実行しないでください。

こうしてできた、hoge.log を同梱のratproxy-report.shで解析すれば、レポートがHTMLとして出力されます。



./ratproxy-report.sh hoge.log > hoge.html


レポートのHTMLもスクリーンショットも公開されています。

その他のプロキシ型スキャナ


その他にもプロキシ型のスキャナは色々とあるのですが、RatProxyのドキュメントページに自分が調べたものは大体記述されていました。なので、簡単に触れるにとどめておきます。

1. WebScarab
2. Paros
3. Burp
4. ProxMon
5. Pantera
6. Chorizo!

それぞれのプログラムについて、検索すれば使い方はわりと簡単にわかると思います。また、Proxyとして動作するものなので、複数のプロキシを繋げて使うことも可能です(スキャナにプロキシを指定できる場合に限定されますが)。

といっても、これから使う場合、新し物好きなこの業界であれば、みなさんRatProxyを使うのではないかと思います。これからRatProxyがどのように発展していくのかが楽しみです。


Nikto


NiktoはWebサーバとその上で動く既知のアプリケーションのセキュリティホールをチェックしてくれるものです。

プラグインでの拡張が可能なのが特徴なのと、かなり広く使われていて簡単に使えるので、既存のソフトウェアを使用している場合は、スキャンしてみるとよいでしょう。

使い方は非常に簡単で、nikto のソースコードperlがインストールされているマシンにダウンロードして、以下のようなコマンドを実行します。



./nikto.pl -host http://example.com


これで、出力される結果を見て、サーバの設定を変えたり、最新版に変えたりするとよいでしょう。

Wapiti


Wapitiはあまり有名でないかもしれませんが、非常に簡単にWebのセキュリティチェックをすることができるツールです。

WapitiもNiktoと同じようにコマンドラインでテストを行うことができます。なお、NiktoはPerlのプログラムですが、WapitiはPythonです。

UbuntuDebianではパッケージがあるので、apt-get install wapitiとすればインストール可能です。CentOSなど、パッケージがない場合でもダウンロードして展開すれば問題ありません。基本的な使い方はサイトに書いてある通りで、以下のようなコマンドになります。



python wapiti.py http://example.com


これで、XSSなどの脆弱性を簡易的にチェックすることができます。なんといっても直感的に、問題がすぐにコマンドラインから調べられるのがよいです。

ログインが必要なサイトでも、クッキーを使用することで対応することができます。ソースコード内に含まれる getcookie.py というファイルを使用します。以下はダウンロードしたソースに含まれている example.txt の内容です。



bash-3.0$ python getcookie.py cookies.txt http://127.0.0.1/vuln/?page=login
Please enter values for the folling form :
url = http://127.0.0.1/vuln/login.php
login (on) : toto
password (on) : toto
0 : <Cookie PHPSESSID=8qte5k7jr6ogkocrlcrk9obmj2 for 127.0.0.1/>


上記のようにログインした時のクッキーをファイルとして保存しておいて、wapiti実行時に指定することで、ログインが必要なサイトでも使用することができます。

最新のRatProxyがプロキシ型のように、最近のWebアプリケーションではJavaScriptActionScriptからのアクセスが多くなっているので、すべてのURLを網羅するという意味ではプロキシ型の方が優れていますが、コマンドライン型でもかなりの部分をカバーすることができます。

個人的には、担保したいセキュリティレベルに応じて、使用するプログラムを変更していくこと、そして何よりも定期的なチェックが大事だと思います。なので、まずはWapitiで簡易チェックを実行することを習慣づけることが重要です。