CVE-2023-40034 (published 2023-08-16T21:15Z)

Woodpecker is a community fork of the Drone CI system. In affected versions an attacker can post malformed webhook data witch lead to an update of the repository data that can e.g. allow the takeover of an repo. This is only critical if the CI is configured for public usage and connected to a forge witch is also in public usage. This issue has been addressed in version 1.0.2. Users are advised to upgrade. Users unable to upgrade should secure the CI system by making it inaccessible to untrusted entities, for example, by placing it behind a firewall.

CVSS v3 base score: 8.1, HIGH (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/woodpecker-ci/woodpecker
commit_details: repo_url: https://github.com/woodpecker-ci/woodpecker
author: 6543
author_date: 2023-08-16 16:19:10+02:00
msg: Validate webhook before change any data (#2221) (#2222)

Backport #2221
repository: repo_url: https://github.com/woodpecker-ci/woodpecker
repo_name: woodpecker-ci/woodpecker
description: Woodpecker is a simple yet powerful CI/CD engine with great extensibility.
repo_language: Go
owner: woodpecker-ci
file_changes: filename: hook.go
change_type: ModificationType.MODIFY
diff: @@ -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,13 +213,30 @@ 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)
@@ -219,6 +244,10 @@ func PostHook(c *gin.Context) {
 		return
 	}
 
+	//
+	// 6. Finally create a pipeline
+	//
+
 	pl, err := pipeline.Create(c, _store, repo, tmpBuild)
 	if err != nil {
 		handlePipelineErr(c, err)

programming_language: Go