VYPR
High severityNVD Advisory· Published Aug 16, 2023· Updated Oct 2, 2024

Repositoty takeover in woodpecker-ci

CVE-2023-40034

Description

In Woodpecker CI, malformed webhooks can be exploited before validation, potentially allowing repository takeover if the system is publicly accessible.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

In Woodpecker CI, malformed webhooks can be exploited before validation, potentially allowing repository takeover if the system is publicly accessible.

What the vulnerability is

CVE-2023-40034 is a vulnerability in Woodpecker CI, a community fork of Drone CI, where an attacker can send malformed webhook data that is processed before proper validation. The root cause, as shown in the commit [2], is that the original code handled webhook data and updated the repository (repo.Update(tmpRepo)) before verifying the webhook's authenticity or checking if the repository had an owner (repo.UserID == 0). This allowed arbitrary updates to repository metadata based on unvalidated input.

How it's exploited

An attacker exploits this by posting a crafted webhook to a publicly exposed Woodpecker instance. The attack requires the CI system to be configured for public usage and connected to a forge (e.g., GitHub, Gitea) that is also publicly accessible [1][4]. No authentication is needed if the webhook endpoint is open; the attacker only needs to know the correct URL. The webhook payload can manipulate repository fields such as the remote ID or full name, leading to unauthorized modifications.

Impact

Successful exploitation can allow an attacker to take over a repository within the CI system. By altering the repository data, the attacker could redirect builds, access secrets, or gain control over the repository's CI pipeline [1]. This is especially critical in public-facing deployments where the forge is also public, as it enables external attackers to compromise repositories.

Mitigation

The vulnerability was patched in Woodpecker version 1.0.2, released August 16, 2023 [1]. The fix [2][3] moves webhook validation (checking authorization, repository ownership) before any data modification. Users should upgrade to 1.0.2 or later. For those unable to upgrade, a workaround is to restrict network access to the CI system, e.g., via a firewall, to prevent untrusted entities from reaching the webhook endpoint [4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/woodpecker-ci/woodpeckerGo
>= 1.0.0, < 1.0.21.0.2

Affected products

2

Patches

1
6e4c2f84cc84

Validate webhook before change any data (#2221) (#2222)

1 file changed · +46 17
  • server/api/hook.go+46 17 modified
    @@ -108,6 +108,10 @@ func PostHook(c *gin.Context) {
     	_store := store.FromContext(c)
     	forge := server.Config.Services.Forge
     
    +	//
    +	// 1. Parse webhook
    +	//
    +
     	tmpRepo, tmpBuild, err := forge.Hook(c, c.Request)
     	if err != nil {
     		if errors.Is(err, &types.ErrIgnoreEvent{}) {
    @@ -136,6 +140,11 @@ func PostHook(c *gin.Context) {
     		return
     	}
     
    +	//
    +	// Skip if commit message contains skip-ci
    +	// TODO: move into global pipeline conditions logic
    +	//
    +
     	// skip the tmpBuild if any case-insensitive combination of the words "skip" and "ci"
     	// wrapped in square brackets appear in the commit message
     	skipMatch := skipRe.FindString(tmpBuild.Message)
    @@ -146,6 +155,10 @@ func PostHook(c *gin.Context) {
     		return
     	}
     
    +	//
    +	// 2. Get related repo from store and take repo renaming into account
    +	//
    +
     	repo, err := _store.GetRepoNameFallback(tmpRepo.ForgeRemoteID, tmpRepo.FullName)
     	if err != nil {
     		msg := fmt.Sprintf("failure to get repo %s from store", tmpRepo.FullName)
    @@ -159,24 +172,19 @@ func PostHook(c *gin.Context) {
     		c.String(http.StatusNoContent, msg)
     		return
     	}
    -
     	oldFullName := repo.FullName
    -	if oldFullName != tmpRepo.FullName {
    -		// create a redirection
    -		err = _store.CreateRedirection(&model.Redirection{RepoID: repo.ID, FullName: repo.FullName})
    -		if err != nil {
    -			_ = c.AbortWithError(http.StatusInternalServerError, err)
    -			return
    -		}
    -	}
     
    -	repo.Update(tmpRepo)
    -	err = _store.UpdateRepo(repo)
    -	if err != nil {
    -		c.String(http.StatusInternalServerError, err.Error())
    +	if repo.UserID == 0 {
    +		msg := fmt.Sprintf("ignoring hook. repo %s has no owner.", repo.FullName)
    +		log.Warn().Msg(msg)
    +		c.String(http.StatusNoContent, msg)
     		return
     	}
     
    +	//
    +	// 3. Check if the webhook is a valid and authorized one
    +	//
    +
     	// get the token and verify the hook is authorized
     	parsed, err := token.ParseRequest(c.Request, func(_ *token.Token) (string, error) {
     		return repo.Hash, nil
    @@ -205,20 +213,41 @@ func PostHook(c *gin.Context) {
     		return
     	}
     
    -	if repo.UserID == 0 {
    -		msg := fmt.Sprintf("ignoring hook. repo %s has no owner.", repo.FullName)
    -		log.Warn().Msg(msg)
    -		c.String(http.StatusNoContent, msg)
    +	//
    +	// 4. Update repo
    +	//
    +
    +	if oldFullName != tmpRepo.FullName {
    +		// create a redirection
    +		err = _store.CreateRedirection(&model.Redirection{RepoID: repo.ID, FullName: repo.FullName})
    +		if err != nil {
    +			_ = c.AbortWithError(http.StatusInternalServerError, err)
    +			return
    +		}
    +	}
    +
    +	repo.Update(tmpRepo)
    +	err = _store.UpdateRepo(repo)
    +	if err != nil {
    +		c.String(http.StatusInternalServerError, err.Error())
     		return
     	}
     
    +	//
    +	// 5. Check if pull requests are allowed for this repo
    +	//
    +
     	if tmpBuild.Event == model.EventPull && !repo.AllowPull {
     		msg := "ignoring hook: pull requests are disabled for this repo in woodpecker"
     		log.Debug().Str("repo", repo.FullName).Msg(msg)
     		c.String(http.StatusNoContent, msg)
     		return
     	}
     
    +	//
    +	// 6. Finally create a pipeline
    +	//
    +
     	pl, err := pipeline.Create(c, _store, repo, tmpBuild)
     	if err != nil {
     		handlePipelineErr(c, err)
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.