湖农大邀请赛shell_rce漏洞复现

2023-12-11 19:20

本文主要是介绍湖农大邀请赛shell_rce漏洞复现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

湖农大邀请赛 shell_rce 复现

在 2023 年湖南农业大学邀请赛的线上初赛中,有一道 shell_rce 题,本文将复现该题。

题目内容,打开即是代码:

<?phpclass shell{public $exp;public function __destruct(){$str = preg_replace('/[^\W]+\((?R)?\)/', '', $this->exp);$code = substr($str , 0, 1);if(preg_match("/^[$code]+$/",$str)){eval($this->exp." hello world!"); }}
}if(!isset($_GET['exp'])){highlight_file(__FILE__);
}if(!preg_match('/^[Oa]|get/i',$_GET['exp'])){unserialize($_GET['exp']);
}

注意到在反序列化之前先进行了一个判断

preg_match('/^[Oa]|get/i',$_GET['exp'])

这段代码使用 PHP 的 preg_match 函数对 $_GET[‘exp’] 变量进行正则表达式匹配。具体而言,该正则表达式为 “1|get”,其解释如下:

  • ^ 表示匹配字符串的开头
  • [Oa] 表示匹配 O 或 a 这两个字符中的任意一个
  • | 表示逻辑或,即要么匹配开头的 O 或 a,要么匹配 get
  • i 表示不区分大小写

因此,该正则表达式可以匹配的字符串包括:

  • 以 O 或 a 开头的任意字符串
  • 包含 get 的任意字符串,不区分大小写

在本代码中,若 $_GET[‘exp’] 变量与上述正则表达式匹配成功,则返回 true,否则返回 false。

如此一来,常见的序列化字符串和数组绕过的方法都不管用了。

C 开头的绕过

推荐博客:

https://fushuling.com/index.php/2023/03/11/php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%ADwakeup%E7%BB%95%E8%BF%87%E6%80%BB%E7%BB%93/

这里只用 ArrayObject() 函数做演示,具体操作如下:

$exp = new shell();
$arr = array("exp"=>$exp);
$ao = new ArrayObject($arr);
echo serialize($ao);

这段 PHP 代码的作用如下:

  1. 创建一个名为 $exp 的对象,该对象是 shell 类的一个实例。

  2. 创建一个名为 $arr 的数组,其中只有一个元素,即键名为 “exp”,键值为 $exp。

  3. 使用 PHP 内置函数 ArrayObject 对 $arr 进行实例化,获得一个名为 $ao 的 ArrayObject 对象。

  4. 调用 PHP 内置函数 serialize 将 $ao 序列化,即将其转换成可以存储或传输的字符串形式,输出结果到页面上。

通过以上操作,即可将序列化字符串变成以 C 开头的形式,绕过第一个检测。

输出结果:

C:11:"ArrayObject":59:{x:i:0;a:1:{s:3:"exp";O:5:"shell":1:{s:3:"exp";N;}};m:a:0:{}}

无参函数 RCE

在 shell 类的 __destruct 方法中,有这样的过滤机制:

$str = preg_replace('/[^\W]+\((?R)?\)/', '', $this->exp);

会将该类中的属性 exp 作一个过滤

该段代码使用 PHP 的 preg_replace 函数对 $this->exp 字符串进行正则表达式替换。具体而言,该正则表达式为 “[^\W]+\((?R)?\)”,其解释如下:

  • [^\W]+ 表示匹配一个或多个非特殊字符(即字母或数字),这里的 ^\W 表示取反后表示非特殊字符
  • ( 表示匹配左括号
  • (?R) 表示匹配一个递归到起始符号(即 “(?R)”)的表达式,即匹配一个嵌套的模式。所以该正则表达式能够处理有括号嵌套的情况
  • ) 表示匹配右括号

因此,该正则表达式可以匹配的字符串为类似于 " functionName(args) " 这样的字符串,并且支持函数嵌套,例如:functionName1(functionName2()) 。其实际含义是匹配一个函数名后紧跟着一对括号,其中可以有递归式的参数列表。

当匹配到这种无参函数格式的字符串时,就会将其替换为空。

使用无参函数的话,所有的值都会被替换为空。假设函数有参,那么参数部分将会被保留,但是这样的话就无法通过后面的过滤了,具体为什么后面会讲。

因此,在 C 绕过的基础上,可以构造如下的 payload :

$exp->exp = "var_dump(scandir(current(localeconv())));"

