CTF-WEB排行榜制作
CTF-WEB排行榜制作
项目需求:
现在14道题对应有14个flag,我需要使用dockerfile搭建一个简单的,能够实现验证这些题目对应的flag来计分的简单网站(要求页面比较精美)
前十题设置为10分
11-14题设置为20分
1. flag{5a3dR7vKpQ9wXyZ2}
2. flag{8G4hLbNqFcTjMnP6}
3. flag{3V9sEkYmWuHxDrz1}
4. flag{J7r2tLqZfNpD4mKs}
5. flag{B6nQwRvXcT3yUh9P}
6. flag{qW4eF8gHjK2lO7iU}
7. flag{M9aZsEdCvFbRtY3N}
8. flag{2Lp5oIuG7yTqW1rD}
9. flag{R4vNkSmXzHcJ8wB3}
10. flag{6dFgHjKl9pOiU7yT}
11. flag{W3zYcT8rDxV5sQmK}
12. flag{9PnZtLwEfR6vGhJ4}
13. flag{7UkXbMqAsHdF2iN1}
14. flag{yT4jKmP9oLvRgS8e}
目录结构打印(项目根目录为ctf):
ctf/
├── docker-compose.yml
├── init.sql
└── web/
├── Dockerfile
├── flags.php
├── index.php
├── leaderboard.php
├── submit.php
├── style.css
└── db.php
创建目录结构命令:
# 创建目录结构与文件
# 切换到工作目录(例如 /root 或 ~/)
mkdir -p ~/ctf && cd ~/ctf
# 创建顶层目录和文件
mkdir -p ctf/web
touch ctf/docker-compose.yml
touch ctf/init.sql
# 创建 web 子目录下的文件
touch ctf/web/Dockerfile
touch ctf/web/flags.php
touch ctf/web/index.php
touch ctf/web/leaderboard.php
touch ctf/web/submit.php
touch ctf/web/style.css
touch ctf/web/db.php
tree ctf
flags.php
:题目flag和分值配置(包含前五奖励逻辑支持)
init.sql
:MySQL建表与初始化
submit.php
:处理flag提交与写入逻辑
leaderboard.php
:统计并展示排行榜
index.php
:主页面
Dockerfile
+ docker-compose.yml
:构建环境配置
ctf/web/flags.php
cat > ctf/web/flags.php <<EOF
<?php
// 已整合到 submit.php 逻辑中
// 此文件保留用于后期扩展
header("Location: index.php");
exit();
?>
EOF
ctf/init.sql
cat > ctf/init.sql<<EOF
CREATE TABLE IF NOT EXISTS challenges (
id INT PRIMARY KEY AUTO_INCREMENT,
flag VARCHAR(50) UNIQUE NOT NULL,
score INT NOT NULL,
solved_count INT DEFAULT 0,
first_five JSON DEFAULT NULL
);
CREATE TABLE IF NOT EXISTS users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
total_score INT DEFAULT 0
);
CREATE TABLE IF NOT EXISTS submissions (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
challenge_id INT NOT NULL,
submit_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_correct BOOLEAN,
is_in_top5 BOOLEAN DEFAULT FALSE,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (challenge_id) REFERENCES challenges(id)
);
-- 初始化题目数据
INSERT INTO challenges (flag, score) VALUES
('flag{5a3dR7vKpQ9wXyZ2}', 10),
('flag{8G4hLbNqFcTjMnP6}', 10),
('flag{3V9sEkYmWuHxDrz1}', 10),
('flag{J7r2tLqZfNpD4mKs}', 10),
('flag{B6nQwRvXcT3yUh9P}', 10),
('flag{qW4eF8gHjK2lO7iU}', 10),
('flag{M9aZsEdCvFbRtY3N}', 10),
('flag{2Lp5oIuG7yTqW1rD}', 10),
('flag{R4vNkSmXzHcJ8wB3}', 10),
('flag{6dFgHjKl9pOiU7yT}', 10),
('flag{W3zYcT8rDxV5sQmK}', 20),
('flag{9PnZtLwEfR6vGhJ4}', 20),
('flag{7UkXbMqAsHdF2iN1}', 20),
('flag{yT4jKmP9oLvRgS8e}', 20);
EOF
ctf/docker-compose.yml
cat > ctf/docker-compose.yml <<EOF
version: '3.8'
services:
web:
build: ./web
ports:
- "80:80"
depends_on:
- mysql
environment:
MYSQL_HOST: mysql
networks:
- ctf-net
mysql:
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD: ctf123
MYSQL_DATABASE: ctf_db
MYSQL_USER: ctf_user
MYSQL_PASSWORD: ctf_pass
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
- mysql-data:/var/lib/mysql
networks:
- ctf-net
restart: always
volumes:
mysql-data:
networks:
ctf-net:
EOF
ctf/web/Dockerfile
cat > ctf/web/Dockerfile <<'EOF'
FROM php:7.4-apache
# 使用阿里云镜像源
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list
# 安装扩展并配置
RUN apt-get update && \
apt-get install -y libzip-dev libpng-dev && \
docker-php-ext-install pdo_mysql zip gd && \
a2enmod rewrite
# 配置PHP
RUN echo "upload_max_filesize = 10M" >> /usr/local/etc/php/php.ini && \
echo "post_max_size = 10M" >> /usr/local/etc/php/php.ini
COPY . /var/www/html/
EOF
ctf/web/submit.php
cat > ctf/web/submit.php <<'EOF'
<?php
require 'db.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username']);
$flag = trim($_POST['flag']);
// 验证输入
if (empty($username) || empty($flag)) {
die("用户名和Flag不能为空");
}
try {
$pdo->beginTransaction();
// 获取用户或创建用户
$stmt = $pdo->prepare("INSERT IGNORE INTO users (username) VALUES (?)");
$stmt->execute([$username]);
$user_id = $pdo->lastInsertId();
if ($user_id == 0) {
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$username]);
$user_id = $stmt->fetchColumn();
}
// 验证Flag
$stmt = $pdo->prepare("SELECT id, score FROM challenges WHERE flag = ?");
$stmt->execute([$flag]);
$challenge = $stmt->fetch(PDO::FETCH_ASSOC);
if ($challenge) {
// 检查是否重复提交
$stmt = $pdo->prepare("SELECT id FROM submissions
WHERE user_id = ? AND challenge_id = ?");
$stmt->execute([$user_id, $challenge['id']]);
if ($stmt->fetch()) {
die("请勿重复提交相同Flag");
}
// 更新题目解决数
$pdo->prepare("UPDATE challenges SET solved_count = solved_count + 1 WHERE id = ?")
->execute([$challenge['id']]);
// 计算奖励分
$stmt = $pdo->prepare("SELECT COUNT(*) FROM submissions
WHERE challenge_id = ? AND is_correct = 1");
$stmt->execute([$challenge['id']]);
$current_solved = $stmt->fetchColumn();
$bonus = 0;
if ($current_solved < 5) {
$bonus = 5 - $current_solved;
}
// 更新用户积分
$total_score = $challenge['score'] + $bonus;
$pdo->prepare("UPDATE users SET total_score = total_score + ? WHERE id = ?")
->execute([$total_score, $user_id]);
// 记录提交
$stmt = $pdo->prepare("INSERT INTO submissions
(user_id, challenge_id, is_correct, is_in_top5)
VALUES (?, ?, 1, ?)");
$stmt->execute([$user_id, $challenge['id'], ($bonus > 0 ? 1 : 0)]);
echo "提交成功!获得积分:$total_score";
} else {
// 记录错误提交
$pdo->prepare("INSERT INTO submissions (user_id, is_correct) VALUES (?, 0)")
->execute([$user_id]);
echo "Flag错误!";
}
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
die("系统错误:" . $e->getMessage());
}
}
?>
EOF
ctf/web/leaderboard.php
cat > ctf/web/leaderboard.php <<'EOF'
<?php require 'db.php'; ?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>排行榜</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>实时排行榜</h1>
<table class="leaderboard">
<thead>
<tr>
<th>排名</th>
<th>用户名</th>
<th>总分</th>
<th>最后提交</th>
</tr>
</thead>
<tbody>
<?php
$stmt = $pdo->query("
SELECT u.username, u.total_score, MAX(s.submit_time) as last_submit
FROM users u
LEFT JOIN submissions s ON u.id = s.user_id
GROUP BY u.id
ORDER BY u.total_score DESC, last_submit ASC
LIMIT 50
");
$rank = 1;
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo "<tr>";
echo "<td>#$rank</td>";
echo "<td>{$row['username']}</td>";
echo "<td>{$row['total_score']}</td>";
echo "<td>" . date('Y-m-d H:i', strtotime($row['last_submit'])) . "</td>";
echo "</tr>";
$rank++;
}
?>
</tbody>
</table>
<div class="back-link">
<a href="index.php">← 返回挑战列表</a>
</div>
</div>
</body>
</html>
EOF
ctf/web/index.php
cat > ctf/web/index.php <<'EOF'
<?php require 'db.php'; ?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>CTF挑战平台</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<h1>CTF挑战列表</h1>
<div class="challenge-list">
<?php
$stmt = $pdo->query("SELECT * FROM challenges");
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$points = $row['score'];
echo "<div class='challenge'>";
echo "<span class='challenge-id'>题目 {$row['id']}</span>";
echo "<span class='points'>分值:{$points}</span>";
echo "<span class='solved'>已解出:{$row['solved_count']}次</span>";
echo "</div>";
}
?>
</div>
<div class="submit-form">
<h2>提交Flag</h2>
<form action="submit.php" method="POST">
<input type="text" name="username" placeholder="用户名" required>
<input type="text" name="flag" placeholder="输入Flag" required>
<button type="submit">提交</button>
</form>
</div>
<div class="leaderboard-link">
<a href="leaderboard.php">查看实时排行榜 →</a>
</div>
</div>
</body>
</html>
EOF
CTF/web/db.php
cat > ctf/web/db.php <<EOF
<?php
$host = 'mysql';
$dbname = 'ctf_db';
$user = 'ctf_user';
$pass = 'ctf_pass';
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
?>
EOF
CTF/web/style.css
cat > ctf/web/style.css <<EOF
/* 基础样式 */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f7fa;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
/* 挑战列表样式 */
.challenge-list {
margin-bottom: 40px;
}
.challenge {
background: #f8f9fc;
padding: 15px 20px;
margin: 10px 0;
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
transition: transform 0.2s;
}
.challenge:hover {
transform: translateX(10px);
}
.challenge-id {
font-weight: 600;
color: #2d3748;
}
.points {
color: #48bb78;
font-weight: bold;
}
.solved {
color: #718096;
}
/* 提交表单样式 */
.submit-form {
background: #f8f9fc;
padding: 25px;
border-radius: 8px;
margin-top: 30px;
}
.submit-form input[type="text"] {
width: 250px;
padding: 12px;
margin-right: 10px;
border: 2px solid #e2e8f0;
border-radius: 6px;
font-size: 16px;
}
.submit-form button {
background: #4299e1;
color: white;
border: none;
padding: 12px 25px;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
transition: background 0.3s;
}
.submit-form button:hover {
background: #3182ce;
}
/* 排行榜样式 */
.leaderboard {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.leaderboard th, .leaderboard td {
padding: 15px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
}
.leaderboard th {
background: #4299e1;
color: white;
}
.leaderboard tr:nth-child(even) {
background: #f8fafc;
}
.back-link {
margin-top: 25px;
text-align: center;
}
.back-link a {
color: #4299e1;
text-decoration: none;
font-weight: 500;
}
.leaderboard-link {
text-align: center;
margin-top: 25px;
}
.leaderboard-link a {
color: #48bb78;
text-decoration: none;
font-weight: 600;
font-size: 1.1em;
}
EOF
构建和运行命令:
cd ctf
docker-compose build
docker-compose up -d
访问测试:
浏览器打开 http://localhost
常见问题解决:
- 如果遇到权限问题,在命令前加sudo
- 如果端口冲突,修改docker-compose.yml中的端口映射为 “8080:80”
- 清除环境使用:docker-compose down -v
- 查看日志:docker-compose logs -f
以上命令会:
- 创建完整的目录结构
- 生成所有配置文件
- 构建PHP+Apache镜像
- 启动MySQL数据库
- 自动初始化数据库结构
- 启动网站服务
- 自动设置好题目flag和评分规则