MTで学ぶセキュアプログラミング
MT  Tokyo  2014  August
junnama@alfasado.jp
何のために?
•サイトの改ざんを防ぐために  
•クライアントのセキュリティ要件を
満たすために  
•被害リスクをできるだけ少なくする
ために
何のために、誰が?
•セキュリティコンサルタントが  
•プログラマが  
•デザイナが  
•ディレクターが
何を?
•MTMLテンプレートを  
•Movable  Typeそのものを  
•MTプラグインを  
•MTベースのウェブサイトを
イントロダクション
<Tweet禁⽌止>
⼤大⼈人の事情につき削除
本題
</Tweet禁⽌止>
例例  :  PHPを出⼒力力するテンプレート
PHPを出⼒力力するテンプレート
これは果たして脆弱性か?
何をされたくないか
•情報の漏漏洩(権限のない情報
へのアクセス)  
•権限昇格=権限を越えた操作  
•情報の書き換え、削除
性善説か?  性悪説か?
攻撃するのは誰?
•悪意の第三者による外部から
のアタック  
•内部犯⾏行行(元社員、コミュニ
ティユーザー)  
•内部(犯⾏行行ではないが)誘発=>
正規のユーザーに踏ませる
そこまで許可した覚えはない。
いや、だって、できるんだ
から。。。
Vs.
この場合、どちらが悪いのか?
はともかく、セキュリティ監査
では、アウト。
基本、性悪説に基づく。
<MTEntryBody
replace=”<?”,””>
完璧に防げるか?  
(程度度問題→やらないより、まし)
AllowPHPScript 0
PowerCMS(DynamicMTMLでは)...
<MTStripPHP>
<MTEntryBody>
</MTStripPHP>
PowerCMS(DynamicMTMLでは)...
function strip_php( $source ) {!
$tokens = token_get_all( $source );!
$res = '';!
$inphp = FALSE;!
foreach ( $tokens as $token ) {!
if ( is_string( $token ) ) {!
$token = array( '', $token );!
}!
list( $id, $str ) = $token;!
if (! $inphp ) {!
if ( $id === T_OPEN_TAG or $id == T_OPEN
$inphp = TRUE;!
} else {!
$res .= $str;!
}!
厳密には、PHPはPHPで評価する
<MTEntryBody
replace=”<?”,””>
でも、やらないより遥かにまし
主なもの
•XSS  =  Cross  Site  Scripting  
•CSRF  =  Cross  site  request  
forgeries  
•SQL  (OS  Command)  Injection  
•Directory  traversal
例例  :  検索索結果テンプレート
例例  :  検索索結果テンプレート
例例  :  検索索結果テンプレート
例例  :  検索索結果テンプレート
例例  :  検索索結果テンプレート
sub load_core_tags {!
require MT::Template::Context;!
return {!
function => {!
SearchString => &_hdlr_search_string,!
...!
!
sub _hdlr_search_string!
{ $_[0]->stash('search_string') || '' }
package MT::Template::Context::Search;!
例例  :  検索索結果テンプレート
sub prepare_context {!
my $app = shift;!
my $q = $app->param;!
...!
if ( my $str = $app->{search_string} ) {!
$ctx->stash(!
'search_string', encode_html($str) );!
}
package MT::App::Search;!
コード中でエスケープしてる
あまり宜しくない!(きっぱり)
例例  :  検索索結果テンプレート
...!
!
sub context_script {!
my ( $ctx, $args, $cond ) = @_;!
!
my $search_string!
= decode_html($ctx->stash('search_string')
package MT::Template::Context::Search;!
わざわざ戻してんのかよ!
•どこでエスケープ済みなのか、
わからなくなるぞ?  
•エンコード済みのクエリを渡さ
れたらおかしくならんか?
統⼀一されていないので、
責任が曖昧(忘れちゃう)
わからなくなるから...
わからなくなるから...
⼊入⼒力力値のエスケープは、  
テンプレート出⼒力力時に統⼀一
次
$q = $_GET[‘search’];!
$sql = “SELECT FROM
mt_entry WHERE!
mt_entry.entry_title LIKE %
${q}%”
例例  :  PHPによる実装
APIやORマッパを使え
my $app = MT->instance;!
my $q = $app->param(‘search’);!
my @entries = MT-
>model(‘entry’)!
->load( {!
title => { like => $q }, !
} )
例例  :  Perlによる実装
$app = $ctx-
>stash(‘bootstrapper’);!
$q = $app->param(‘search’);!
$entries = $app->!
load(‘entry’,!
array( ‘title’ => !
array( ‘like’ => $q )));
例例  :  PHP(DynamicMTML)による実装
var q = jQuery('#search').value;!
var params = {!
search: q,!
searchFields: "title,body",!
};!
api.listEntries(siteId, params,
function(response) {!
if (response.error) {!
return;!
}!
例例  :  JavaScriptによる実装
何かあったらメーカーのせいw
ユーザーの⼊入⼒力力を信⽤用しない!
そもそも
次
applications:!
cms:!
menus:!
page:page_foo:!
label: Foo!
order: 1000!
mode: page_foo!
permission: manage_pages!
view:!
- blog!
- website!
例例  :  Pluginでのメソッドの拡張
例例  :  Pluginでのメソッドの拡張
methods:!
page_foo: $Foo::Foo::CMS:: page_foo!
package Foo::CMS!
!
sub page_foo {!
my $app = shift;!
# Do Something! <= え?!
}!
例例  :  Pluginでのメソッドの拡張
短縮URLでもメールで送り
つけてやれ!
やっちゃえ!(*)
脱線  :  管理理画⾯面にBasic認証かけても、正規
ユーザー攻撃タイプには意味がないよね。
新規作成
編集
削除
例例  :  Pluginで独⾃自オブジェクト
え?
sub  save  {  
        my  $app    =  shift;  
        my  $q        =  $app-‐‑‒>param;  
        my  $type  =  $q-‐‑‒>param('_̲type');  
        my  $save_̲mode  =  'save_̲'  .  $type;  
        if  (  my  $hdlrs  =  $app-‐‑‒>handlers_̲for_̲mode($save_̲mode)  )  {  
                return  $app-‐‑‒>forward($save_̲mode);}  
        my  $id  =  $q-‐‑‒>param('id');  
        $app-‐‑‒>validate_̲magic()  or  return;  
        my  $author  =  $app-‐‑‒>user;  
        my  $perms  =  $app-‐‑‒>permissions;  
        if  (  !$author-‐‑‒>is_̲superuser  )  {  
                if  (  (  $type  ne  'author'  )  &&  (  $type  ne  'template'  )  ){  
                        return  return  $app-‐‑‒>permission_̲denied()  if  !$perms  &&  $id;  
                }  
                $app-‐‑‒>run_̲callbacks(  'cms_̲save_̲permission_̲filter.'  .  $type,  
                $app,  $id  )  ||  return  $app-‐‑‒>permission_̲denied();  
        }  
#以下、保存
MT::CMS::Common::save(⼀一部省省略略)
magic_̲tokenを
チェック
save_̲fooメソッドが定義され
ているときはそちらへforward
システム管理理者の場
合、ここはスルー
コールバックcms_̲save_̲permission_̲filter.fooが1以外を返すと権限エラー
対策
程度度問題=読み出されるだけより、改ざ
んされるほうが  い・や・だ。
ちょっと脱線。何故ここだけ対策を?
セキュリティにおいて、「程度度問題」
はいつもつきまとう。
但し、前提は事情に
よって変わる
例例えば、個⼈人情報。  
流流出も改ざんもどっちもどっち
改めて対策
package  MT::App::CMS;
ホワイトリストではなくブラックリスト
なので、不不要なものは塞塞いでおく
GETリクエストは弾く(気休めだが)
package Foo::CMS!
!
sub page_foo {!
my $app = shift;!
if ( $app->request_method ne “POST” ) { !
return $app->permission_denied();!
}!
#Do Something!
}
POSTしか受け付けない
⼀一時トークンのチェック
package Foo::CMS!
!
sub page_foo {!
my $app = shift;!
if $app->validate_magic!
or return $app->permission_denied();!
}!
#Do Something!
}
magic_̲tokenパラメタをチェック
⼀一時トークンのチェック
•magic_̲token=アクティブユーザーの
session情報  
•saveやdelete等の操作ではチェックを!  
•(但し)magic_̲tokenが無効もしくは空の
時、ID/Password⼊入⼒力力画⾯面を表⽰示  
•この時↑deleteメソッド以外ではパラメ
タが引き継がれる
⼀一時トークンのチェック
•magic_̲token=アクティブユーザーの
session情報  
•saveやdelete等の操作ではチェックを!  
•(但し)magic_̲tokenが無効もしくは空の
時、ID/Password⼊入⼒力力画⾯面を表⽰示  
•この時↑deleteメソッド以外ではパラメ
タが引き継がれる
でも
CSRFとしては、不不⼗十分ではないか?
mt.cgi?_̲_̲mode=save&_̲type=foo&title=Title...
クリックしちゃだめー!
悪意のURL
改善案
if $app->validate_magic(!
{ with_confirm!
=> { message =>!
ʻ‘FooをBarしようとしています。  
                                                    宜しいですか?ʼ’,!
icon => ‘CAUTION’!
} } )!
or return $app->trans_error...!
}!
改善案(簡易易指定)
if $app->validate_magic!
( ʻ‘FooをBarしようとしています。宜しいですか?ʼ’ )!
or return $app->trans_error...!
}!
改善案(次に何が起こるのかを明⽰示する)
FooをBarしようとして  
います。宜しいですか?
これからはセキュリティだっ
て、UX重視だろ!
話しを戻します
package Foo::CMS!
!
sub page_foo {!
my $app = shift;!
my $perms = $app->permissions!
or return $app->permission_denied();!
return $app->permission_denied()!
unless $perms!
->can_do('edit_all_pages');!
#Do Something!
}
ちゃんと権限をチェック
+更更新が必要なものは権限をチェック
例例題
sub  some_̲action{  
        my  $app=  shift;  
        require  MT::Page;  
        my  $blog  =  app-‐‑‒>blog;  
        my  $page  =  MT::Page-‐‑‒>load(  $app-‐‑‒>param(  'id'  )  );  
        if  (!  $page  )  {  
                return  $app-‐‑‒>translate(  'Invalid  Page  ID:[_̲1].',  $app-‐‑‒>param(  'id'  )  );  
        }  
        my  $perm  =  $app-‐‑‒>user-‐‑‒>is_̲superuser;  
        if  (!  $perm  )  {  
                $perm  =  $app-‐‑‒>user-‐‑‒>permissions(  $blog-‐‑‒>id  )-‐‑‒>can_̲administer_̲website
                $perm  =  $app-‐‑‒>user-‐‑‒>permissions(  $blog-‐‑‒>id  )-‐‑‒>can_̲administer_̲blog  
                                            unless  $perm;  
                $perm  =  $app-‐‑‒>user-‐‑‒>permissions(  $blog-‐‑‒>id  )-‐‑‒>can_̲manage_̲pages  
                unless  $perm;  
        }  
        if  (!  $perm  )  {  
                return  $app-‐‑‒>translate(  'Permission  denied.'  );  
        }  
        #  この後、$pageへの処理理}
1.パラメタidに数字以外のものが渡された場合  
2.パラメタidにpageオブジェクトではなく
entryオブジェクトのidが渡された場合  
3.パラメタidが空の場合  
4.パラメタidに現在のblogに属さないentry/
pageのidが渡された場合
考えておかなければならないケース
sub  some_̲action{  
        my  $app=  shift;  
        require  MT::Page;  
        my  $blog  =  app-‐‑‒>blog;  
        my  $page  =  MT::Page-‐‑‒>load(  $app-‐‑‒>param(  'id'  )  );  
        if  (!  $page  )  {  
                return  $app-‐‑‒>translate(  'Invalid  Page  ID:[_̲1].',  $app-‐‑‒>param(  'id'  )  );  
        }  
        my  $perm  =  $app-‐‑‒>user-‐‑‒>is_̲superuser;  
        if  (!  $perm  )  {  
                $perm  =  $app-‐‑‒>user-‐‑‒>permissions(  $blog-‐‑‒>id  )-‐‑‒>can_̲administer_̲website
                $perm  =  $app-‐‑‒>user-‐‑‒>permissions(  $blog-‐‑‒>id  )-‐‑‒>can_̲administer_̲blog  
                                            unless  $perm;  
                $perm  =  $app-‐‑‒>user-‐‑‒>permissions(  $blog-‐‑‒>id  )-‐‑‒>can_̲manage_̲pages  
                unless  $perm;  
        }  
        if  (!  $perm  )  {  
                return  $app-‐‑‒>translate(  'Permission  denied.'  );  
        }  
        #  この後、$pageへの処理理}
mt.cgi?_̲_̲mode=some_̲action&id=
<script>alert('do')</script>
id=1&blog_̲id=3  
↑  
id:1のpageのblog_̲id
が3である保証は?
idが空だったら?もしくはEntryのIDが渡されたら?
てか、テンプレート
エンジン使え!
添削後
sub  some_̲action  {  
        my  $app  =  shift;  
        $app-‐‑‒>validate_̲magic(  ʻ‘FooをBarしてもよろしいですか?ʼ’  )  or  
                return  $app-‐‑‒>trans_̲error(  'Permission  denied.'  );  
        require  MT::Page;  
        my  $page;  
        if  (!  $app-‐‑‒>param(  'id'  )  ||  
                (!  $page  =  MT::Page-‐‑‒>load(  $app-‐‑‒>param(  'id'  )  )  )  {  
                return  $app-‐‑‒>trans_̲error(  'Invalid  Page  ID:'[_̲1]'',    
                                                            MT::Util::encode_̲html(  $app-‐‑‒>param(  'id'  )  )  );  
        }  
        if  (  $page-‐‑‒>class  ne  'page'  )  {  
                return  $app-‐‑‒>trans_̲error(  'Invalid  Class:'[_̲1]'',  $page-‐‑‒>class  );  
        }  
        #  permission  check  case  1  
        if  (  $app-‐‑‒>blog-‐‑‒>id  !=  $page-‐‑‒>blog_̲id  )  {  
                return  $app-‐‑‒>trans_̲error(  'Invalid  BlogID:'[_̲1]'',    
                                                            MT::Util::encode_̲html(  $app-‐‑‒>blog-‐‑‒>id  )  );  
        }  #  続く  
操作を伴う場合最初にmagic_̲tokenをチェック
渡された値をそのまま表⽰示する場合、エスケープ
Entryでないかチェック
違うブログIDに属するもので
ないかチェック(※)
※システムスコープやウェブサイトスコープの場合はあり得るケース
てか、テンプレート
エンジン使え!
       if  (!  $app-‐‑‒>can_̲do(  'manage_̲pages'  )  )  {  
                return  $app-‐‑‒>trans_̲error(  'Permission  denied.'  );  
        }  
        #  or  case  2  
        my  $perm  =  $app-‐‑‒>user-‐‑‒>is_̲superuser;    
        if  (!  $perm  )  {  
                $perm  =  $app-‐‑‒>user-‐‑‒>permissions(  $page-‐‑‒>blog_̲id  )-‐‑‒>  
can_̲administer_̲website;  
                $perm  =  $app-‐‑‒>user-‐‑‒>permissions(  $page-‐‑‒>blog_̲id  )-‐‑‒>  
can_̲administer_̲blog  unless  $perm;  
                $perm  =  $app-‐‑‒>user-‐‑‒>permissions(  $page-‐‑‒>blog_̲id  )-‐‑‒>  
can_̲manage_̲pages  unless  $perm;  
        }  
        if  (!  $perm  )  {  
                return  $app-‐‑‒>trans_̲error(  'Permission  denied.'  );  
        }  
        if  (!  $app-‐‑‒>run_̲callbacks(  'cms_̲save_̲permission_̲filter.page'  $app,  $page  )  {
                return  $app-‐‑‒>trans_̲error(  'Permission  denied.'  );  
        }  
        #  Do  Something.}
$blog-‐‑‒>idではなく$page-‐‑‒>blog_̲idでチェック
コールバックをすり
抜けないようにする
⼀一気に難易易度度あがりましたが...
結論論
まとめ
•セキュアプログラミングは性悪説  
•それでも程度度問題。何をやられない
ようにするか。何を優先するか(何
は、あきらめるか)  
•セキュリティにもUXの視点が必要
まとめ
•ユーザーの⼊入⼒力力を信⽤用しない  
•エスケープは出⼒力力時に統⼀一する  
•SQLを⾃自前で書くな。API使え  
•正規ユーザー攻撃型改ざん対策のた
めに、⼀一時トークンを使う
ありがとうございました。

MTで学ぶセキュアプログラミング@MT Tokyo