这个 PHP 嵌套函数的作用如下:

  1. localeconv() 函数返回当前设置的地区的格式化信息,包括货币符号、小数点符号等。它返回一个数组,其中包含了与当前地区相关的格式化参数,该函数返回的第一个元素的值通常是小数点 “.” 。

  2. current() 函数用于获取数组中的当前元素的值。在这里,它用于获取 localeconv() 函数返回的数组的第一个元素的值,即一个小数点。

  3. scandir() 函数用于获取指定目录中的文件和文件夹列表。它接受一个路径作为参数,并返回一个包含指定目录中所有文件和文件夹的数组。scandir(".") 表示获取当前目录下的文件列表。

因此,该代码的作用是获取当前地区的第一个格式化参数(通常是小数点符号),然后将该参数作为路径传递给 scandir() 函数,从而获取该路径下的文件和文件夹列表。最终,使用 var_dump() 函数将该列表输出到页面上,以便查看它们的详细信息。

因为 eval 函数的参数结尾必须要带分号,所以该字符串的末尾添加了分号,如此一来,str 字符串就只是一个分号了

__halt_compiler() 中断 eval() 函数的执行

在无参函数绕过之后,又进行了一次过滤:

$str = preg_replace('/[^\W]+\((?R)?\)/', '', $this->exp);
$code = substr($str , 0, 1);
if(preg_match("/^[$code]+$/",$str))
{eval($this->exp." hello world!"); 
}

这段 PHP 代码的作用如下:

  1. 使用 substr 函数从字符串 $str 中取出第一个字符,并将其赋值给变量 $code。

  2. 使用正则表达式 “/^[$code]+$/” 对字符串 s t r 进行匹配。其中, " ‘ [ str 进行匹配。其中,"`^[ str进行匹配。其中,"[code]+`" 匹配以 $code 中的字符开头,并且由 c o d e 中的字符组成的任何长度的字符串, " code 中的字符组成的任何长度的字符串," code中的字符组成的任何长度的字符串,"" 表示匹配到字符串结尾。总的来说,该正则表达式表示 $str 只包含 $code 中的字符。

  3. 如果匹配成功,即 $str 只包含 $code 中的字符,那么执行 eval 函数,将 $this->exp 和字符串 “hello world!” 连接在一起作为 PHP 代码执行。eval 函数可以将字符串作为 PHP 代码执行,因此这里相当于在执行 $this->exp 和 “hello world!” 的拼接结果。如果 $this->exp 中含有函数调用等需要被执行的代码,则会在这里被执行。

  4. 如果正则表达式匹配失败,即 $str 中含有 $code 中未包含的字符,则不执行 eval 函数。

已知 str 只是一个分号,那么 code 就也是一个分号,这样就能通过后面的过滤了。假如 str 不是只有分号的话,这里就过不去,所以上面要用无参函数。

到这里我们知道,$this->exp 字符串中有且只能有无参函数和分号,有多少个都可以,怎么排列都可以,但是不能有其他的东西。

但是问题来了:

eval($this->exp." hello world!"); 

这段代码做一个字符串拼接,导致 eval 函数无法正常执行,当时想了好久,知道赛后放 wp 才知道,__halt_compiler() 函数可以中断 eval() 的执行,这是连 exit() 都做不到的事。比如说:

eval(a();__halt_compiler(); hello world!)

那么 eval 函数执行完 a() 函数后,执行到 __halt_compiler() 函数时就会中断,不再执行后面的代码,也不会因为后面的代码不符合规范而报错。

据此,构造最终的 payload :

$exp = new shell();
$exp->exp = "var_dump(scandir(current(localeconv())));__halt_compiler();";
$arr = array("exp"=>$exp);
$ao = new ArrayObject($arr);
echo serialize($ao);

本地成功输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

想要读取文件的话,修改 $exp->exp 的值就可以了。

官方的 wp 中使用 array_rand() 随机获取当前目录下的文件内容。

官方 wp :

var_dump(readfile(array_rand(array_flip(scandir(current(localeconv()))))));

array_flip() 反转数组中的键值对,即将数组中的每个值作为键,原来的键作为新的值。

array_rand() 随机获取反转后的数组中的一个键,即获取当前文件夹下随机一个文件或文件夹的名称。

输出结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(scandir(current(localeconv()))))));


array_flip() 反转数组中的键值对,即将数组中的每个值作为键,原来的键作为新的值。array_rand() 随机获取反转后的数组中的一个键,即获取当前文件夹下随机一个文件或文件夹的名称。输出结果:[外链图片转存中...(img-rYtPJnlu-1702287965559)]在上面查看查看当前目录下文件时,返回的数组中还有 "." 和 ".." 两个值,所以上面的 wp 可能会不成功,需要多试几次。

  1. Oa ↩︎

