Skip to content

Commit

Permalink
Rebuilt the PwnedAdmin application.
Browse files Browse the repository at this point in the history
  • Loading branch information
lanmaster53 committed May 11, 2024
1 parent 2c3c0a2 commit 8638f77
Show file tree
Hide file tree
Showing 14 changed files with 205 additions and 136 deletions.
2 changes: 1 addition & 1 deletion adminbot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def log_in(self, email):
login_button.click()

self.debug('Fetching the Passwordless Authentication code.')
inbox_url = 'http://admin.pwnedhub.com/inbox/'
inbox_url = 'http://admin.pwnedhub.com/inbox/[email protected]'
import urllib.request
contents = urllib.request.urlopen(inbox_url).read().decode()
match = re.search(r'<br><br>(\d{6})<br><br>', contents)
Expand Down
62 changes: 62 additions & 0 deletions common/static/js/tailwind-3.4.3.min.js

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion database/cs/04-pwnedhub-admin.sql
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ DROP TABLE IF EXISTS `configs`;
CREATE TABLE `configs` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`description` text NOT NULL,
`type` varchar(255) NOT NULL,
`value` tinyint(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Expand All @@ -39,7 +41,7 @@ CREATE TABLE `configs` (

LOCK TABLES `configs` WRITE;
/*!40000 ALTER TABLE `configs` DISABLE KEYS */;
INSERT INTO `configs` VALUES (1,'CSRF_PROTECT',1),(2,'BEARER_AUTH_ENABLE',1),(3,'CORS_RESTRICT',1),(4,'OIDC_ENABLE',0),(5,'OSCI_PROTECT',0),(6,'SQLI_PROTECT',0),(7,'CTF_MODE',0),(8,'SSO_ENABLE',0),(9,'JWT_VERIFY',1),(10,'JWT_ENCRYPT',0),(11,'OOB_RESET_ENABLE',0);
INSERT INTO `configs` VALUES (1,'CSRF_PROTECT','Profile CSRF Protection (PwnedHub)','security control',1),(2,'OSCI_PROTECT','Tools OSCI Protection (PwnedHub)','security control',0),(3,'SQLI_PROTECT','Login SQLi Protection (PwnedHub)','security control',0),(4,'CORS_RESTRICT','Restricted CORS (PwnedAPI)','security control',1),(5,'JWT_VERIFY','Verify JWT Signatures (PwnedAPI)','security control',1),(6,'JWT_ENCRYPT','Encrypt JWTs (PwnedAPI)','security control',0),(7,'BEARER_AUTH_ENABLE','Bearer Token Authentication (PwnedAPI)','feature',1),(8,'OIDC_ENABLE','OpenID Connect Authentication (PwnedHub)','feature',0),(9,'SSO_ENABLE','SSO Authentication (PwnedHub)','feature',0),(10,'OOB_RESET_ENABLE','Out-of-Band Password Reset (PwnedHub)','feature',0),(11,'CTF_MODE','CTF Mode (Warning: Disables this interface!)','feature',0);
/*!40000 ALTER TABLE `configs` ENABLE KEYS */;
UNLOCK TABLES;

Expand Down
4 changes: 3 additions & 1 deletion database/ctf/04-pwnedhub-admin.sql
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ DROP TABLE IF EXISTS `configs`;
CREATE TABLE `configs` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`description` text NOT NULL,
`type` varchar(255) NOT NULL,
`value` tinyint(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Expand All @@ -39,7 +41,7 @@ CREATE TABLE `configs` (

LOCK TABLES `configs` WRITE;
/*!40000 ALTER TABLE `configs` DISABLE KEYS */;
INSERT INTO `configs` VALUES (1,'CSRF_PROTECT',0),(2,'BEARER_AUTH_ENABLE',0),(3,'CORS_RESTRICT',0),(4,'OIDC_ENABLE',0),(5,'OSCI_PROTECT',1),(6,'SQLI_PROTECT',1),(7,'CTF_MODE',1),(8,'SSO_ENABLE',0),(9,'JWT_VERIFY',1),(10,'JWT_ENCRYPT',0),(11,'OOB_RESET_ENABLE',1);
INSERT INTO `configs` VALUES (1,'CSRF_PROTECT','Profile CSRF Protection (PwnedHub)','security control',0),(2,'OSCI_PROTECT','Tools OSCI Protection (PwnedHub)','security control',1),(3,'SQLI_PROTECT','Login SQLi Protection (PwnedHub)','security control',1),(4,'CORS_RESTRICT','Restricted CORS (PwnedAPI)','security control',0),(5,'JWT_VERIFY','Verify JWT Signatures (PwnedAPI)','security control',1),(6,'JWT_ENCRYPT','Encrypt JWTs (PwnedAPI)','security control',0),(7,'BEARER_AUTH_ENABLE','Bearer Token Authentication (PwnedAPI)','feature',0),(8,'OIDC_ENABLE','OpenID Connect Authentication (PwnedHub)','feature',0),(9,'SSO_ENABLE','SSO Authentication (PwnedHub)','feature',0),(10,'OOB_RESET_ENABLE','Out-of-Band Password Reset (PwnedHub)','feature',1),(11,'CTF_MODE','CTF Mode (Warning: Disables this interface!)','feature',1);
/*!40000 ALTER TABLE `configs` ENABLE KEYS */;
UNLOCK TABLES;

Expand Down
4 changes: 3 additions & 1 deletion database/init/04-pwnedhub-admin.sql
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ DROP TABLE IF EXISTS `configs`;
CREATE TABLE `configs` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`description` text NOT NULL,
`type` varchar(255) NOT NULL,
`value` tinyint(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Expand All @@ -39,7 +41,7 @@ CREATE TABLE `configs` (

LOCK TABLES `configs` WRITE;
/*!40000 ALTER TABLE `configs` DISABLE KEYS */;
INSERT INTO `configs` VALUES (1,'CSRF_PROTECT',1),(2,'BEARER_AUTH_ENABLE',1),(3,'CORS_RESTRICT',1),(4,'OIDC_ENABLE',0),(5,'OSCI_PROTECT',0),(6,'SQLI_PROTECT',0),(7,'CTF_MODE',0),(8,'SSO_ENABLE',0),(9,'JWT_VERIFY',1),(10,'JWT_ENCRYPT',0),(11,'OOB_RESET_ENABLE',0);
INSERT INTO `configs` VALUES (1,'CSRF_PROTECT','Profile CSRF Protection (PwnedHub)','security control',1),(2,'OSCI_PROTECT','Tools OSCI Protection (PwnedHub)','security control',0),(3,'SQLI_PROTECT','Login SQLi Protection (PwnedHub)','security control',0),(4,'CORS_RESTRICT','Restricted CORS (PwnedAPI)','security control',1),(5,'JWT_VERIFY','Verify JWT Signatures (PwnedAPI)','security control',1),(6,'JWT_ENCRYPT','Encrypt JWTs (PwnedAPI)','security control',0),(7,'BEARER_AUTH_ENABLE','Bearer Token Authentication (PwnedAPI)','feature',1),(8,'OIDC_ENABLE','OpenID Connect Authentication (PwnedHub)','feature',0),(9,'SSO_ENABLE','SSO Authentication (PwnedHub)','feature',0),(10,'OOB_RESET_ENABLE','Out-of-Band Password Reset (PwnedHub)','feature',0),(11,'CTF_MODE','CTF Mode (Warning: Disables this interface!)','feature',0);
/*!40000 ALTER TABLE `configs` ENABLE KEYS */;
UNLOCK TABLES;

Expand Down
18 changes: 18 additions & 0 deletions pwnedadmin/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
RESTRICTED_USERS = [
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
'[email protected]',
]

class ConfigTypes:
CONTROL = 'security control'
FEATURE = 'feature'

def __init__(self):
pass

@property
def serialized(self):
return [self.CONTROL, self.FEATURE]
11 changes: 11 additions & 0 deletions pwnedadmin/models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from pwnedadmin import db
from pwnedadmin.constants import RESTRICTED_USERS
from pwnedadmin.utils import get_current_utc_time, get_local_from_utc


class Config(db.Model):
__tablename__ = 'configs'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False)
description = db.Column(db.Text, nullable=False)
type = db.Column(db.String(255), nullable=False)
value = db.Column(db.Boolean, nullable=False)

@staticmethod
Expand Down Expand Up @@ -33,5 +36,13 @@ class Email(db.Model):
def created_as_string(self):
return get_local_from_utc(self.created).strftime("%Y-%m-%d %H:%M:%S")

@staticmethod
def get_unrestricted():
return Email.query.filter(Email.receiver.notin_(RESTRICTED_USERS))

@staticmethod
def get_by_receiver(receiver):
return Email.query.filter_by(receiver=receiver)

def __repr__(self):
return "<Email '{}'>".format(self.id)
102 changes: 29 additions & 73 deletions pwnedadmin/templates/config.html
Original file line number Diff line number Diff line change
@@ -1,74 +1,30 @@
<!DOCTYPE HTML>
<html>
<head>
<title>PwnedAdmin | Config</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>html{visibility:hidden;opacity:0;}</style><!-- FOUC fix 1/2 -->
<link rel="stylesheet" type="text/css" href="{{ url_for('common.static', filename='css/fontawesome.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('common.static', filename='css/skeleton-flexbox.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('common.static', filename='css/custom-flex.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('common.static', filename='css/custom-utility.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('common.static', filename='css/pwnedhub.css') }}">
<style>html{visibility:visible;opacity:1;}</style><!-- FOUC fix 2/2 -->
</head>
<body class="flex-column">
<section class="flex-grow flex-row">
<div class="flex-grow container-fluid config flex-column">
<form action="{{ url_for('config.index') }}" method="post">
<div class="panel rounded">
<div>Security Controls</div>
<hr>
<label for="csrf_protect">
<input name="csrf_protect" type="checkbox" onchange="this.form.submit()" {% if app_config('CSRF_PROTECT') %}checked {% endif %}/>
<span>Profile CSRF Protection (PwnedHub) - {% if not app_config('CSRF_PROTECT') %}<span class="red">DISABLED</span>{% else %}<span class="green">ENABLED</span>{% endif %}</span>
</label>
<label for="osci_protect">
<input name="osci_protect" type="checkbox" onchange="this.form.submit()" {% if app_config('OSCI_PROTECT') %}checked {% endif %}/>
<span>Tools OSCI Protection (PwnedHub) - {% if not app_config('OSCI_PROTECT') %}<span class="red">DISABLED</span>{% else %}<span class="green">ENABLED</span>{% endif %}</span>
</label>
<label for="sqli_protect">
<input name="sqli_protect" type="checkbox" onchange="this.form.submit()" {% if app_config('SQLI_PROTECT') %}checked {% endif %}/>
<span>Login SQLi Protection (PwnedHub) - {% if not app_config('SQLI_PROTECT') %}<span class="red">DISABLED</span>{% else %}<span class="green">ENABLED</span>{% endif %}</span>
</label>
<label for="cors_restrict">
<input name="cors_restrict" type="checkbox" onchange="this.form.submit()" {% if app_config('CORS_RESTRICT') %}checked {% endif %}/>
<span>Restricted CORS (PwnedAPI) - {% if not app_config('CORS_RESTRICT') %}<span class="red">DISABLED</span>{% else %}<span class="green">ENABLED</span>{% endif %}</span>
</label>
<label for="jwt_verify">
<input name="jwt_verify" type="checkbox" onchange="this.form.submit()" {% if app_config('JWT_VERIFY') %}checked {% endif %}/>
<span>Verify JWT Signatures (PwnedAPI) - {% if not app_config('JWT_VERIFY') %}<span class="red">DISABLED</span>{% else %}<span class="green">ENABLED</span>{% endif %}</span>
</label>
<label for="jwt_encrypt">
<input name="jwt_encrypt" type="checkbox" onchange="this.form.submit()" {% if app_config('JWT_ENCRYPT') %}checked {% endif %}/>
<span>Encrypt JWTs (PwnedAPI) - {% if not app_config('JWT_ENCRYPT') %}<span class="red">DISABLED</span>{% else %}<span class="green">ENABLED</span>{% endif %}</span>
</label>
</div>
<div class="panel rounded">
<div>Features</div>
<hr>
<label for="bearer_auth_enable">
<input name="bearer_auth_enable" type="checkbox" onchange="this.form.submit()" {% if app_config('BEARER_AUTH_ENABLE') %}checked {% endif %}/>
<span>Bearer Token Authentication (PwnedAPI) - {% if not app_config('BEARER_AUTH_ENABLE') %}<span class="red">DISABLED</span>{% else %}<span class="green">ENABLED</span>{% endif %}</span>
</label>
<label for="oidc_enable">
<input name="oidc_enable" type="checkbox" onchange="this.form.submit()" {% if app_config('OIDC_ENABLE') %}checked {% endif %}/>
<span>OpenID Connect Authentication (PwnedHub) - {% if not app_config('OIDC_ENABLE') %}<span class="red">DISABLED</span>{% else %}<span class="green">ENABLED</span>{% endif %}</span>
</label>
<label for="sso_enable">
<input name="sso_enable" type="checkbox" onchange="this.form.submit()" {% if app_config('SSO_ENABLE') %}checked {% endif %}/>
<span>SSO Authentication (PwnedHub) - {% if not app_config('SSO_ENABLE') %}<span class="red">DISABLED</span>{% else %}<span class="green">ENABLED</span>{% endif %}</span>
</label>
<label for="oob_reset_enable">
<input name="oob_reset_enable" type="checkbox" onchange="this.form.submit()" {% if app_config('OOB_RESET_ENABLE') %}checked {% endif %}/>
<span>Out-of-Band Password Reset (PwnedHub) - {% if not app_config('OOB_RESET_ENABLE') %}<span class="red">DISABLED</span>{% else %}<span class="green">ENABLED</span>{% endif %}</span>
</label>
<label for="ctf_mode">
<input name="ctf_mode" type="checkbox" onchange="this.form.submit()" {% if app_config('CTF_MODE') %}checked {% endif %}/>
<span>CTF Mode (Warning: Disables this interface!) - {% if not app_config('CTF_MODE') %}<span class="red">DISABLED</span>{% else %}<span class="green">ENABLED</span>{% endif %}</span>
</label>
</div>
</form>
{% extends 'layout.html' %}
{% block title %}PwnedAdmin | Config{% endblock %}
{% block content %}
<form class="flex flex-col lg:flex-row m-4 gap-4" action="{{ url_for('config.index') }}" method="post">
{% for config_type in config_types %}
<div class="flex flex-col lg:flex-1 rounded-lg border-2 border-black">
<div class="flex flex-col p-4 rounded-t-lg bg-gray-200">
<div class="text-xl font-bold">{{ config_type|title }}</div>
</div>
</section>
</body>
</html>
<div class="flex flex-col gap-2 p-4 rounded-b-lg border-t-2 border-black">
{% for config in configs %}
{% if config.type == config_type %}
<label for="{{ config.name|lower }}">
<input name="{{ config.name|lower }}" type="checkbox" class="w-4 h-4 accent-red-500" {% if app_config(config.name) %}checked {% endif %}/>
<span class="font-bold">{{ config.description }} - {% if not app_config(config.name) %}<span class="text-red-500">DISABLED</span>{% else %}<span class="text-green-500">ENABLED</span>{% endif %}</span>
</label>
{% endif %}
{% endfor %}
</div>
</div>
{% endfor %}
</form>
<script>
document.querySelectorAll('input[type="checkbox"]').forEach((element) => {
element.addEventListener("change", (e) => {
e.target.form.submit();
});
});
</script>
{% endblock %}
73 changes: 35 additions & 38 deletions pwnedadmin/templates/emails.html
Original file line number Diff line number Diff line change
@@ -1,40 +1,37 @@
<!DOCTYPE HTML>
<html>
<head>
<title>PwnedAdmin | Inbox</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>html{visibility:hidden;opacity:0;}</style><!-- FOUC fix 1/2 -->
<link rel="stylesheet" type="text/css" href="{{ url_for('common.static', filename='css/fontawesome.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('common.static', filename='css/skeleton-flexbox.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('common.static', filename='css/custom-flex.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('common.static', filename='css/custom-utility.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('common.static', filename='css/pwnedhub.css') }}">
<style>html{visibility:visible;opacity:1;}</style><!-- FOUC fix 2/2 -->
</head>
<body class="flex-column">
<div class="flex-grow flex-row">
<div class="flex-grow email flex-column">
<table>
<thead>
<th>Time</th>
<th>Sender</th>
<th>Receiver</th>
<th>Subject</th>
<th>Body</th>
</thead>
<tbody>
{% for email in emails %}
<tr>
<td>{{ email.created_as_string }}</td>
<td>{{ email.sender }}</td>
<td>{{ email.receiver }}</td>
<td>{{ email.subject }}</td>
<td>{{ email.body|safe }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% extends 'layout.html' %}
{% block title %}PwnedAdmin | Inbox{% endblock %}
{% block content %}
<div class="flex flex-col m-4 gap-4">
<div class="flex flex-row justify-end">
<button class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">Empty</button>
</div>
{% if emails|length > 0 %}
{% for email in emails %}
<div class="flex flex-col rounded-lg border-2 border-black">
<div class="flex flex-col gap-2 p-4 rounded-t-lg bg-gray-200">
<div class="flex flex-row justify-between">
<div class="text-xl font-bold">{{ email.subject }}</div>
<div><code>{{ email.created_as_string }}</code></div>
</div>
<div><code>From:</code> <span>{{ email.sender }}</span></div>
<div><code>To:</code> <span class="font-bold">{{ email.receiver }}</span></div>
</div>
<div class="flex flex-col p-4 rounded-b-lg border-t-2 border-black">
<div class="break-all">{{ email.body|safe }}</div>
</div>
</div>
</body>
</html>
{% endfor %}
{% else %}
<div class="flex flex-row justify-center">
<div class="text-xl font-bold">Inbox is empty.</div>
</div>
{% endif %}
</div>
<script>
document.querySelector("button").addEventListener("click", (e) => {
if (confirm("Are you sure?")) {
window.location="{{ url_for('email.empty') }}";
};
});
</script>
{% endblock %}
15 changes: 15 additions & 0 deletions pwnedadmin/templates/layout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %}</title>
<style>html{visibility:hidden;opacity:0;}</style><!-- FOUC fix 1/2 -->
<!--script src="https://cdn.tailwindcss.com"></script-->
<script src="{{ url_for('common.static', filename='js/tailwind-3.4.3.min.js') }}"></script>
<style>html{visibility:visible;opacity:1;}</style><!-- FOUC fix 2/2 -->
</head>
<body class="flex flex-col">
{% block content %}{% endblock %}
</body>
</html>
26 changes: 8 additions & 18 deletions pwnedadmin/views/config.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
from flask import Blueprint, request, render_template, abort
from pwnedadmin.models import Config
from flask import Blueprint, request, render_template, abort, redirect, url_for
from pwnedadmin import db
from pwnedadmin.constants import ConfigTypes
from pwnedadmin.models import Config

blp = Blueprint('config', __name__, url_prefix='/config')

@blp.route('/', methods=['GET', 'POST'])
def index():
if Config.get_value('CTF_MODE'):
abort(404)
configs = Config.query.all()
if request.method == 'POST':
options = [
'CSRF_PROTECT',
'BEARER_AUTH_ENABLE',
'CORS_RESTRICT',
'OIDC_ENABLE',
'OSCI_PROTECT',
'SQLI_PROTECT',
'CTF_MODE',
'SSO_ENABLE',
'JWT_VERIFY',
'JWT_ENCRYPT',
'OOB_RESET_ENABLE',
]
for option in options:
Config.get_by_name(option.upper()).value = request.form.get(option.lower()) == 'on'
for config in configs:
config.value = request.form.get(config.name.lower()) == 'on'
db.session.commit()
return render_template('config.html')
return redirect(url_for('config.index'))
return render_template('config.html', configs=configs, config_types=ConfigTypes().serialized)
Loading

0 comments on commit 8638f77

Please sign in to comment.