vpixray 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
core/__init__.py ADDED
File without changes
core/cmdi.py ADDED
@@ -0,0 +1,40 @@
1
+ import re
2
+ from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
3
+ from utils.colors import console
4
+
5
+ CMDI_PAYLOADS = [
6
+ (";id", r"uid=\d+\("),
7
+ ("|id", r"uid=\d+\("),
8
+ ("`id`", r"uid=\d+\("),
9
+ ("$(id)", r"uid=\d+\("),
10
+ (";cat /etc/passwd", r"root:.*:0:0:"),
11
+ ("|whoami", r"(root|www-data|apache|nginx)"),
12
+ ]
13
+
14
+ def check_cmdi(requester, url):
15
+ result = {"name": "Command Injection", "findings": [], "vulnerable": []}
16
+ parsed = urlparse(url)
17
+ params = parse_qs(parsed.query)
18
+ if not params:
19
+ console.print("[dim] i No parameters found, skipping[/dim]")
20
+ return result
21
+ console.print(f"[info] -> Testing {len(params)} param(s) with {len(CMDI_PAYLOADS)} payloads...[/info]")
22
+ for param in params:
23
+ console.print(f"[dim] -> Param: {param}[/dim]")
24
+ for i, (payload, pattern) in enumerate(CMDI_PAYLOADS, 1):
25
+ q = params.copy()
26
+ q[param] = [params[param][0] + payload]
27
+ test_url = urlunparse(parsed._replace(query=urlencode(q, doseq=True)))
28
+ console.print(f"[dim] [{i}/{len(CMDI_PAYLOADS)}] {payload!r}[/dim]")
29
+ resp = requester.get(test_url)
30
+ if resp and re.search(pattern, resp.text):
31
+ console.print(f"[vuln] !! Command Injection via {param}![/vuln]")
32
+ result["vulnerable"].append({"param": param, "payload": payload})
33
+ result["findings"].append({
34
+ "severity": "critical",
35
+ "msg": f"Command Injection via param: {param} (payload: {payload})",
36
+ })
37
+ break
38
+ if not result["vulnerable"]:
39
+ console.print("[success] + No command injection found[/success]")
40
+ return result
core/cors.py ADDED
@@ -0,0 +1,38 @@
1
+ from utils.colors import console
2
+
3
+ def check_cors(requester, url):
4
+ result = {"name": "CORS Misconfig", "findings": [], "vulnerable": []}
5
+ console.print("[info] -> Sending requests with crafted Origin headers...[/info]")
6
+
7
+ origins = [
8
+ "https://evil.example.com",
9
+ "null",
10
+ "http://localhost",
11
+ ]
12
+
13
+ for origin in origins:
14
+ console.print(f"[dim] -> Testing Origin: {origin}[/dim]")
15
+ resp = requester.get(url, headers={"Origin": origin})
16
+ if not resp:
17
+ continue
18
+ acao = resp.headers.get("Access-Control-Allow-Origin", "")
19
+ acac = resp.headers.get("Access-Control-Allow-Credentials", "")
20
+
21
+ if acao == "*":
22
+ console.print(f"[warn] ! CORS wildcard * (no credentials)[/warn]")
23
+ result["findings"].append({
24
+ "severity": "low",
25
+ "msg": "CORS allows wildcard origin (*) — low risk without credentials",
26
+ })
27
+ elif acao == origin:
28
+ sev = "high" if acac.lower() == "true" else "medium"
29
+ console.print(f"[vuln] !! CORS reflects Origin: {origin} (credentials: {acac})[/vuln]")
30
+ result["vulnerable"].append({"origin": origin, "credentials": acac})
31
+ result["findings"].append({
32
+ "severity": sev,
33
+ "msg": f"CORS reflects origin '{origin}' (credentials: {acac or 'none'})",
34
+ })
35
+
36
+ if not result["findings"]:
37
+ console.print("[success] + CORS properly configured[/success]")
38
+ return result
core/crlf.py ADDED
@@ -0,0 +1,40 @@
1
+ from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
2
+ from utils.colors import console
3
+
4
+ CRLF_PAYLOADS = [
5
+ "%0d%0aInjected-Header: vpixray-test",
6
+ "%0aInjected-Header: vpixray-test",
7
+ "%0d%0a%0d%0a<html>injected</html>",
8
+ ]
9
+
10
+ def check_crlf(requester, url):
11
+ result = {"name": "CRLF Injection", "findings": [], "vulnerable": []}
12
+ parsed = urlparse(url)
13
+ params = parse_qs(parsed.query)
14
+ if not params:
15
+ console.print("[dim] i No parameters found, skipping[/dim]")
16
+ return result
17
+ console.print(f"[info] -> Testing {len(params)} param(s) with {len(CRLF_PAYLOADS)} payloads...[/info]")
18
+ for param in params:
19
+ console.print(f"[dim] -> Param: {param}[/dim]")
20
+ for i, payload in enumerate(CRLF_PAYLOADS, 1):
21
+ console.print(f"[dim] [{i}/{len(CRLF_PAYLOADS)}] Testing...[/dim]")
22
+ q = params.copy()
23
+ q[param] = [payload]
24
+ # نبني URL يدويًا لأن urlencode يشوّه %0d%0a
25
+ query = "&".join(f"{k}={v[0]}" for k, v in q.items())
26
+ test_url = urlunparse(parsed._replace(query=query))
27
+ resp = requester.get(test_url)
28
+ if not resp:
29
+ continue
30
+ if "vpixray-test" in resp.headers or "Injected-Header" in str(resp.headers):
31
+ console.print(f"[vuln] !! CRLF Injection via {param}![/vuln]")
32
+ result["vulnerable"].append({"param": param, "payload": payload})
33
+ result["findings"].append({
34
+ "severity": "high",
35
+ "msg": f"CRLF Injection via param: {param}",
36
+ })
37
+ break
38
+ if not result["vulnerable"]:
39
+ console.print("[success] + No CRLF injection found[/success]")
40
+ return result
core/csrf.py ADDED
@@ -0,0 +1,35 @@
1
+ from bs4 import BeautifulSoup
2
+ from utils.colors import console
3
+
4
+ def check_csrf(requester, url):
5
+ result = {"name": "CSRF", "findings": [], "vulnerable": []}
6
+ console.print("[info] -> Scanning for forms...[/info]")
7
+ resp = requester.get(url)
8
+ if not resp:
9
+ console.print("[error] x Connection failed[/error]")
10
+ return result
11
+ soup = BeautifulSoup(resp.text, "html.parser")
12
+ forms = soup.find_all("form")
13
+ console.print(f"[dim] Found {len(forms)} form(s)[/dim]")
14
+ for i, form in enumerate(forms, 1):
15
+ action = form.get("action", "")
16
+ method = form.get("method", "GET").upper()
17
+ inputs = form.find_all("input")
18
+ has_token = False
19
+ for inp in inputs:
20
+ name = (inp.get("name") or "").lower()
21
+ if "token" in name or "csrf" in name or "_token" in name:
22
+ has_token = True
23
+ break
24
+ if method == "POST" and not has_token:
25
+ console.print(f"[vuln] !! Form #{i} POST without CSRF token[/vuln]")
26
+ result["vulnerable"].append({"form": i, "action": action, "method": method})
27
+ result["findings"].append({
28
+ "severity": "medium",
29
+ "msg": f"POST form without CSRF token (action: {action or 'same page'})",
30
+ })
31
+ else:
32
+ console.print(f"[success] + Form #{i} OK ({method}, token: {has_token})[/success]")
33
+ if not result["vulnerable"]:
34
+ console.print("[success] + No CSRF issues found[/success]")
35
+ return result
core/dirlisting.py ADDED
@@ -0,0 +1,31 @@
1
+ from utils.colors import console
2
+
3
+ COMMON_DIRS = [
4
+ "/", "/images/", "/uploads/", "/files/", "/backup/",
5
+ "/admin/", "/assets/", "/static/", "/css/", "/js/",
6
+ "/logs/", "/tmp/", "/data/",
7
+ ]
8
+
9
+ def check_dirlisting(requester, base_url):
10
+ result = {"name": "Directory Listing", "findings": [], "vulnerable": []}
11
+ base = base_url.rstrip("/")
12
+ console.print(f"[info] -> Testing {len(COMMON_DIRS)} directories...[/info]")
13
+
14
+ for i, path in enumerate(COMMON_DIRS, 1):
15
+ url = f"{base}{path}"
16
+ console.print(f"[dim] [{i}/{len(COMMON_DIRS)}] {path}[/dim]")
17
+ resp = requester.get(url)
18
+ if not resp or resp.status_code != 200:
19
+ continue
20
+ body = resp.text.lower()
21
+ if "<title>index of" in body or "<h1>index of" in body:
22
+ console.print(f"[vuln] !! Directory listing enabled: {path}[/vuln]")
23
+ result["vulnerable"].append({"path": path})
24
+ result["findings"].append({
25
+ "severity": "medium",
26
+ "msg": f"Directory listing enabled: {path}",
27
+ })
28
+
29
+ if not result["vulnerable"]:
30
+ console.print("[success] + No directory listing found[/success]")
31
+ return result
core/fingerprint.py ADDED
@@ -0,0 +1,38 @@
1
+ import re
2
+ from utils.colors import console
3
+
4
+ TECH_SIGNATURES = {
5
+ "WordPress": [r"wp-content", r"wp-includes"],
6
+ "Joomla": [r"/components/com_", r"joomla"],
7
+ "Drupal": [r"sites/default/files", r"Drupal"],
8
+ "React": [r"__REACT_DEVTOOLS", r"react-dom"],
9
+ "Vue.js": [r"vue\.js", r"__vue__"],
10
+ "Angular": [r"ng-version", r"angular"],
11
+ "jQuery": [r"jquery(\.min)?\.js"],
12
+ "Bootstrap": [r"bootstrap(\.min)?\.(css|js)"],
13
+ "Nginx": [r"nginx"],
14
+ "Apache": [r"apache"],
15
+ "Cloudflare": [r"cloudflare"],
16
+ "PHP": [r"X-Powered-By:\s*PHP", r"PHPSESSID"],
17
+ "ASP.NET": [r"ASP\.NET", r"X-AspNet-Version"],
18
+ "Django": [r"csrftoken", r"django"],
19
+ }
20
+
21
+ def check_fingerprint(requester, url):
22
+ console.print("[info] -> Analyzing server response...[/info]")
23
+ result = {"name": "Tech Fingerprint", "findings": [], "technologies": []}
24
+ resp = requester.get(url)
25
+ if not resp:
26
+ console.print("[error] x Connection failed[/error]")
27
+ return result
28
+ haystack = resp.text + "\n" + "\n".join(f"{k}: {v}" for k, v in resp.headers.items())
29
+ for tech, patterns in TECH_SIGNATURES.items():
30
+ for pat in patterns:
31
+ if re.search(pat, haystack, re.IGNORECASE):
32
+ console.print(f"[success] + Detected: {tech}[/success]")
33
+ result["technologies"].append(tech)
34
+ result["findings"].append({"severity": "info", "msg": f"Detected: {tech}"})
35
+ break
36
+ if not result["technologies"]:
37
+ console.print("[dim] i No known technologies detected[/dim]")
38
+ return result
core/headers_check.py ADDED
@@ -0,0 +1,38 @@
1
+ from utils.colors import console
2
+
3
+ SECURITY_HEADERS = {
4
+ "Strict-Transport-Security": "Enforces HTTPS",
5
+ "Content-Security-Policy": "Mitigates XSS",
6
+ "X-Frame-Options": "Prevents Clickjacking",
7
+ "X-Content-Type-Options": "Prevents MIME sniffing",
8
+ "Referrer-Policy": "Controls referrer leakage",
9
+ "Permissions-Policy": "Restricts browser features",
10
+ "Cross-Origin-Opener-Policy":"Isolates windows",
11
+ "Cross-Origin-Resource-Policy":"Blocks cross-origin loads",
12
+ }
13
+
14
+ def check_headers(requester, url):
15
+ console.print("[info] -> Fetching HTTP headers...[/info]")
16
+ result = {"name": "Security Headers", "findings": [], "missing": [], "score": 0}
17
+ resp = requester.get(url)
18
+ if not resp:
19
+ console.print("[error] x Connection failed[/error]")
20
+ result["findings"].append({"severity": "info", "msg": "Could not connect"})
21
+ return result
22
+ console.print("[dim] OK Response received, analyzing...[/dim]")
23
+ present = 0
24
+ lower_keys = {k.lower() for k in resp.headers.keys()}
25
+ for header, desc in SECURITY_HEADERS.items():
26
+ if header.lower() in lower_keys:
27
+ console.print(f"[success] + {header}[/success]")
28
+ present += 1
29
+ else:
30
+ console.print(f"[warn] - {header} MISSING[/warn]")
31
+ result["missing"].append({"header": header, "desc": desc})
32
+ result["score"] = int((present / len(SECURITY_HEADERS)) * 100)
33
+ if result["missing"]:
34
+ result["findings"].append({
35
+ "severity": "low" if result["score"] > 50 else "medium",
36
+ "msg": f"{len(result['missing'])} missing security headers (score: {result['score']}%)",
37
+ })
38
+ return result
core/hostheader.py ADDED
@@ -0,0 +1,29 @@
1
+ from utils.colors import console
2
+
3
+ def check_host_header(requester, url):
4
+ result = {"name": "Host Header Injection", "findings": [], "vulnerable": []}
5
+ console.print("[info] -> Testing Host header manipulation...[/info]")
6
+
7
+ evil_hosts = [
8
+ "evil.example.com",
9
+ "localhost",
10
+ ]
11
+
12
+ for host in evil_hosts:
13
+ console.print(f"[dim] -> Testing Host: {host}[/dim]")
14
+ resp = requester.get(url, headers={"Host": host})
15
+ if not resp:
16
+ continue
17
+ body = resp.text.lower()
18
+ if "evil.example.com" in body or host in body:
19
+ console.print(f"[vuln] !! Host header reflected in response: {host}[/vuln]")
20
+ result["vulnerable"].append({"host": host})
21
+ result["findings"].append({
22
+ "severity": "high",
23
+ "msg": f"Host header '{host}' reflected in response (password reset poisoning risk)",
24
+ })
25
+ break
26
+
27
+ if not result["vulnerable"]:
28
+ console.print("[success] + Host header not reflected[/success]")
29
+ return result
core/idor.py ADDED
@@ -0,0 +1,32 @@
1
+ from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
2
+ from utils.colors import console
3
+
4
+ def check_idor(requester, url):
5
+ result = {"name": "IDOR", "findings": [], "vulnerable": []}
6
+ parsed = urlparse(url)
7
+ params = parse_qs(parsed.query)
8
+ numeric_params = {k: v for k, v in params.items() if v and v[0].isdigit()}
9
+ if not numeric_params:
10
+ console.print("[dim] i No numeric parameters found, skipping[/dim]")
11
+ return result
12
+ console.print(f"[info] -> Testing {len(numeric_params)} numeric param(s) for IDOR...[/info]")
13
+ for param, values in numeric_params.items():
14
+ original_id = int(values[0])
15
+ console.print(f"[dim] -> Param: {param} (id={original_id})[/dim]")
16
+ for test_id in [original_id - 1, original_id + 1, original_id + 100, 1, 0]:
17
+ if test_id < 0 or test_id == original_id:
18
+ continue
19
+ q = params.copy()
20
+ q[param] = [str(test_id)]
21
+ test_url = urlunparse(parsed._replace(query=urlencode(q, doseq=True)))
22
+ resp = requester.get(test_url)
23
+ if resp and resp.status_code == 200 and len(resp.text) > 100:
24
+ console.print(f"[warn] ? Different response for {param}={test_id} (potential IDOR)[/warn]")
25
+ result["findings"].append({
26
+ "severity": "medium",
27
+ "msg": f"Possible IDOR via {param}={test_id} (manual verification needed)",
28
+ })
29
+ break
30
+ if not result["findings"]:
31
+ console.print("[success] + No obvious IDOR found[/success]")
32
+ return result
core/jwt.py ADDED
@@ -0,0 +1,77 @@
1
+ import base64
2
+ import json
3
+ import re
4
+ from utils.colors import console
5
+
6
+ JWT_REGEX = re.compile(r"eyJ[A-Za-z0-9_\-]+\.eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]*")
7
+
8
+ def _b64decode_pad(s):
9
+ s += "=" * (-len(s) % 4)
10
+ try:
11
+ return base64.urlsafe_b64decode(s).decode("utf-8", errors="ignore")
12
+ except Exception:
13
+ return ""
14
+
15
+ def check_jwt(requester, url):
16
+ result = {"name": "JWT Analyzer", "findings": [], "vulnerable": []}
17
+ console.print("[info] -> Scanning for JWT tokens in response...[/info]")
18
+ resp = requester.get(url)
19
+ if not resp:
20
+ console.print("[error] x Connection failed[/error]")
21
+ return result
22
+
23
+ # ندور في الرد والـ headers
24
+ haystack = resp.text + "\n" + "\n".join(f"{k}: {v}" for k, v in resp.headers.items())
25
+ tokens = set(JWT_REGEX.findall(haystack))
26
+
27
+ if not tokens:
28
+ console.print("[dim] i No JWT tokens found[/dim]")
29
+ return result
30
+
31
+ console.print(f"[dim] Found {len(tokens)} JWT token(s)[/dim]")
32
+
33
+ for token in tokens:
34
+ parts = token.split(".")
35
+ if len(parts) != 3:
36
+ continue
37
+
38
+ header = _b64decode_pad(parts[0])
39
+ payload = _b64decode_pad(parts[1])
40
+ console.print(f"[dim] Header: {header[:100]}[/dim]")
41
+ console.print(f"[dim] Payload: {payload[:100]}[/dim]")
42
+
43
+ try:
44
+ header_json = json.loads(header)
45
+ alg = header_json.get("alg", "").lower()
46
+ except Exception:
47
+ alg = ""
48
+
49
+ if alg == "none":
50
+ console.print("[vuln] !! JWT with 'alg: none'![/vuln]")
51
+ result["findings"].append({
52
+ "severity": "critical",
53
+ "msg": "JWT uses 'alg: none' (signature bypass)",
54
+ })
55
+ result["vulnerable"].append({"issue": "alg_none"})
56
+
57
+ if alg in ("hs256", "hs384", "hs512"):
58
+ console.print(f"[warn] ! JWT uses {alg.upper()} (test weak secret)[/warn]")
59
+ result["findings"].append({
60
+ "severity": "low",
61
+ "msg": f"JWT uses HMAC ({alg}) — test for weak secret",
62
+ })
63
+
64
+ try:
65
+ payload_json = json.loads(payload)
66
+ if "exp" not in payload_json:
67
+ console.print("[warn] ! JWT has no expiration (exp)[/warn]")
68
+ result["findings"].append({
69
+ "severity": "medium",
70
+ "msg": "JWT has no 'exp' claim",
71
+ })
72
+ except Exception:
73
+ pass
74
+
75
+ if not result["findings"]:
76
+ console.print("[success] + No JWT issues found[/success]")
77
+ return result
core/lfi.py ADDED
@@ -0,0 +1,49 @@
1
+ import re
2
+ from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
3
+ from utils.colors import console
4
+
5
+ LFI_PAYLOADS = [
6
+ "../../../../etc/passwd",
7
+ "../../../../../../etc/passwd",
8
+ "....//....//....//etc/passwd",
9
+ "..%2F..%2F..%2F..%2Fetc%2Fpasswd",
10
+ "/etc/passwd",
11
+ "....//....//....//....//etc/passwd",
12
+ ]
13
+
14
+ LFI_PATTERNS = [
15
+ r"root:.*:0:0:",
16
+ r"daemon:.*:1:1:",
17
+ r"\[boot loader\]",
18
+ r"\[extensions\]",
19
+ ]
20
+
21
+ def check_lfi(requester, url):
22
+ result = {"name": "LFI", "findings": [], "vulnerable": []}
23
+ parsed = urlparse(url)
24
+ params = parse_qs(parsed.query)
25
+ if not params:
26
+ console.print("[dim] i No parameters found, skipping[/dim]")
27
+ return result
28
+ console.print(f"[info] -> Testing {len(params)} param(s) with {len(LFI_PAYLOADS)} payloads...[/info]")
29
+ for param in params:
30
+ console.print(f"[dim] -> Param: {param}[/dim]")
31
+ for i, payload in enumerate(LFI_PAYLOADS, 1):
32
+ q = params.copy()
33
+ q[param] = [payload]
34
+ test_url = urlunparse(parsed._replace(query=urlencode(q, doseq=True)))
35
+ console.print(f"[dim] [{i}/{len(LFI_PAYLOADS)}] {payload}[/dim]")
36
+ resp = requester.get(test_url)
37
+ if resp:
38
+ for pat in LFI_PATTERNS:
39
+ if re.search(pat, resp.text):
40
+ console.print(f"[vuln] !! LFI via {param}![/vuln]")
41
+ result["vulnerable"].append({"param": param, "payload": payload})
42
+ result["findings"].append({
43
+ "severity": "high",
44
+ "msg": f"LFI via param: {param} (payload: {payload})",
45
+ })
46
+ return result
47
+ if not result["vulnerable"]:
48
+ console.print("[success] + No LFI found[/success]")
49
+ return result
core/nosqli.py ADDED
@@ -0,0 +1,51 @@
1
+ from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
2
+ from utils.colors import console
3
+
4
+ NOSQL_ERRORS = [
5
+ "mongoerror",
6
+ "mongo",
7
+ "$where",
8
+ "unexpected token",
9
+ "json parse error",
10
+ "cast to objectid failed",
11
+ ]
12
+
13
+ NOSQL_PAYLOADS = [
14
+ '{"$ne": null}',
15
+ '{"$gt": ""}',
16
+ "' || '1'=='1",
17
+ '"; return true; var x="',
18
+ "[$ne]=1",
19
+ ]
20
+
21
+ def check_nosqli(requester, url):
22
+ result = {"name": "NoSQL Injection", "findings": [], "vulnerable": []}
23
+ parsed = urlparse(url)
24
+ params = parse_qs(parsed.query)
25
+ if not params:
26
+ console.print("[dim] i No parameters found, skipping[/dim]")
27
+ return result
28
+ console.print(f"[info] -> Testing {len(params)} param(s) with {len(NOSQL_PAYLOADS)} payloads...[/info]")
29
+ for param in params:
30
+ console.print(f"[dim] -> Param: {param}[/dim]")
31
+ for i, payload in enumerate(NOSQL_PAYLOADS, 1):
32
+ console.print(f"[dim] [{i}/{len(NOSQL_PAYLOADS)}] {payload[:40]}[/dim]")
33
+ q = params.copy()
34
+ q[param] = [payload]
35
+ test_url = urlunparse(parsed._replace(query=urlencode(q, doseq=True)))
36
+ resp = requester.get(test_url)
37
+ if not resp:
38
+ continue
39
+ body = resp.text.lower()
40
+ for err in NOSQL_ERRORS:
41
+ if err in body:
42
+ console.print(f"[vuln] !! NoSQL injection possible via {param}! Pattern: {err}[/vuln]")
43
+ result["vulnerable"].append({"param": param, "payload": payload})
44
+ result["findings"].append({
45
+ "severity": "critical",
46
+ "msg": f"NoSQL injection possible via param: {param}",
47
+ })
48
+ break
49
+ if not result["vulnerable"]:
50
+ console.print("[success] + No NoSQL injection found[/success]")
51
+ return result
core/redirect.py ADDED
@@ -0,0 +1,39 @@
1
+ from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
2
+ from utils.colors import console
3
+
4
+ PAYLOADS = [
5
+ "https://evil.example.com",
6
+ "//evil.example.com",
7
+ "https://evil.example.com/%2f..",
8
+ "/\\evil.example.com",
9
+ ]
10
+
11
+ def _build_url(parsed, param, value, original_query):
12
+ q = original_query.copy()
13
+ q[param] = [value]
14
+ return urlunparse(parsed._replace(query=urlencode(q, doseq=True)))
15
+
16
+ def check_redirect(requester, url):
17
+ result = {"name": "Open Redirect", "findings": [], "vulnerable": []}
18
+ parsed = urlparse(url)
19
+ params = parse_qs(parsed.query)
20
+ if not params:
21
+ console.print("[dim] i No parameters found, skipping[/dim]")
22
+ return result
23
+ console.print(f"[info] -> Testing {len(params)} param(s) with {len(PAYLOADS)} payloads...[/info]")
24
+ for param in params:
25
+ console.print(f"[dim] -> Param: {param}[/dim]")
26
+ for payload in PAYLOADS:
27
+ test_url = _build_url(parsed, param, payload, params)
28
+ resp = requester.get(test_url)
29
+ if not resp:
30
+ continue
31
+ loc = resp.headers.get("Location", "")
32
+ if resp.status_code in (301, 302, 303, 307, 308) and "evil.example.com" in loc:
33
+ console.print(f"[vuln] !! VULNERABLE via {param}![/vuln]")
34
+ result["vulnerable"].append({"param": param, "payload": payload, "location": loc})
35
+ result["findings"].append({"severity": "high", "msg": f"Open Redirect via param: {param}"})
36
+ break
37
+ if not result["vulnerable"]:
38
+ console.print("[success] + No open redirect found[/success]")
39
+ return result
core/requester.py ADDED
@@ -0,0 +1,44 @@
1
+ import requests
2
+ from requests.adapters import HTTPAdapter
3
+ from urllib3.util.retry import Retry
4
+
5
+ DEFAULT_TIMEOUT = 10
6
+ DEFAULT_UA = "Mozilla/5.0 (X11; Linux x86_64) VpiXray/0.1"
7
+
8
+ class Requester:
9
+ def __init__(self, timeout=DEFAULT_TIMEOUT, headers=None, proxy=None, verify=False, cookie=None):
10
+ self.timeout = timeout
11
+ self.session = requests.Session()
12
+ self.session.headers.update({"User-Agent": DEFAULT_UA})
13
+ if headers:
14
+ self.session.headers.update(headers)
15
+ if proxy:
16
+ self.session.proxies = {"http": proxy, "https": proxy}
17
+ if cookie:
18
+ for item in cookie.split(";"):
19
+ item = item.strip()
20
+ if "=" in item:
21
+ k, v = item.split("=", 1)
22
+ self.session.cookies.set(k.strip(), v.strip())
23
+ retries = Retry(total=2, backoff_factor=0.3, status_forcelist=[500, 502, 503, 504])
24
+ self.session.mount("http://", HTTPAdapter(max_retries=retries))
25
+ self.session.mount("https://", HTTPAdapter(max_retries=retries))
26
+ self.verify = verify
27
+
28
+ def get(self, url, **kwargs):
29
+ kwargs.setdefault("timeout", self.timeout)
30
+ kwargs.setdefault("verify", self.verify)
31
+ kwargs.setdefault("allow_redirects", False)
32
+ try:
33
+ return self.session.get(url, **kwargs)
34
+ except requests.RequestException:
35
+ return None
36
+
37
+ def post(self, url, **kwargs):
38
+ kwargs.setdefault("timeout", self.timeout)
39
+ kwargs.setdefault("verify", self.verify)
40
+ kwargs.setdefault("allow_redirects", False)
41
+ try:
42
+ return self.session.post(url, **kwargs)
43
+ except requests.RequestException:
44
+ return None
core/sensitive.py ADDED
@@ -0,0 +1,41 @@
1
+ import os
2
+ from utils.colors import console
3
+
4
+ DEFAULT_FILES = [
5
+ ".env", ".git/config", ".git/HEAD", "backup.zip", "backup.tar.gz",
6
+ "config.php.bak", "wp-config.php.bak", ".DS_Store", "phpinfo.php",
7
+ "admin/", "robots.txt", "sitemap.xml", ".htaccess", "web.config",
8
+ "composer.json", "package.json", "Dockerfile", ".dockerignore",
9
+ ]
10
+
11
+ def load_wordlist(path=None):
12
+ if path and os.path.isfile(path):
13
+ with open(path, "r", encoding="utf-8", errors="ignore") as f:
14
+ return [l.strip() for l in f if l.strip() and not l.startswith("#")]
15
+ return DEFAULT_FILES
16
+
17
+ def check_sensitive(requester, base_url, wordlist_path=None):
18
+ files = load_wordlist(wordlist_path)
19
+ console.print(f"[info] -> Testing {len(files)} sensitive paths...[/info]")
20
+ result = {"name": "Sensitive Files", "findings": [], "hits": []}
21
+ base = base_url.rstrip("/")
22
+ hits = 0
23
+ for i, path in enumerate(files, 1):
24
+ url = f"{base}/{path}"
25
+ console.print(f"[dim] [{i}/{len(files)}] -> /{path}[/dim]")
26
+ resp = requester.get(url)
27
+ if resp and resp.status_code in (200, 301, 302, 403):
28
+ size = len(resp.content)
29
+ hits += 1
30
+ result["hits"].append({"url": url, "status": resp.status_code, "size": size})
31
+ sev = "high" if resp.status_code == 200 else "low"
32
+ console.print(f"[vuln] !! HIT [{resp.status_code}] {url} ({size} bytes)[/vuln]")
33
+ result["findings"].append({
34
+ "severity": sev,
35
+ "msg": f"[{resp.status_code}] {url} ({size} bytes)",
36
+ })
37
+ if hits == 0:
38
+ console.print("[success] + No sensitive files exposed[/success]")
39
+ else:
40
+ console.print(f"[error] !! {hits} sensitive file(s) found[/error]")
41
+ return result