RCTF2025-photographer

前言

虽然这道题被ai打烂了,但是复现的话还是认真分析一下整个流程

解法

首先看一下在哪里获取flag public\superadmin.php

1
2
3
4
5
6
7
8
9
<?php
require_once __DIR__ . '/../app/config/autoload.php';
Auth::init();
$user_types = config('user_types');
if (Auth::check() && Auth::type() < $user_types['admin']) {
echo getenv('FLAG') ?: 'RCTF{test_flag}';
}else{
header('Location: /');
}

Auth::check() 是检查是否登录

Auth::type() 是查看用户的身份类型

image-20251215140857815

配置文件中可以看到不同身份对应的数字

所以这里要求用户的身份类别对应的数字小于0,正常情况是2,所以我们继续分析系统是如何识别用户身份的

Auth::type()代码如下

1
2
3
public static function type() {
return self::$user['type'];
}

那么user数组是怎么来的呢

app\middlewares\Auth.php存在如下代码

1
2
3
4
5
6
7
8
9
10
public static function init() {
if (session_status() === PHP_SESSION_NONE) {
session_name(config('session.name'));
session_start();
}

if (isset($_SESSION['user_id'])) {
self::$user = User::findById($_SESSION['user_id']);
}
}

他是调用User::findById函数,根据session中的user_id来的,继续跟进findById函数

1
2
3
4
5
6
public static function findById($userId) {
return DB::table('user')
->leftJoin('photo', 'user.background_photo_id', '=', 'photo.id')
->where('user.id', '=', $userId)
->first();
}

这里会返回id匹配的第一条记录,同时还返回了背景图的信息,问题就出在这里

https://github.com/php/php-src/issues/20300

我们继续跟进分析一下原理

first函数

1
2
3
4
5
public function first() {
$this->limit(1);
$results = $this->get();
return !empty($results) ? $results[0] : null;
}

是通过get方法来获取结果的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function get() {
$sql = $this->buildSql();
$stmt = $this->db->prepare($sql);

foreach ($this->bindings as $key => $value) {
$type = is_int($value) ? SQLITE3_INTEGER : SQLITE3_TEXT;
$stmt->bindValue($key, $value, $type);
}

$result = $stmt->execute();
$rows = [];

while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$rows[] = $row;
}

return $rows;
}

问题出现在fetchArray(SQLITE3_ASSOC),根据php的官方文档

image-20251215141651172

SQLITE3_ASSOC返回的数组是按列名索引的,加上这里的join的user表和photo表存在相同的type列,那么后者就会覆盖前者的数据,而我们是可以上传照片的,接下来分析一下photo表中的type是怎么获取的image-20251215141951806

直接从上传的文件的type中获取

那么我们的思路就是上传一张type为-1的图片,并设置为背景图,虽然这里的type”-1”是字符串类型,但是php的弱类型毕竟是满足“-1”< 0的,最后访问superadmin即可

总结

这题考察了SQLite3Result::fetchArray处理join两个存在相同列名的表时的数据覆盖问题


RCTF2025-photographer
http://example.com/2025/12/15/RCTF2025-photographer/
作者
onehang
发布于
2025年12月15日
许可协议