Nezha Dashboard, Cross-Site Request Forgery (CSRF), No CVE -DC-Jun2026-340

Listen to this Post

The vulnerability exists because the Nezha dashboard exposes a state-changing cron manual-trigger action as an authenticated GET endpoint at /api/v1/cron/:id/manual. The dashboard uses JWT authentication stored in a cookie named `nz-jwt` with `SameSite=Lax` attribute. Under SameSite=Lax, browsers still send the cookie on top-level cross-site GET navigations, but not on cross-site POST requests. Since the endpoint is a GET request and lacks any CSRF token, Origin/Referer validation, or Fetch Metadata guard, an attacker can force a logged-in victim’s browser to trigger a cron task simply by navigating to the crafted URL.
The source-to-sink chain begins with the route registration in `cmd/dashboard/controller/controller.go:131-134` where `auth.GET(“/cron/:id/manual”, commonHandler(manualTriggerCron))` is defined. JWT configuration in `cmd/dashboard/controller/jwt.go:23-46` sets CookieName: "nz-jwt", SendCookie: true, CookieSameSite: http.SameSiteLaxMode, and token lookup from cookie. The handler `manualTriggerCron` at `cmd/dashboard/controller/cron.go:170-187` parses the cron ID, loads the cron object, checks permissions via `cr.HasPermission(c)` (which only verifies user ownership or admin rights), then calls singleton.ManualTrigger(cr). This triggers `CronTrigger(cr)()` at service/singleton/crontask.go:249-250, which dispatches the stored command to online eligible agents through `s.TaskStream.Send(&pb.Task{Id: cr.ID, Data: cr.Command, Type: model.TaskTypeCommand})` at service/singleton/crontask.go:289-304. The permission check ensures the attacker can only trigger cron tasks owned by the victim (not create or modify them), but the CSRF abuses the victim’s active session to satisfy that check.
The attacker must know or guess a numeric cron ID belonging to the victim. The PoC provided in the demonstrates the issue with an in-memory SQLite database, a victim user, a fake agent stream, and a cron task with command touch /tmp/should-not-run. A cross-site GET request with the victim’s `nz-jwt` cookie successfully dispatches the task, while a request without the cookie fails with ApiErrorUnauthorized. The impact allows remote forced execution of stored commands on victim-controlled agents, affecting integrity and availability. No confidentiality breach is shown. Remediation suggests changing the endpoint to POST, adding CSRF tokens, or validating Origin/Referer headers.

DailyCVE Form:

Platform: Nezha Dashboard
Version: Current master
Vulnerability: CSRF cron trigger
Severity: Medium
date: 2026-06-10

Prediction: 2026-06-30

What Undercode Say:

PoC test overlay creation
cat >/tmp/nezha-docs-stub.go <<'EOF'
package docs
var SwaggerInfo = struct {
Version string
}{Version: "test"}
EOF
cat >/tmp/nezha-cron-csrf-poc-test.go <<'EOF'
package controller
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/patrickmn/go-cache"
"google.golang.org/grpc/metadata"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/nezhahq/nezha/model"
"github.com/nezhahq/nezha/pkg/i18n"
pb "github.com/nezhahq/nezha/proto"
"github.com/nezhahq/nezha/service/singleton"
)
type capturedTaskStream struct { tasks []pb.Task }
func (s capturedTaskStream) Send(task pb.Task) error { s.tasks = append(s.tasks, task); return nil }
// ... (other stream methods)
func TestCronManualTriggerAcceptsCookieAuthenticatedCrossSiteGET(t testing.T) {
// Setup in-memory DB, victim user, server, cron task with command "touch /tmp/should-not-run"
// Perform negative control (no cookie) -> expect ApiErrorUnauthorized, no task dispatched
// Perform positive proof (with cookie) -> expect task dispatched with ID 7 and command
}
EOF
Run test with overlay
go test -vet=off -overlay=/tmp/nezha-overlay-cron-csrf.json ./cmd/dashboard/controller -run TestCronManualTriggerAcceptsCookieAuthenticatedCrossSiteGET -count=1 -v
Cleanup
rm -f /tmp/nezha-docs-stub.go /tmp/nezha-cron-csrf-poc-test.go /tmp/nezha-overlay-cron-csrf.json

Exploit:

Attacker crafts a malicious webpage or link containing `` or uses window.location. When a logged-in victim visits the attacker’s site, the browser sends the GET request with the `nz-jwt` cookie due to SameSite=Lax. The dashboard triggers cron ID 7, executing its stored command on the victim’s online agent. No user interaction other than visiting the link is required.

Protection:

  • Change the endpoint from GET to POST (or PUT) to leverage `SameSite=Lax` cookie protection against cross-site POST requests.
  • Implement CSRF tokens for all state-changing API calls authenticated via cookies.
  • Validate `Origin` and `Referer` headers against a trusted list, and reject cross-site requests using Fetch Metadata headers (Sec-Fetch-Site: cross-site).
  • Remove cookie-based authentication for sensitive actions and require bearer tokens in Authorization headers only.

Impact:

  • Integrity: Forced execution of stored cron commands (e.g., rm, touch, scripts) on victim’s agents, potentially altering system state.
  • Availability: Repeated triggers can exhaust resources, disrupt services, or cause unintended notifications/offline actions.
  • Scope: Limited to existing cron tasks owned by the victim; no command creation or modification. Attacker must know numeric cron ID (enumerable).

🎯Let’s Practice Exploiting & Learn Patching For Free:

🎓 Live Courses & Certifications:

Join Undercode Academy for Verified Certifications

🚀 Request a Custom Project:

Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands

Sources:

Reported By: github.com
Extra Source Hub:
Undercode

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top