Security blog by Ierae Security,Inc.

脆弱性診断技術や関連情報を発信するブログメディア

オープンソースカンファレンス2016 Hokkaido | ひげで学ぶWebアプリケーションに潜むリスク(OSコマンドインジェクション対策)

osc16do

札幌ラボ在籍の岸谷です。2016年6月18日(土)に北海道札幌市で開催されたオープンソースカンファレンス2016 HokkaidoでWebアプリケーションセキュリティ関連のお話をする機会がありましたので、その内容を紹介します。スライドはこちらです。

osc2016do ひげで学ぶWebアプリケーションに潜むリスク from Ierae Security

 

サービス利用者にOSコマンドインジェクションの実行を許す脆弱性

今回お話しした内容は、主にOSコマンド実行につながるWebアプリケーションの脆弱性についてです。

今どきOSコマンドインジェクションの話題?とお思いかもしれません。OSコマンドインジェクションと言えば非常に危険な脆弱性として知られる一方で、安全なウェブサイトの作り方に紹介されているような典型的なタイプは近年開発されたWebアプリケーション等にはほとんど見られなくなっているように感じられます。またIPAの公開する脆弱性関連情報に関する届出状況を見ても割合として非常に低いと言えるようです(※ただし「ケータイキット」のような事例も報じられたばかりですし、数としては減少しても注意が必要なことに変わりはないでしょう)。

上でふれた典型的なタイプのOSコマンドインジェクション脆弱性とは、プログラム内でシェル(など)を呼び出す際にWebアプリケーション利用者から受け取ったパラメータを渡すようなコードを原因とするもので、シングルクォートやセミコロン、バッククォート文字などのシェル上のメタ文字の入力によって発生するものが代表的です。しかしそれとは少し異なる形でWebアプリケーション利用者にOSコマンドの実行を許してしまう脆弱性に遭遇することがあります。私がここしばらくの間に確認した複数のケースに共通していた動作は、Black Hat USA 2015 でブリーフィングがあった “Server-Side Template Injection: RCE For The Modern Web App” で解説されているような原因によるもののようでした(ブリーフィングの動画はYouTubeで公開されています)。

テンプレートエンジンとOSコマンド実行

一定以上の規模のWebアプリケーション等の開発の場であれば、ひとつのファイル内にロジックとビューを一緒にして書くということはあまりしないものと思います。MVCモデルであればViewとして例えばHTMLを出力するためのテンプレートファイルにひな型となるHTMLと、Controllerから受け取ったデータを使用するための必要最低限の処理を所定の記法で書いておき、テンプレートエンジンがそれらを処理してHTMLを出力するというやり方が一般的ではないでしょうか。

テンプレートの使用例(スライドより)

osc2016hokkaido-01

現在ではとても一般的に用いられるこのテンプレートエンジンですが、使い方によっては任意コード実行の原因を作ってしまうことがあります。多くのエンジンには、プロダクトごとに記法は様々ですがそのプラットフォームとなるプログラミング言語の式を評価(実行)する機能が備わっています。この式を外部から挿入できるとしたらとても危険です。お作法に従って利用すればそのようなことにはならないのですが、少し変わったことを行おうとする時には慎重になる必要があります。

本記事の内容と目的について

  • 本記事で紹介する情報は、脆弱性が生じるケースについての情報を共有しその対策に役立てて頂くことを目的としています。以降で紹介する内容を他者の管理するコンピュータに対し許可なく行うと不正アクセス行為(違法)となる可能性がありますので、絶対に行わないでください。
  • 自分が管理するコンピュータ上で実験する場合、他人がアクセス可能な状態にならないようご注意ください。
  • 本記事で紹介する脆弱性は原理的には決して新しいものではありません。また、特定のテンプレートエンジンやフレームワーク、言語等に脆弱性があるということでもありません。

脆弱性が生じる例

以下のようにテンプレートを使用するサンプルコードを用意しました。ここで紹介する例はNode.jsベースのWebアプリケーションを想定しておりExpressフレームワークのコントローラでSwigテンプレートエンジンを使用する例です。

router.get('/', function(req, res, next) {
    users.find().toArray(function(err, users) {
        res.set('Content-Type','text/html; charset=utf-8');
        res.render('tmpl', { title: '会員情報', users:users });
    });
});

上から、usersという名前でアクセスできるデータベースから取得したデータの配列をusersというオブジェクト名でtmplという名前のテンプレートファイル(上で使用例とした内容)に渡しHTMLを出力するという内容です。これは問題ない使用方法です。もちろん、データにシェル上のメタ文字などが含まれていても想定外の動作は見られません。

Webブラウザで表示した結果

osc2016hokkaido-02

