glacier ctf 25
This commit is contained in:
9
2025/glacier/web/glacier-todo/Dockerfile
Normal file
9
2025/glacier/web/glacier-todo/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
# Debian 12
|
||||
# PHP 8.4.6
|
||||
# Apache 2.4.62
|
||||
FROM docker.io/library/php@sha256:abce8fe7e3390e96d3ac52dff8e6f5ff9507f4a7bee2b18f11404b74d7efec66
|
||||
|
||||
# Copy challenge required files
|
||||
COPY ./config/php.ini $PHP_INI_DIR/php.ini
|
||||
COPY ./web /var/www/html
|
||||
COPY ./flag.txt /flag.txt
|
||||
1961
2025/glacier/web/glacier-todo/config/php.ini
Normal file
1961
2025/glacier/web/glacier-todo/config/php.ini
Normal file
File diff suppressed because it is too large
Load Diff
35
2025/glacier/web/glacier-todo/deploy.bat
Normal file
35
2025/glacier/web/glacier-todo/deploy.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@echo off
|
||||
setlocal
|
||||
call :setESC
|
||||
|
||||
:req
|
||||
echo %ESC%[34m[+] Note: This script has only been tested for docker using WSL2. It might work with Hyper-V, but it was not tested.%ESC%[0m
|
||||
REM echo %ESC%[93m[+] Challenge Integrity is disabled for Windows%ESC%[0m
|
||||
where docker > NUL 2>&1
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
ECHO %ESC%[31m[+] docker command not found. Is docker installed?%ESC%[0m
|
||||
exit /B 1
|
||||
)
|
||||
docker ps >NUL 2>&1
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
ECHO %ESC%[31m[+] "docker ps" failed. Is docker running?%ESC%[0m
|
||||
exit /B 1
|
||||
)
|
||||
|
||||
|
||||
|
||||
:build
|
||||
echo %ESC%[34m[+] Building Challenge Container%ESC%[0m
|
||||
REM !!! THIS DOES NOT WORK IF YOU ARE IN A SYMLINKED FOLDER !!!
|
||||
docker build -t localhost/chall-glacier-todo --platform linux/amd64 --pull=true .
|
||||
|
||||
:run
|
||||
echo %ESC[34m[+] Running Challenge Container on 127.0.0.1:1337%ESC%[0m"
|
||||
docker run --name chall-glacier-todo --rm -p 127.0.0.1:1337:1337 -p 127.0.0.1:8080:8080 -e HOST=127.0.0.1 -e PORT=1337 -e PUBPORTSTART=11000 -e PUBPORTEND=15000 -e NAME=glacier-todo -e TIMEOUT=600 -e DOMAIN=localhost -e DOMAIN_PORT=8080 -e DOMAIN_PROT=http -e REGISTRY=localhost --privileged --platform linux/amd64 --pull=never localhost/chall-glacier-todo
|
||||
|
||||
:setESC
|
||||
for /F "tokens=1,2 delims=#" %%a in ('"prompt ### & echo on & for %%b in (1) do rem"') do (
|
||||
set ESC=%%b
|
||||
exit /B 0
|
||||
)
|
||||
exit /B 0
|
||||
51
2025/glacier/web/glacier-todo/deploy.sh
Executable file
51
2025/glacier/web/glacier-todo/deploy.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/bin/sh
|
||||
|
||||
check() {
|
||||
echo -e "\e[1;34m[+] Verifying Challenge Integrity\e[0m"
|
||||
sha256sum -c sha256sum
|
||||
}
|
||||
|
||||
build_container() {
|
||||
echo -e "\e[1;34m[+] Building Challenge Docker Container\e[0m"
|
||||
docker build -t localhost/chall-glacier-todo --platform linux/amd64 --pull=true .
|
||||
}
|
||||
|
||||
# Common error on default Ubuntu 24.04:
|
||||
#
|
||||
# initCloneNs():391 mount('/', '/', NULL, MS_REC|MS_PRIVATE, NULL): Permission denied
|
||||
# Change --user 1337:1337 to --user 0:0 in run_container()
|
||||
# or
|
||||
# $ sudo sysctl -w kernel.apparmor_restrict_unprivileged_unconfined=0
|
||||
# $ sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
||||
# and then restore them back when finished
|
||||
|
||||
run_container() {
|
||||
echo -e "\e[1;34m[+] Running Challenge Docker Container on 127.0.0.1:1337\e[0m"
|
||||
docker run --name chall-glacier-todo --rm -p 1337:80 localhost/chall-glacier-todo
|
||||
}
|
||||
|
||||
kill_container() {
|
||||
docker ps --filter "name=chall-glacier-todo" --format "{{.ID}}" \
|
||||
| tr '\n' ' ' \
|
||||
| xargs docker stop -t 0 \
|
||||
|| true
|
||||
}
|
||||
|
||||
case "${1}" in
|
||||
"check")
|
||||
check
|
||||
;;
|
||||
"build")
|
||||
build_container
|
||||
;;
|
||||
"run")
|
||||
run_container
|
||||
;;
|
||||
"kill")
|
||||
kill_container
|
||||
;;
|
||||
*)
|
||||
check
|
||||
build_container && run_container
|
||||
;;
|
||||
esac
|
||||
1
2025/glacier/web/glacier-todo/flag.txt
Normal file
1
2025/glacier/web/glacier-todo/flag.txt
Normal file
@@ -0,0 +1 @@
|
||||
gctf{IT_WOULD_BE_REALLY_STUPID_TO_NOT_TRY_OUT_IF_THIS_FLAG_IS_CORRECT}
|
||||
11
2025/glacier/web/glacier-todo/sha256sum
Normal file
11
2025/glacier/web/glacier-todo/sha256sum
Normal file
@@ -0,0 +1,11 @@
|
||||
82fcbc9281b0abf9401f216efe7c9e81e78cf9bcf184a424623509779c7de19e ./deploy.sh
|
||||
8147c53c32ade1451cc1dfaf52eaf8f1fe88ae0bdd6eef27f7120256094a69db ./deploy.bat
|
||||
2533da00f66b1cf18430b08871262afa12f58cf2ee43b2077740c9c3972bb980 ./config/php.ini
|
||||
e5538bfc30e2b06a53a8362c1bdec3c89fc2851847588fad084eed609ab35491 ./web/index.html
|
||||
6416c3446cd37ff7d158794f3266458d2ed19ee56ecd5c05f167704e6087f11f ./web/api.php
|
||||
105b3b42d4c7dccd85d776dcb47f2db51e95185a9394f0f150b84494e40e7691 ./web/assets/img/bg.jpg
|
||||
333bbde178ad17fe6bf64916ab790315fd96ff84c0e4df057a4994749d09d595 ./web/assets/js/app.js
|
||||
3e0ccc2e71ad0494b29b88b89daa77b1517efefeb86d718023b19ed54170c918 ./web/assets/js/api.js
|
||||
61cb273217adca3a58bd7942a37621e02d16f06f687f7194a5528ebf8fe1d217 ./web/assets/css/style.css
|
||||
dad3ca5cd81789d651a7dd3f7f2f65e830be0f0c1df26a5bb354cbd3b626fd17 ./Dockerfile
|
||||
a1789ea1c25dd7c2249dbdf3cca873220afb0b06eafdc0426c2574c16731759f ./flag.txt
|
||||
89
2025/glacier/web/glacier-todo/web/api.php
Normal file
89
2025/glacier/web/glacier-todo/web/api.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
session_start();
|
||||
header("Content-Type: application/json");
|
||||
$start = microtime(true);
|
||||
|
||||
define("TODOS", "/tmp/todos");
|
||||
define("USERS", "/tmp/users.json");
|
||||
define("SESS", "LOGIN_ID");
|
||||
if(!file_exists(USERS)) file_put_contents(USERS, "[]");
|
||||
if(!is_dir(TODOS)) mkdir(TODOS);
|
||||
|
||||
$res = array();
|
||||
$data = array();
|
||||
$status = 1;
|
||||
$path = $_GET["path"];
|
||||
|
||||
if($path === "/todos/list") {
|
||||
$isLoggedIn = isset($_SESSION[SESS]);
|
||||
if(!$isLoggedIn) goto fail;
|
||||
$user = $_SESSION[SESS];
|
||||
if(!file_exists(TODOS . "/" . $user)) file_put_contents(TODOS . "/" . $user, "[]");
|
||||
$todos = json_decode(file_get_contents(TODOS . "/" . $user), true);
|
||||
$data["todos"] = array_values($todos);
|
||||
} elseif($path === "/todos/add") {
|
||||
$isLoggedIn = isset($_SESSION[SESS]);
|
||||
if(!$isLoggedIn) goto fail;
|
||||
$user = $_SESSION[SESS];
|
||||
if(!file_exists(TODOS . "/" . $user)) file_put_contents(TODOS . "/" . $user, "[]");
|
||||
$todos = json_decode(file_get_contents(TODOS . "/" . $user));
|
||||
$name = isset($_POST["name"]) ? filter_input(INPUT_POST, "name") : '';
|
||||
$desc = isset($_POST["desc"]) ? filter_input(INPUT_POST, "desc") : '';
|
||||
$todos[] = array(
|
||||
"id" => uniqid(),
|
||||
"name" => $name,
|
||||
"desc" => $desc
|
||||
);
|
||||
file_put_contents(TODOS . "/" . $user, json_encode(array_values($todos)));
|
||||
} elseif($path === "/todos/remove") {
|
||||
$isLoggedIn = isset($_SESSION[SESS]);
|
||||
if(!$isLoggedIn) goto fail;
|
||||
$user = $_SESSION[SESS];
|
||||
if(!file_exists(TODOS . "/" . $user)) file_put_contents(TODOS . "/" . $user, "[]");
|
||||
$todos = json_decode(file_get_contents(TODOS . "/" . $user));
|
||||
$id = isset($_POST["id"]) ? filter_input(INPUT_POST, "id") : '';
|
||||
file_put_contents(TODOS . "/" . $user, json_encode(array_values(array_filter($todos, function($item) use($id) {
|
||||
return $item->id !== $id;
|
||||
}))));
|
||||
} elseif($path === "/account/info") {
|
||||
$isLoggedIn = isset($_SESSION[SESS]);
|
||||
$data["loggedIn"] = $isLoggedIn;
|
||||
$data["username"] = $_SESSION[SESS];
|
||||
} elseif($path === "/account/login") {
|
||||
$username = isset($_POST["username"]) ? filter_input(INPUT_POST, "username") : '';
|
||||
$password = isset($_POST["password"]) ? filter_input(INPUT_POST, "password") : '';
|
||||
$users = json_decode(file_get_contents(USERS));
|
||||
foreach($users as $user)
|
||||
if($user->username === $username && password_verify($password, $user->password))
|
||||
goto valid_login;
|
||||
goto fail;
|
||||
valid_login:
|
||||
$_SESSION[SESS] = $username;
|
||||
} elseif($path === "/account/register") {
|
||||
$username = isset($_POST["username"]) ? filter_input(INPUT_POST, "username") : '';
|
||||
$password = isset($_POST["password"]) ? filter_input(INPUT_POST, "password") : '';
|
||||
$users = json_decode(file_get_contents(USERS));
|
||||
foreach($users as $user)
|
||||
if($user->username === $username) goto fail;
|
||||
|
||||
$users[] = array(
|
||||
"username" => $username,
|
||||
"password" => password_hash($password, PASSWORD_DEFAULT)
|
||||
);
|
||||
file_put_contents(USERS, json_encode($users));
|
||||
} else {
|
||||
http_response_code(404);
|
||||
}
|
||||
|
||||
goto end;
|
||||
fail:
|
||||
http_response_code(500);
|
||||
$status = 0;
|
||||
end:
|
||||
$end = microtime(true);
|
||||
$res["data"] = $data;
|
||||
$res["success"] = $status;
|
||||
$res["exec_time"] = $end - $start;
|
||||
echo json_encode($res);
|
||||
|
||||
81
2025/glacier/web/glacier-todo/web/assets/css/style.css
Normal file
81
2025/glacier/web/glacier-todo/web/assets/css/style.css
Normal file
@@ -0,0 +1,81 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Segoe UI', sans-serif;
|
||||
background: linear-gradient(to bottom, #e0f7fa, #80deea);
|
||||
background-image: url('/assets/img/bg.jpg');
|
||||
background-size: cover;
|
||||
color: #fff;
|
||||
min-height: 100vh;
|
||||
backdrop-filter: blur(0px);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 5rem auto;
|
||||
background: rgba(0, 60, 100, 0.7);
|
||||
padding: 2rem;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 0.7rem;
|
||||
margin: 0.5rem 0;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
background: #4dd0e1;
|
||||
color: #003c64;
|
||||
border: none;
|
||||
padding: 0.7rem 1.2rem;
|
||||
margin: 0.5rem 0.2rem;
|
||||
font-size: 1rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #00acc1;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
margin: 0.5rem 0;
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todo-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.auth-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
BIN
2025/glacier/web/glacier-todo/web/assets/img/bg.jpg
Normal file
BIN
2025/glacier/web/glacier-todo/web/assets/img/bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 396 KiB |
52
2025/glacier/web/glacier-todo/web/assets/js/api.js
Normal file
52
2025/glacier/web/glacier-todo/web/assets/js/api.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const API = new class {
|
||||
register(username, password) {
|
||||
const formData = new FormData();
|
||||
formData.append("username", username)
|
||||
formData.append("password", password)
|
||||
return fetch("/api.php?path=" + encodeURIComponent("/account/register"), {
|
||||
method: "POST",
|
||||
body: formData
|
||||
})
|
||||
}
|
||||
|
||||
login(username, password) {
|
||||
const formData = new FormData();
|
||||
formData.append("username", username)
|
||||
formData.append("password", password)
|
||||
return fetch("/api.php?path=" + encodeURIComponent("/account/login"), {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
})
|
||||
}
|
||||
|
||||
info() {
|
||||
return fetch("/api.php?path=" + encodeURIComponent("/account/info"), {
|
||||
method: "GET",
|
||||
})
|
||||
}
|
||||
|
||||
list() {
|
||||
return fetch("/api.php?path=" + encodeURIComponent("/todos/list"), {
|
||||
method: "GET",
|
||||
})
|
||||
}
|
||||
|
||||
add(name, desc) {
|
||||
const formData = new FormData();
|
||||
formData.append("name", name)
|
||||
formData.append("desc", desc)
|
||||
return fetch("/api.php?path=" + encodeURIComponent("/todos/add"), {
|
||||
method: "POST",
|
||||
body: formData
|
||||
})
|
||||
}
|
||||
|
||||
remove(id) {
|
||||
const formData = new FormData();
|
||||
formData.append("id", id)
|
||||
return fetch("/api.php?path=" + encodeURIComponent("/todos/remove"), {
|
||||
method: "POST",
|
||||
body: formData
|
||||
})
|
||||
}
|
||||
}()
|
||||
79
2025/glacier/web/glacier-todo/web/assets/js/app.js
Normal file
79
2025/glacier/web/glacier-todo/web/assets/js/app.js
Normal file
@@ -0,0 +1,79 @@
|
||||
function register() {
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
API.register(username, password)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
alert('Registered successfully!');
|
||||
}).catch(err => alert('Registration failed'));
|
||||
}
|
||||
|
||||
function login() {
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
API.login(username, password)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
loadApp();
|
||||
}).catch(err => alert('Login failed'));
|
||||
}
|
||||
|
||||
function loadApp() {
|
||||
API.info().then(res => res.json()).then(user => {
|
||||
document.getElementById('auth').classList.add('hidden');
|
||||
document.getElementById('todoApp').classList.remove('hidden');
|
||||
document.getElementById('userDisplay').innerText = user.data.username;
|
||||
loadTodos();
|
||||
}).catch(err => alert('Session expired or unauthorized'));
|
||||
}
|
||||
|
||||
function loadTodos() {
|
||||
API.list()
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
const todos = data.data.todos;
|
||||
const list = document.getElementById('todoList');
|
||||
list.innerHTML = '';
|
||||
todos.forEach(todo => {
|
||||
const li = document.createElement('li');
|
||||
li.innerHTML = `
|
||||
<div>
|
||||
<strong>${todo.name}</strong><br>
|
||||
<small>${todo.desc}</small>
|
||||
</div>
|
||||
<button onclick="deleteTodo('${todo.id}')">🗑</button>
|
||||
`;
|
||||
list.appendChild(li);
|
||||
});
|
||||
}).catch(err => alert('Failed to load todos'));
|
||||
}
|
||||
|
||||
function addTodo() {
|
||||
const name = document.getElementById('todoName').value;
|
||||
const desc = document.getElementById('todoDesc').value;
|
||||
API.add(name, desc)
|
||||
.then(res => res.json())
|
||||
.then(() => {
|
||||
document.getElementById('todoName').value = '';
|
||||
document.getElementById('todoDesc').value = '';
|
||||
loadTodos();
|
||||
}).catch(err => alert('Failed to add todo'));
|
||||
}
|
||||
|
||||
function deleteTodo(id) {
|
||||
API.remove(id)
|
||||
.then(res => res.json())
|
||||
.then(() => {
|
||||
loadTodos();
|
||||
}).catch(err => alert('Failed to delete todo'));
|
||||
}
|
||||
|
||||
// Try auto-login
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
API.info()
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.data.loggedIn) loadApp();
|
||||
});
|
||||
});
|
||||
|
||||
38
2025/glacier/web/glacier-todo/web/index.html
Normal file
38
2025/glacier/web/glacier-todo/web/index.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Glacier TODO App</title>
|
||||
<link rel="stylesheet" href="./assets/css/style.css">
|
||||
<script defer src="./assets/js/api.js"></script>
|
||||
<script defer src="./assets/js/app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🗻 Glacier TODO</h1>
|
||||
|
||||
<div id="auth">
|
||||
<input type="text" id="username" placeholder="Username">
|
||||
<input type="password" id="password" placeholder="Password">
|
||||
<div class="auth-buttons">
|
||||
<button onclick="register()">Register</button>
|
||||
<button onclick="login()">Login</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="todoApp" class="hidden">
|
||||
<div class="todo-header">
|
||||
<h2>Welcome, <span id="userDisplay"></span>!</h2>
|
||||
</div>
|
||||
|
||||
<div class="add-todo">
|
||||
<input type="text" id="todoName" placeholder="Todo title">
|
||||
<input type="text" id="todoDesc" placeholder="Todo description">
|
||||
<button onclick="addTodo()">Add</button>
|
||||
</div>
|
||||
|
||||
<ul id="todoList"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user