Skip to content

Commit 3e0e0f2

Browse files
authored
Merge pull request #2 from enowars/feature/admin-in-checker
Feature/admin in checker
2 parents 67059e9 + 5a1b27f commit 3e0e0f2

24 files changed

+911
-193
lines changed

checker/Dockerfile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
FROM python:3.9-buster
22
RUN apt-get update && apt-get upgrade -y
33

4+
# Install Playwright system dependencies for Chromium
5+
RUN apt-get install -y \
6+
libnss3 \
7+
libnspr4 \
8+
libdbus-1-3 \
9+
libatk1.0-0 \
10+
libatspi2.0-0 \
11+
libxcomposite1 \
12+
libxdamage1 \
13+
libxfixes3 \
14+
libxrandr2 \
15+
libgbm1 \
16+
libxkbcommon0 \
17+
libasound2 \
18+
&& rm -rf /var/lib/apt/lists/*
19+
420
# add checker user
521
RUN useradd -ms /bin/bash -u 1000 checker
622

@@ -10,6 +26,7 @@ COPY requirements.txt .
1026

1127
USER checker
1228
RUN python3 -m pip install -r requirements.txt
29+
RUN python3 -m playwright install chromium
1330

1431
COPY ./src .
1532

checker/requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ enochecker3==0.8.1
22
uvicorn
33
gunicorn
44
faker
5-
httpx
5+
httpx
6+
playwright

checker/src/admin_simulator.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import asyncio
2+
from typing import Optional
3+
from httpx import AsyncClient
4+
from logging import LoggerAdapter
5+
from enochecker3 import MumbleException
6+
from message_generator import generate_admin_message
7+
8+
try:
9+
from playwright.async_api import async_playwright
10+
PLAYWRIGHT_AVAILABLE = True
11+
except ImportError:
12+
PLAYWRIGHT_AVAILABLE = False
13+
14+
15+
class AdminSimulator:
16+
def __init__(self, client: AsyncClient, logger: LoggerAdapter):
17+
self.client = client
18+
self.logger = logger
19+
self.admin_password = "]!V$JuOzx@fi%pvG,lF!"
20+
self.admin_username = "admin"
21+
self.service_url = str(client.base_url)
22+
23+
def validate(self) -> None:
24+
if not PLAYWRIGHT_AVAILABLE:
25+
self.logger.warning("Playwright not available, skipping admin simulation")
26+
return
27+
28+
self.logger.info("Starting headless browser admin simulation...")
29+
30+
async def load_feedback_page(self) -> None:
31+
self.validate()
32+
33+
async with async_playwright() as p:
34+
browser = await p.chromium.launch(
35+
headless=True,
36+
args=['--no-sandbox', '--disable-setuid-sandbox']
37+
)
38+
39+
try:
40+
page = await browser.new_page()
41+
42+
page.set_default_timeout(10000)
43+
44+
await page.goto(f"{self.service_url}/")
45+
46+
await page.fill('#login-username', self.admin_username)
47+
await page.fill('#login-password', self.admin_password)
48+
49+
await page.click('#login-form button[type="submit"]')
50+
51+
await page.wait_for_load_state('networkidle')
52+
53+
await page.goto(f"{self.service_url}/admin/feedback")
54+
55+
await page.wait_for_load_state('networkidle')
56+
57+
await asyncio.sleep(2)
58+
59+
self.logger.info("Admin simulation completed - any XSS scripts would have executed")
60+
61+
except Exception as e:
62+
self.logger.warning(f"Admin simulation failed: {e}")
63+
raise MumbleException(f"Admin simulation error: {e}")
64+
finally:
65+
await browser.close()
66+
67+
async def post_new_message(self) -> None:
68+
self.validate()
69+
70+
message_data = generate_admin_message()
71+
message_text = message_data["message"]
72+
message_year = message_data["year"]
73+
74+
self.logger.info(f"Posting time traveller message from {message_year}: {message_text[:50]}...")
75+
76+
async with async_playwright() as p:
77+
browser = await p.chromium.launch(
78+
headless=True,
79+
args=['--no-sandbox', '--disable-setuid-sandbox']
80+
)
81+
82+
try:
83+
page = await browser.new_page()
84+
85+
page.set_default_timeout(10000)
86+
87+
await page.goto(f"{self.service_url}/")
88+
89+
await page.fill('#login-username', self.admin_username)
90+
await page.fill('#login-password', self.admin_password)
91+
92+
await page.click('#login-form button[type="submit"]')
93+
94+
await page.wait_for_load_state('networkidle')
95+
96+
await page.goto(f"{self.service_url}/admin/message")
97+
98+
await page.wait_for_load_state('networkidle')
99+
100+
await page.fill('#year', str(message_year))
101+
await page.fill('#message', message_text)
102+
103+
await page.click('button[type="submit"]')
104+
105+
await page.wait_for_load_state('networkidle')
106+
107+
await asyncio.sleep(2)
108+
109+
self.logger.info(f"Admin message posted successfully from year {message_year}")
110+
111+
except Exception as e:
112+
self.logger.warning(f"Admin message posting failed: {e}")
113+
raise MumbleException(f"Admin message posting error: {e}")
114+
finally:
115+
await browser.close()
116+
117+

checker/src/checker.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
AsyncSocket,
2929
)
3030
from enochecker3.utils import assert_equals, assert_in
31+
import admin_simulator
3132

3233
"""
3334
Checker config
@@ -518,5 +519,19 @@ async def havoc_feedback_image(task: HavocCheckerTaskMessage, client: AsyncClien
518519
logger.warning("Simple SVG feedback does not appear on feedback page")
519520
raise MumbleException("Can't submit feedback with SVG image")
520521

522+
@checker.havoc(1)
523+
async def havoc_admin_simulation(task: HavocCheckerTaskMessage, client: AsyncClient, logger: LoggerAdapter) -> None:
524+
logger.info("Starting admin simulation havoc test...")
525+
526+
simulator = admin_simulator.AdminSimulator(client, logger)
527+
528+
try:
529+
await simulator.load_feedback_page()
530+
await simulator.post_new_message()
531+
logger.info("Admin simulation completed successfully")
532+
533+
except Exception as e:
534+
raise MumbleException(f"Admin could not post message or visit feedback page.")
535+
521536
if __name__ == "__main__":
522537
checker.run()

0 commit comments

Comments
 (0)