以下のコードだとどうでしょうか。今度はHTMLではなくJSON形式でデータを返す機能を作ろうと思い、HTMLよりシンプルな形式なのでコントローラ内でテンプレート文字列を連結・整形して組み立て、その文字列をswig.renderメソッドに渡してレンダーすることにしました。

router.get('/', function(req, res, next) {
    users.find().toArray(function(err, users) {
        // formatting
        var tmpl = '{"users":[\r\n' + '{# some templates #}';
        users.forEach(function(user){
            tmpl += ' {"name":"' + escapeHTML(user.name)
                + '","age":' + user.age + '},'
                + '\r\n{# some templates #}';
        });
        tmpl += ']}';
        res.set('Content-Type','application/json; charset=utf-8');
        res.send(swig.render(tmpl));
    });
});

osc2016hokkaido-03

JSONっぽい文字列を出力させることができました。 {# から #} までの部分はコメントのため無視されますが、実際には何かテンプレート処理が書かれることを想定しています。「JSONだけどHTMLエスケープ」「配列の最後の要素の後にもカンマ」といったような雑な点には目をつぶってください。年齢(age)は数値のみ登録可能であるものとして、名前(name)に注目します。例えば、以下のような名前が登録されていたらどうなるでしょうか。

{{ process.binding('fs').internalModuleReadFile('/etc/hosts') }}

登録して再度JSONデータを取得してみます。

osc2016hokkaido-04

サーバOS上の/etc/hostsファイルを読み取ることができました。これは、先に示したJSON出力用コード内でswig.renderメソッドに渡すテンプレート文字列(tmpl変数)に以下のような文字列が挿入されたためです。

{"name":"{{ process.binding('fs').internalModuleReadFile('/etc/hosts') }}","age":123}

2重中かっこに挟まれた文字列はSwigテンプレート上では変数の指す値を表示するために使用でき、式を解釈します。そのためJavaScriptで書かれた通りにhostsファイルの内容を出力したという流れです。

スライドではもう一つの例としてRuby on RailsとHamlテンプレートエンジンのケースを紹介しました。これも同様にコントローラ内で文字列連結によりテンプレートを組み立ててエンジンに渡すというコードです。

class TestController < ApplicationController
    def index
        @tmpl = "-# some templates \r\n" \
                + CGI.escapeHTML(params[:str]) \
                + "\r\n-# some templates"
        render :inline => @tmpl, :type => 'haml'
    end
end

strパラメータに、Haml上の記法に従って#{ `ls -la` }という値を入力してみるとカレントディレクトリのファイル一覧が出力されます。中かっこの中の文字列はRubyのコードとして解釈されますが、Ruby上ではバッククォート文字で囲んだ文字列がシェルコマンドとして実行されます。

osc2016hokkaido-05

なお、Ruby on Railsのrenderオプション(上記コードの「:inline」など)そのものを外部から指定できてしまうケースがあるという問題について今年情報が公開され、フレームワークの潜在的な脆弱性として修正が行われています。以下の記事で詳しく取り上げられていますので、そちらについても併せてご覧頂くことをおすすめします。

対策

本記事で紹介した2例ともテンプレートを文字列連結で生成しており、その中にテンプレート上のメタ文字を含む文字列が挿入されたために問題が発生しています。

なぜそのようなコードを?という点については、想像でしかありませんが例えば以下のような場合がありうるのではと考えます。

  • ちょっとフォーマットする程度だしコード内で完結したい
  • データ内の文字列加工や合成のような処理が必要な流れで
  • エスケープ処理等により実際には問題のない箇所からの部分的流用

特殊な事情がない限り、最初に紹介した下記のサンプルコードのようにテンプレートファイルにオブジェクトを渡す形でレンダーすれば問題は起こりません。テンプレートエンジンによりますが、デフォルトでHTMLエスケープを行ってくれたりするものが多く記述量も減ります。

router.get('/', function(req, res, next) {
    users.find().toArray(function(err, users) {
        res.set('Content-Type','text/html; charset=utf-8');
        res.render('tmpl', { title: '会員情報', users:users });
    });
});

なお本記事では中かっこが特殊文字となるケースに着目して見てきましたが、プロダクトに応じて様々な文字がテンプレート上のメタ文字として使用されますのでご注意ください。

まとめ

ごく一般的に利用されるテンプレートエンジンですが、脆弱性が生じてしまう使い方もあることを紹介しました。本記事が安全なアプリケーション開発の一助となれば幸いです。

イエラエセキュリティではこのような脆弱性の診断経験のある方や興味がある方を積極的に募集しています。もう東京で消耗したくない方は札幌勤務も可能です。気軽にご連絡ください。

補足として、スライドの最後では中かっこに関連した話題としてAngularJSのAngular Expressionsサンドボックスのバイパス(によるクロスサイトスクリプティング)についても紹介しましたが、本記事の話題とはだいぶ内容の異なるトピックですのでまた回を改めて紹介したいと思います。