# Exploit probes (band 1050) # # The requests that arrive at every public address within hours of it going # up: one exact request for one known hole, sent to everybody by a machine that # does not know or care what is running. PHPUnit's eval-stdin.php, Laravel's # debug console, ThinkPHP's invokefunction, a home router's firmware endpoint. # Honeypot reports put several of these in the thousands per host per week, # years after each was fixed. # # The other sets describe what an attack looks like. These name requests — # which is why every rule here blocks, and why there are few of them. A rule is # here only if the request it matches has no innocent sender: the path, or the # parameter, exists nowhere except in the exploit for one product. # # BLOCKED, NOT SCORED, and that is the point of the set rather than a setting # of it. A probe scored and passed on gets a 404 from the application and the # scanner moves to the next entry on its list. A probe blocked is a refusal, # and refusals are what Smart Protect counts: three of them and the address is # refused outright, a few requests into a list that is hundreds long. # # What is deliberately NOT here, because another set already has it: # # /.env, wp-config.php — 930-lfi blocks them # /.git, /.svn — 913-scanners blocks them # /phpinfo.php, /actuator — 913-scanners scores them # ../ traversal — 930-lfi # ${jndi:…} — 1030-java (Log4Shell), and 932-rce scores ${…} # /actuator/env, /heapdump — 1030-java, since only a Spring application has # them to protect # wp-content/… PHP — 1010-wordpress # # Two rules matching one request both add their score, so a set that repeats # another halves the policy's effective threshold for that request. # # What is not here because it is somebody's real traffic: /cgi-bin/luci, # /remote/login, /dana-na/, /owa/, Autodiscover's v1.0 endpoint, Confluence's # page-variable actions, and the administrative APIs of Docker, Elasticsearch # and Solr. Each is probed constantly and each is also what the product itself # serves, so a rule for it would block the product. Only the exploit's own # request is matched: /cgi-bin/luci/;stok=, not /cgi-bin/luci. # # DO NOT INSTALL THIS IN FRONT OF THE DEVICE A RULE NAMES. A site that is a # D-Link router's own interface speaks /HNAP1 for a living; rule 1050013 would # refuse it. In front of a web application, which is what EasyWAF is for, none # of these paths exists. # # The requests are public record: each rule names the CVE or the advisory. The # paths were checked against honeypot reporting and the ProjectDiscovery # nuclei-templates corpus (MIT; see LICENSE-MIT and NOTICE). The patterns are # written for EasyWAF. # # Optional, not basic. These are new here and have not run against real traffic # in this project. Promote them once they have. [set] id = "exploit-probes" name = "Exploit probes" version = 1 # ─── PHP ───────────────────────────────────────────────────── [[rules]] id = 1050001 name = "Probe: PHPUnit eval-stdin.php" description = "CVE-2017-9841 — a test helper that runs whatever is posted to it, reachable when vendor/ is under the document root" zone = "URL" # The file name alone, under any path: scanners try dozens of prefixes — # /vendor/phpunit/…, /lib/phpunit/…, /laravel/vendor/… — and the name is the # constant. Nothing but PHPUnit has ever shipped a file called this, and # PHPUnit removed it in 2016. pattern = '(?i)/eval-stdin\.php(\?|$)' score = 10 action = "block" [[rules]] id = 1050002 name = "Probe: Laravel Ignition" description = "CVE-2021-3129 — the debug page's solution runner, which executes code when a Laravel application is left in debug mode" zone = "URL" # Only the endpoints the exploit and its fingerprinting use. /_ignition/ also # serves the debug page's own scripts and styles, which a developer's browser # does fetch; those are left alone. pattern = '(?i)/_ignition/(execute-solution|health-check|share-report|update-config)(\?|$)' score = 10 action = "block" [[rules]] id = 1050003 name = "Probe: ThinkPHP remote execution" description = "CVE-2018-20062 and CVE-2019-9082 — a controller name with a namespace in it, which ThinkPHP 5 resolved to any class and method" zone = "URL" # Two shapes. The namespace itself — \think\app, \think\Container — which is a # backslash in a URL and so never typed by a person; matched in both its # written and its percent-encoded form. And invokefunction with the # call_user_func it is always given, for the variants that spell the namespace # some other way. # # Not bare "invokefunction": that is an ordinary method name, and an API with a # route called /invokeFunction would be refused for having it. pattern = '(?i)think(\\|%5c)(app|container|request|config|template|view|process)\b|invokefunction&function=call_user_func' score = 10 action = "block" [[rules]] id = 1050004 name = "Probe: PHP pearcmd inclusion" description = "A file-inclusion hole turned into code execution through PEAR's command-line script — ThinkPHP's lang parameter (CVE-2022-47945) and any other include that takes a path" zone = "URL" # pearcmd.php is a command-line program. It is in a URL only when something is # being made to include it, and config-create — with the + that register_argc_ # argv turns into argument separators — is what writes the attacker's file. pattern = '(?i)[/\\]pearcmd(\.php)?([&?/]|$)|[?&][+ ]config-create[+ ]' score = 10 action = "block" [[rules]] id = 1050005 name = "Probe: PHP-CGI argument injection" description = "CVE-2012-1823 and CVE-2024-4577 — a query string PHP-CGI reads as its own command line, setting allow_url_include and auto_prepend_file" zone = "URL" # A query that begins with -d and an ini setting. %AD is the 2024 form: a soft # hyphen that Windows' best-fit conversion turns back into the hyphen the 2012 # fix refuses, so it is matched as written — it does not decode to anything. # # And php-cgi.exe by name, which is how the same hole is reached on a default # XAMPP. No site links to its own interpreter. pattern = '(?i)\?(%ad|-)d(\+|%20| )(allow_url_include|auto_prepend_file|cgi\.force_redirect|safe_mode|disable_functions|open_basedir)|/php-cgi\.exe(\?|$)' score = 10 action = "block" [[rules]] id = 1050006 name = "Probe: Drupal render-array injection" description = "CVE-2018-7600, Drupalgeddon 2 — form values carrying #post_render and its relatives, which Drupal treated as instructions" zone = "ANY" # A parameter name with a # key in brackets, or element_parents pointing at # one. Drupal's own forms never send either: # keys exist only on the server # side of a render array. The exploit sends them in the query or the body, so # this looks at both. # # PHP source quoting a render array — ['#markup'] — has a quote before the #, # and is not matched. pattern = '(?i)\[(%23|#)(post_render|pre_render|lazy_builder|markup|access_callback)\]|element_parents=[^&]{0,80}(%23|#)' score = 10 action = "block" [[rules]] id = 1050007 name = "Probe: vBulletin widget rendering" description = "CVE-2019-16759 and CVE-2020-17496 — a routestring that renders a template the request supplies, which is code execution without an account" zone = "ANY" # The routestring arrives in the body as often as in the URL. pattern = '(?i)ajax/render/widget_(php|tabbedcontainer_tab_panel)' score = 10 action = "block" [[rules]] id = 1050008 name = "Probe: Joomla configuration API" description = "CVE-2023-23752 — public=true on the web-services API returned the configuration, database password included, to anyone" zone = "URL" pattern = '(?i)/api/index\.php/v1/(config/application|users)\?public=true' score = 10 action = "block" [[rules]] id = 1050009 name = "Probe: phpMyAdmin setup script" description = "CVE-2009-1151 — the setup script of phpMyAdmin 2 and 3, which wrote a configuration file containing the request's own PHP" zone = "URL" # Under one of the names phpMyAdmin is installed as. /scripts/setup.php on its # own is a path another application could have. pattern = '(?i)/(phpmyadmin|pma|phpma|myadmin|mysqladmin|sqladmin|dbadmin)[^/?]{0,20}/scripts/setup\.php(\?|$)' score = 10 action = "block" # ─── Any stack ─────────────────────────────────────────────── [[rules]] id = 1050010 name = "Probe: Shellshock" description = "CVE-2014-6271 — a bash function definition in a header, which a CGI program's environment handed to bash to run" zone = "HEADERS" # Headers only, and that is deliberate. The attack is delivered in User-Agent, # Referer or Cookie, because those are what CGI turns into environment # variables. The same characters in a body are JavaScript — `function() {};` # is in every script anybody uploads — and matching there would refuse it. # # The definition is followed by ; in the original and by > in the variant that # got past its first fix (CVE-2014-7169). pattern = '\(\)\s*\{[^{}]{0,12};\s*\}\s*[;>]' score = 10 action = "block" [[rules]] id = 1050011 name = "Probe: Struts OGNL expression" description = "CVE-2017-5638 and the Struts 2 holes before and after it — an OGNL expression in Content-Type or a parameter, evaluated by the framework" zone = "ANY" # %{(# opens every one of them, and #_memberAccess is the first thing each # reaches for. Neither means anything outside OGNL. pattern = '%\{\s*\(\s*#|#_memberAccess\b|@ognl\.OgnlContext@' score = 10 action = "block" [[rules]] id = 1050012 name = "Probe: Confluence setup takeover" description = "CVE-2023-22515 — a parameter that tells a finished Confluence it has not been set up, so the request after it can create an administrator" zone = "ANY" # The parameter, not the setup pages it unlocks: those are what a real # installation serves on its first day. pattern = '(?i)bootstrapStatusProvider\.applicationConfig' score = 10 action = "block" [[rules]] id = 1050013 name = "Probe: Exchange Autodiscover SSRF" description = "CVE-2021-34473, ProxyShell — an address in Autodiscover's query that makes Exchange's front end request its own back end as SYSTEM" zone = "URL" # autodiscover.json, a query holding an @, and then a back-end path. The # legitimate endpoint is /autodiscover/autodiscover.json/v1.0/user@domain — the # address is in the path there, not after a ?, and it is not matched. pattern = '(?i)autodiscover\.json\?[^/]{0,100}@[^/]{0,100}/(mapi/nspi|powershell|ews/|owa/|ecp/)' score = 10 action = "block" # ─── Devices ───────────────────────────────────────────────── [[rules]] id = 1050014 name = "Probe: router and camera firmware" description = "Endpoints of home routers, modems and DVRs with unauthenticated command execution: Realtek SDK, GPON, D-Link HNAP, Huawei HG532, Netgear DGN, Linksys, TP-Link Archer (CVE-2023-1389), JAWS" zone = "URL" # Anchored to the root, where a device serves them. What sends these is a # botnet looking for devices to join it — Mirai's descendants — and it sends # them to every address, web servers included. # # /cgi-bin/luci is OpenWrt's interface and is not matched. ;stok=/ with nothing # between the = and the / is the TP-Link exploit: a session token left empty. pattern = '(?i)^/(boaform/|GponForm/|HNAP1/?(\?|$)|ctrlt/DeviceUpgrade|picsdesc\.xml|wanipcn\.xml|UD/act(\?|$)|(tm|hnd)Unblock\.cgi|setup\.cgi\?next_file=|soap\.cgi\?service=|cgi-bin/luci/;stok=/|shell\?cd[+ ])' score = 10 action = "block" [[rules]] id = 1050015 name = "Probe: VPN and gateway appliances" description = "The exploit requests for Citrix ADC (CVE-2019-19781), FortiOS (CVE-2018-13379), F5 BIG-IP (CVE-2020-5902, CVE-2022-1388), Ivanti Connect Secure (CVE-2023-46805), Zyxel (CVE-2022-30525) and vCenter (CVE-2021-21972, CVE-2021-22005)" zone = "URL" # Each alternative is the exploit's request, not the appliance's login page: # the traversal out of /vpn/, the language file that reads the session store, # the ..; that walks past BIG-IP's login, and so on. An appliance's own portal # paths — /remote/login, /dana-na/, /vpn/index.html — are not here. pattern = '(?i)/vpns?/\.\./vpns/|^/remote/fgt_lang\?lang=|/tmui/login\.jsp/\.\.;/|^/mgmt/tm/util/bash|/api/v1/totp/user-backup-code/\.\.|^/ztp/cgi-bin/handler|/ui/vropspluginui/rest/services/uploadova|/analytics/telemetry/ph/api/hyper/send' score = 10 action = "block"