这篇关于湖农大邀请赛shell_rce漏洞复现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/481814

相关文章

jenkins 插件执行shell命令时,提示“Command not found”处理方法

首先提示找不到“Command not found,可能我们第一反应是查看目标机器是否已支持该命令,不过如果相信能找到这里来的朋友估计遇到的跟我一样,其实目标机器是没有问题的通过一些远程工具执行shell命令是可以执行。奇怪的就是通过jenkinsSSH插件无法执行,经一番折腾各种搜索发现是jenkins没有加载/etc/profile导致。 【解决办法】: 需要在jenkins调用shell脚

Detectorn2预训练模型复现:数据准备、训练命令、日志分析与输出目录

Detectorn2预训练模型复现:数据准备、训练命令、日志分析与输出目录 在深度学习项目中,目标检测是一项重要的任务。本文将详细介绍如何使用Detectron2进行目标检测模型的复现训练,涵盖训练数据准备、训练命令、训练日志分析、训练指标以及训练输出目录的各个文件及其作用。特别地,我们将演示在训练过程中出现中断后,如何使用 resume 功能继续训练,并将我们复现的模型与Model Zoo中的

UMI复现代码运行逻辑全流程(一)——eval_real.py(尚在更新)

一、文件夹功能解析 全文件夹如下 其中,核心文件作用为: diffusion_policy:扩散策略核心文件夹,包含了众多模型及基础库 example:标定及配置文件 scripts/scripts_real:测试脚本文件,区别在于前者倾向于单体运行,后者为整体运行 scripts_slam_pipeline:orb_slam3运行全部文件 umi:核心交互文件夹,作用在于构建真

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《考虑燃料电池和电解槽虚拟惯量支撑的电力系统优化调度方法》

本专栏栏目提供文章与程序复现思路,具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源程序擅长文章解读,论文与完整源程序,等方面的知识,电网论文源程序关注python

【CTF Web】BUUCTF Upload-Labs-Linux Pass-13 Writeup(文件上传+PHP+文件包含漏洞+PNG图片马)

Upload-Labs-Linux 1 点击部署靶机。 简介 upload-labs是一个使用php语言编写的,专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。旨在帮助大家对上传漏洞有一个全面的了解。目前一共20关,每一关都包含着不同上传方式。 注意 1.每一关没有固定的通关方法,大家不要自限思维! 2.本项目提供的writeup只是起一个参考作用,希望大家可以分享出自己的通关思路

站长常用Shell脚本整理分享(全)

站长常用Shell脚本整理分享 站长常用Shell脚本整理分享1-10 站长常用Shell脚本整理分享11-20 站长常用Shell脚本整理分享21-30 站长常用Shell脚本整理分享31-40 站长常用Shell脚本整理分享41-50 站长常用Shell脚本整理分享51-59 长期更新

Shell脚本实现自动登录服务器

1.登录脚本 login_server.sh #!/bin/bash# ReferenceLink:https://yq.aliyun.com/articles/516347#show all host infos of serverList.txtif [[ -f ./serverList.txt ]]thenhostNum=`cat ./serverList.txt | wc -l`e

[轻笔记]ubuntu shell脚本切换conda环境

source /home/yourhostname/anaconda3/etc/profile.d/conda.sh # 关键!!!conda activate env_name

[轻笔记] ubuntu Shell脚本实现监视指定进程的运行状态,并能在程序崩溃后重启动该程序

根据网上博客实现,发现只能监测进程离线,然后对其进行重启;然而,脚本无法打印程序正常状态的信息。自己通过不断修改测试,发现问题主要在重启程序的命令上(需要让重启的程序在后台运行,不然会影响监视脚本进程,使其无法正常工作)。具体程序如下: #!/bin/bashwhile [ 1 ] ; dosleep 3if [ $(ps -ef|grep exe_name|grep -v grep|

adb shell 执行后台程序后断开adb后台进程被结束的解决办法

环境:Android 版本 Android8 通常让程序后台执行就是在命令 最后加上 &即可,但是在Android 8上实验发现,程序的确后台了,但是拔掉USB线再连接上发现进程已结束。不确定Android早期版本是否存在此问题。 参考网上一些Linux方法,如加nohup 仍然无效,还是会结束。看来Android adb shell 与 Linux shell 还是有一定区别。 后来在网上