KEV Guardrails on BIG-IP: iRules + AWAF custom violations for Vite, Versa Concerto, and Zimbra
CISA added four actively exploited bugs to the Known Exploited Vulnerabilities (KEV) catalog, and BleepingComputer provides the best “single page of context” for what’s in scope and why people responsible for application delivery should care.
If you’re an F5 admin (or the person who got voluntold to be), BIG-IP can buy you time with surgical L7 guardrails: detect risky request patterns at the VIP, raise a custom violation, and let the security policy decide whether to stage, alarm, or block.
This post focuses on the three web/L7-facing items where an F5 customer can deploy meaningful mitigations at the edge:
- Vite dev server file exposure (CVE-2025-31125)
- Versa Concerto auth bypass / admin surface exposure (CVE-2025-34026)
- Zimbra Classic UI /h/rest file inclusion / LFI class issue (CVE-2025-68645)
The fourth KEV item mentioned (eslint-config-prettier supply chain compromise) is not an inbound-L7/WAF-signature problem in the usual sense—handle it in CI/CD and endpoint controls instead of trying to “iRule your way out of npm.”
TL;DR (for skimmers and change-control survivors)
Goal: Deploy three separate iRules (one per app family) that detect high-signal suspicious requests and raise User-Defined Violations (UDVs) when a Security Policy is attached. The policy then controls whether that becomes Staging, Alarm, or Block.
You will do:
- Create 3 UDVs (one per iRule)
- Configure those UDVs in your security policy (Staging → Alarm/Block)
- Attach the WAF policy + the iRule to the correct VIPs (AS3 optional)
- Validate in staging/transparent mode, then enforce if needed
You will not do: treat iRules as the “fix.” Patch and/or remove exposure. This is a guardrail, not a cure.
Why UDVs instead of hard-blocking in HTTP_REQUEST?
If you hard HTTP::respond 403 in HTTP_REQUEST, you get quick relief…but also:
- Fewer tuning options,
- Less visibility/consistency in security reporting,
- And a higher chance of “who broke my app?” tickets.
Using UDVs keeps the detection logic close to the VIP while letting the policy decide enforcement:
- ASM::raise <violation-name> <details> adds a user-defined violation to the transaction.
- The security policy decides whether that violation is staged, alarms, or blocks.
Critical detail: where ASM::raise is valid
ASM::raise is valid in ASM events, not HTTP_REQUEST. The iRules reference lists valid events as ASM_REQUEST_DONE and ASM_REQUEST_VIOLATION.
So we’ll use a two-stage flow:
- Detect in HTTP_REQUEST and set a per-request flag
- Raise the UDV in ASM_REQUEST_DONE (where it’s valid)
ASM_REQUEST_DONE fires after ASM has processed the request and before it enforces, which is exactly the timing we want.
Threat overview (plain English, no exploit cookbook)
Vite dev server (CVE-2025-31125)
If a Vite dev server is exposed to a network, certain request patterns can lead to the exposure of files that should be denied. The real fix is “don’t publish dev servers,” but we can add guardrails while you clean up exposure.
Versa Concerto (CVE-2025-34026)
Auth-bypass conditions can lead to access of sensitive administrative surfaces (including common framework management endpoints). Practical mitigations focus on restricting those endpoints and blocking suspicious header-manipulation behaviors.
Zimbra Classic UI (CVE-2025-68645)
Crafted requests to Classic UI REST paths can enable file-inclusion/LFI-class behavior. The practical edge mitigation is to restrict /h/rest and block obvious include/traversal patterns.
Design approach on BIG-IP
We deploy three separate iRules because:
- The VIPs are different,
- The app owners are different,
- And bundling them together is how you end up debugging a mail outage while the networking team swears “we didn’t change anything.”
Each iRule:
- Detects a small set of high-signal indicators,
- Logs a short line for triage,
- Sets a flag in HTTP_REQUEST,
- Raises a UDV in ASM_REQUEST_DONE using ASM::raise,
- And relies on the policy to stage/alarm/block.
iRule 1: Versa Concerto guardrail (CVE-2025-34026)
TL;DR
Flags:
- External access to /actuator (unless allowlisted),
- Connection header manipulation referencing X-Real-Ip,
- Optionally semicolons in the path (test first).
UDV name: UDV_VERSA_CONCERTO_GUARDRAIL
Optional DG: dg_concerto_mgmt_allowlist (Address type)
when RULE_INIT {
set static::udv "UDV_VERSA_CONCERTO_GUARDRAIL"
set static::mgmt_allowlist_dg "dg_concerto_mgmt_allowlist"
set static::log 1
}
when HTTP_REQUEST {
# Clear per-request state (important for keep-alive)
unset -nocomplain kev_flag kev_reason kev_detail
set path [string tolower [HTTP::path]]
set kev_flag 0
set kev_reason ""
set kev_detail ""
# 1) /actuator exposure (typically should be internal-only)
if { $path starts_with "/actuator" } {
if { ![class exists $static::mgmt_allowlist_dg] ||
![class match [IP::client_addr] equals $static::mgmt_allowlist_dg] } {
set kev_flag 1
set kev_reason "external_actuator_access"
}
}
# 2) Connection header manipulation referencing X-Real-Ip
if { !$kev_flag && [HTTP::header exists "Connection"] } {
set conn [string tolower [HTTP::header "Connection"]]
if { $conn contains "x-real-ip" } {
set kev_flag 1
set kev_reason "conn_header_x_real_ip_manipulation"
}
}
# 3) Optional hardening: semicolons in path (test first)
if { !$kev_flag && ($path contains ";") } {
set kev_flag 1
set kev_reason "semicolon_in_path"
}
if { $kev_flag } {
set kev_detail "reason=$kev_reason host=[HTTP::host] uri=[HTTP::uri]"
if { $static::log } {
log local0. "VERSA_GUARDRAIL candidate $kev_detail ip=[IP::client_addr]"
}
# Do NOT call ASM::raise here (not a valid event)
}
}
when ASM_REQUEST_DONE {
if { [info exists kev_flag] && $kev_flag } {
# ASM::raise is valid here.
ASM::raise $static::udv $kev_detail
}
}
Tuning notes
- If you truly need /actuator from outside, use the allowlist DG and keep it tight.
- The semicolon check is optional because some apps legitimately use matrix parameters (rare, but it happens).
iRule 2: Zimbra Classic UI guardrail (CVE-2025-68645)
TL;DR
On requests to /h/rest:
- Optionally disable public access (strongest control),
- Flag servlet include-style parameter keys,
- Flag traversal patterns (decoded + encoded).
UDV name: UDV_ZIMBRA_HREST_GUARDRAIL
when RULE_INIT {
set static::udv "UDV_ZIMBRA_HREST_GUARDRAIL"
set static::log 1
set static::zimbra_public_rest 1 ;# 0 = never allow /h/rest publicly
}
when HTTP_REQUEST {
unset -nocomplain kev_flag kev_reason kev_detail
set uri [string tolower [HTTP::uri]]
set path [string tolower [HTTP::path]]
set q [string tolower [HTTP::query]]
set kev_flag 0
set kev_reason ""
set kev_detail ""
if { $path starts_with "/h/rest" } {
# Strongest mitigation: disable public /h/rest
if { !$static::zimbra_public_rest } {
set kev_flag 1
set kev_reason "hrest_public_disabled"
}
# servlet include/dispatch parameter keys (high-signal)
if { !$kev_flag && [regexp -nocase {javax\.servlet\.include\.} $q] } {
set kev_flag 1
set kev_reason "servlet_include_params"
}
# generic traversal patterns (decoded + encoded)
if { !$kev_flag && [regexp -nocase {\.\./|%2e%2e%2f|%2e%2e\\|%252e%252e%252f} $uri] } {
set kev_flag 1
set kev_reason "path_traversal_pattern"
}
if { $kev_flag } {
set kev_detail "reason=$kev_reason host=[HTTP::host] uri=[HTTP::uri]"
if { $static::log } {
log local0. "ZIMBRA_GUARDRAIL candidate $kev_detail ip=[IP::client_addr]"
}
}
}
}
when ASM_REQUEST_DONE {
if { [info exists kev_flag] && $kev_flag } {
ASM::raise $static::udv $kev_detail
}
}
Tuning notes
- If you can set zimbra_public_rest 0, that’s the cleanest edge control.
- If /h/rest must remain public, start with staging/alarm and tune.
iRule 3: Vite dev server guardrail (CVE-2025-31125)
TL;DR
Flags:
- Dev filesystem routes (notably /@fs/)
- Suspicious dev query combos
UDV name: UDV_VITE_DEVSERVER_GUARDRAIL
when RULE_INIT {
set static::udv "UDV_VITE_DEVSERVER_GUARDRAIL"
set static::log 1
}
when HTTP_REQUEST {
unset -nocomplain kev_flag kev_reason kev_detail
set path [string tolower [HTTP::path]]
set q [string tolower [HTTP::query]]
set kev_flag 0
set kev_reason ""
set kev_detail ""
# Vite dev server filesystem route
if { $path starts_with "/@fs/" } {
set kev_flag 1
set kev_reason "vite_fs_route"
}
# Suspicious dev query combos
if { !$kev_flag && ($q contains "import") && ( ($q contains "inline") || ($q contains "raw") ) } {
set kev_flag 1
set kev_reason "vite_import_inline_raw_combo"
}
if { $kev_flag } {
set kev_detail "reason=$kev_reason host=[HTTP::host] uri=[HTTP::uri]"
if { $static::log } {
log local0. "VITE_GUARDRAIL candidate $kev_detail ip=[IP::client_addr]"
}
}
}
when ASM_REQUEST_DONE {
if { [info exists kev_flag] && $kev_flag } {
ASM::raise $static::udv $kev_detail
}
}
Tuning notes
- If this VIP is internet-facing, you’re already in “why is this exposed?” territory. The iRule is a seatbelt; the real fix is closing the garage door.
Advanced WAF/ASM setup: create and manage the custom violations
F5’s ASM documentation covers the general flow: define violations, configure whether they alarm/block, and monitor.
TL;DR
Create the UDVs globally, then configure how they behave inside the policy (staging/alarm/block). iRules just raise them.
Step 1 — Create the three User-Defined Violations (UDVs)
In the BIG-IP UI (menu labels vary by version, but the concept is consistent):
- Navigate to Security → Application Security → (Advanced Configuration/Options) → Violations
- Find User-Defined Violations
- Create:
- UDV_VERSA_CONCERTO_GUARDRAIL
- UDV_ZIMBRA_HREST_GUARDRAIL
- UDV_VITE_DEVSERVER_GUARDRAIL
Step 2 — Configure staging/alarm/block behavior in your policy
In each affected security policy:
- Locate the three UDVs on the violations list
- Set to Staging initially (recommended)
- Optionally move to Alarm or Block once you’ve validated signal/noise
This is where you control the outcome in transparent vs blocking mode.
Step 3 — Confirm the event timing is correct
ASM::raise must be called from valid ASM events; we’re using ASM_REQUEST_DONE.
BIG-IP deployment (VIP-by-VIP)
TL;DR
Attach the correct iRule + the correct policy to the correct VIP. Don’t “globalize” this unless you like surprise side effects.
1) (Optional) Create the Versa allowlist datagroup
If you want to allow only trusted sources to access /actuator:
- Create an Address datagroup named dg_concerto_mgmt_allowlist
- Add trusted admin IPs/CIDRs
2) Create the iRules
- Local Traffic → iRules → iRule List → Create
- Paste the relevant iRule
- Save
3) Attach iRule + security policy to the VIP
- Local Traffic → Virtual Servers → → Resources
- Add the appropriate iRule
- Ensure the VIP also has the security policy attached
Mapping guidance
- Vite iRule → only the VIP(s) fronting Vite dev servers
- Zimbra iRule → only Zimbra VIP(s)
- Versa iRule → only Versa Concerto VIP(s)
AS3: attaching WAF policy + iRule (UDVs still managed in the policy)
AS3 can declaratively attach a WAF policy using policyWAF and attach an iRule using the service’s iRules property.
TL;DR
Use AS3 to bind:
- policyWAF → your WAF policy object (referenced or delivered via URL)
- iRules → the correct guardrail iRule for that VIP
Example AS3 declaration (single VIP)
{
"class": "ADC",
"schemaVersion": "3.26.0",
"id": "KEV-Guardrails",
"Tenant1": {
"class": "Tenant",
"App1": {
"class": "Application",
"service": {
"class": "Service_HTTP",
"virtualAddresses": [
"192.0.2.10"
],
"virtualPort": 80,
"policyWAF": {
"use": "wafPolicy"
},
"iRules": [
"irule_zimbra_guardrail"
]
},
"wafPolicy": {
"class": "WAF_Policy",
"url": "https://example.invalid/policies/my_awaf_policy.json",
"ignoreChanges": false
}
}
}
}
Notes
- WAF_Policy is the AS3 object for WAF policies, and the AS3 Application Security guide shows reference patterns.
- AS3 typically attaches policies and app objects; UDVs are configured within the policy lifecycle (created on-box / in policy management), not created the same way as pools/virtuals.
Safe rollout (a.k.a. “don’t be the reason people learn your name”)
TL;DR rollout plan
- Attach iRule + policy
- Set UDV to Staging (or Alarm-only)
- Watch logs for 24h
- Tune allowlists/exceptions
- Move UDV to Block if signal is good
What to watch
- /var/log/ltm for:
- VERSA_GUARDRAIL candidate ...
- ZIMBRA_GUARDRAIL candidate ...
- VITE_GUARDRAIL candidate ...
- Security event logs for the UDV names (since ASM::raise adds the violation to the transaction).
Common false-positive hotspots
- Semicolon blocking (;) can break legacy matrix parameters—use staging first.
- Zimbra /h/rest access patterns may vary. If it must be public, rely on staging/alarm and tune.
Thanx and closing
First, remember that whatever you read here should be taken with your own understanding of your applications. Review the code, check it and test it outside of production. If you have suggestions for updates, I’d love to hear them.
And finally, thanx to a wide team of friends this weekend who took the time to review my notes (and my AI-generated noise) and provide valuable and supportive feedback.
Help guide the future of your DevCentral Community!
What tools do you use to collaborate? (1min - anonymous)