Improper Control of Generation of Code ('Code Injection')

The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.

Improper Neutralization of Special Elements used in a Command ('Command Injection')

The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.

Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.

module.exports = exports = function (proto) {
  function compare(orig, compareTo, options, cb) {
    orig = utils.escape(orig);
    compareTo = utils.escape(compareTo);

    var isImageMagick = this._options && this._options.imageMagick;
    // compare binary for IM is `compare`, for GM it's `gm compare`
    var bin = isImageMagick ? '' : 'gm ';
    var execCmd = bin + 'compare -metric mse ' + orig + ' ' + compareTo;
    var tolerance = 0.4;
    // outputting the diff image
    if (typeof options === 'object') {

      if (options.highlightColor && options.highlightColor.indexOf('"') < 0) {
        options.highlightColor = '"' + options.highlightColor + '"';
      }

      if (options.file) {
        if (typeof options.file !== 'string') {
          throw new TypeError('The path for the diff output is invalid');
        }
         // graphicsmagick defaults to red
        var highlightColorOption = options.highlightColor
          ? ' -highlight-color ' + options.highlightColor + ' '
          : ' ';
        var highlightStyleOption = options.highlightStyle
          ? ' -highlight-style ' + options.highlightStyle + ' '
          : ' ';
        var diffFilename = utils.escape(options.file);
        // For IM, filename is the last argument. For GM it's `-file <filename>`
        var diffOpt = isImageMagick ? diffFilename : ('-file ' + diffFilename);
        execCmd += highlightColorOption + highlightStyleOption  + ' ' + diffOpt;
      }

      if (typeof options.tolerance != 'undefined') {
        if (typeof options.tolerance !== 'number') {
          throw new TypeError('The tolerance value should be a number');
        }
        tolerance = options.tolerance;
      }
    } else {
      // For ImageMagick diff file is required but we don't care about it, so null it out
      isImageMagick && (execCmd += ' null:');

      if (typeof options == 'function') {
        cb = options; // tolerance value not provided, flip the cb place
      } else {
        tolerance = options
      }
    }

    exec(execCmd, function (err, stdout, stderr) {
      // ImageMagick returns err code 2 if err, 0 if similar, 1 if dissimilar
      if (isImageMagick) {
        if (!err) {
          return cb(null, 0 <= tolerance, 0, stdout);
        }
        if (err.code === 1) {
          err = null;
          stdout = stderr;
        }
      }
      if (err) {
        return cb(err);
      }
      // Since ImageMagick similar gives err code 0 and no stdout, there's really no matching
      // Otherwise, output format for IM is `12.00 (0.123)` and for GM it's `Total: 0.123`
      var regex = isImageMagick ? /\((\d+\.?[\d\-\+e]*)\)/m : /Total: (\d+\.?\d*)/m;
      var match = regex.exec(stdout);
      if (!match) {
        err = new Error('Unable to parse output.\nGot ' + stdout);
        return cb(err);
      }

      var equality = parseFloat(match[1]);
      cb(null, equality <= tolerance, equality, stdout, utils.unescape(orig), utils.unescape(compareTo));
    });
  }

  if (proto) {
    proto.compare = compare;
  }
  return compare;
};

module.exports = exports = function (proto) {
  function compare(orig, compareTo, options, cb) {

    var isImageMagick = this._options && this._options.imageMagick;
    // compare binary for IM is `compare`, for GM it's `gm compare`
    var bin = isImageMagick ? '' : 'gm ';
    var args = ['compare', '-metric', 'mse', orig, compareTo]
    var tolerance = 0.4;
    // outputting the diff image
    if (typeof options === 'object') {

      if (options.highlightColor && options.highlightColor.indexOf('"') < 0) {
        options.highlightColor = '"' + options.highlightColor + '"';
      }

      if (options.file) {
        if (typeof options.file !== 'string') {
          throw new TypeError('The path for the diff output is invalid');
        }
         // graphicsmagick defaults to red
        if (options.highlightColour) {
            args.push('-highlight-color');
            args.push(options.highlightColor);
        }
        if (options.highlightStyle) {
            args.push('-highlight-style')
            args.push(options.highlightStyle)
        }
        // For IM, filename is the last argument. For GM it's `-file <filename>`
        if (isImageMagick) {
            args.push('-file');
        }
        args.push(options.file);
      }

      if (typeof options.tolerance != 'undefined') {
        if (typeof options.tolerance !== 'number') {
          throw new TypeError('The tolerance value should be a number');
        }
        tolerance = options.tolerance;
      }
    } else {
      // For ImageMagick diff file is required but we don't care about it, so null it out
      if (isImageMagick) {
        args.push('null:');
      }

      if (typeof options == 'function') {
        cb = options; // tolerance value not provided, flip the cb place
      } else {
        tolerance = options
      }
    }

    var proc = spawn('/usr/bin/gm', args);
    var stdout = '';
    var stderr = '';
    proc.stdout.on('data',function(data) { stdout+=data });
    proc.stderr.on('data',function(data) { stderr+=data });
    proc.on('close', function (code) {
      // ImageMagick returns err code 2 if err, 0 if similar, 1 if dissimilar
      if (isImageMagick) {
        if (code === 0) {
          return cb(null, 0 <= tolerance, 0, stdout);
        }
        else if (code === 1) {
          err = null;
          stdout = stderr;
        } else {
        return cb(stderr);
        }
      } else {
        if(code !== 0) {
          return cb(stderr);
        }
      }
      // Since ImageMagick similar gives err code 0 and no stdout, there's really no matching
      // Otherwise, output format for IM is `12.00 (0.123)` and for GM it's `Total: 0.123`
      var regex = isImageMagick ? /\((\d+\.?[\d\-\+e]*)\)/m : /Total: (\d+\.?\d*)/m;
      var match = regex.exec(stdout);
      if (!match) {
        err = new Error('Unable to parse output.\nGot ' + stdout);
        return cb(err);
      }

      var equality = parseFloat(match[1]);
      cb(null, equality <= tolerance, equality, stdout, orig, compareTo);
    });
  }

  if (proto) {
    proto.compare = compare;
  }
  return compare;
};

--- a/lib/compare.js
+++ b/lib/compare.js
@@ -1,12 +1,10 @@
 module.exports = exports = function (proto) {
   function compare(orig, compareTo, options, cb) {
-    orig = utils.escape(orig);
-    compareTo = utils.escape(compareTo);

     var isImageMagick = this._options && this._options.imageMagick;
     // compare binary for IM is `compare`, for GM it's `gm compare`
     var bin = isImageMagick ? '' : 'gm ';
-    var execCmd = bin + 'compare -metric mse ' + orig + ' ' + compareTo;
+    var args = ['compare', '-metric', 'mse', orig, compareTo]
     var tolerance = 0.4;
     // outputting the diff image
     if (typeof options === 'object') {
@@ -20,16 +18,19 @@
           throw new TypeError('The path for the diff output is invalid');
         }
          // graphicsmagick defaults to red
-        var highlightColorOption = options.highlightColor
-          ? ' -highlight-color ' + options.highlightColor + ' '
-          : ' ';
-        var highlightStyleOption = options.highlightStyle
-          ? ' -highlight-style ' + options.highlightStyle + ' '
-          : ' ';
-        var diffFilename = utils.escape(options.file);
+        if (options.highlightColour) {
+            args.push('-highlight-color');
+            args.push(options.highlightColor);
+        }
+        if (options.highlightStyle) {
+            args.push('-highlight-style')
+            args.push(options.highlightStyle)
+        }
         // For IM, filename is the last argument. For GM it's `-file <filename>`
-        var diffOpt = isImageMagick ? diffFilename : ('-file ' + diffFilename);
-        execCmd += highlightColorOption + highlightStyleOption  + ' ' + diffOpt;
+        if (isImageMagick) {
+            args.push('-file');
+        }
+        args.push(options.file);
       }

       if (typeof options.tolerance != 'undefined') {
@@ -40,7 +41,9 @@
       }
     } else {
       // For ImageMagick diff file is required but we don't care about it, so null it out
-      isImageMagick && (execCmd += ' null:');
+      if (isImageMagick) {
+        args.push('null:');
+      }

       if (typeof options == 'function') {
         cb = options; // tolerance value not provided, flip the cb place
@@ -49,19 +52,27 @@
       }
     }

-    exec(execCmd, function (err, stdout, stderr) {
+    var proc = spawn('/usr/bin/gm', args);
+    var stdout = '';
+    var stderr = '';
+    proc.stdout.on('data',function(data) { stdout+=data });
+    proc.stderr.on('data',function(data) { stderr+=data });
+    proc.on('close', function (code) {
       // ImageMagick returns err code 2 if err, 0 if similar, 1 if dissimilar
       if (isImageMagick) {
-        if (!err) {
+        if (code === 0) {
           return cb(null, 0 <= tolerance, 0, stdout);
         }
-        if (err.code === 1) {
+        else if (code === 1) {
           err = null;
           stdout = stderr;
+        } else {
+        return cb(stderr);
         }
-      }
-      if (err) {
-        return cb(err);
+      } else {
+        if(code !== 0) {
+          return cb(stderr);
+        }
       }
       // Since ImageMagick similar gives err code 0 and no stdout, there's really no matching
       // Otherwise, output format for IM is `12.00 (0.123)` and for GM it's `Total: 0.123`
@@ -73,7 +84,7 @@
       }

       var equality = parseFloat(match[1]);
-      cb(null, equality <= tolerance, equality, stdout, utils.unescape(orig), utils.unescape(compareTo));
+      cb(null, equality <= tolerance, equality, stdout, orig, compareTo);
     });
   }


MLflow allowed arbitrary files to be PUT onto the server.

External Control of File Name or Path

The product allows user input to control or influence paths or file names that are used in filesystem operations.

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.

def validate_path_is_safe(path):
    """
    Validates that the specified path is safe to join with a trusted prefix. This is a security
    measure to prevent path traversal attacks.
    A valid path should:
        not contain separators other than '/'
        not contain .. to navigate to parent dir in path
        not be an absolute path
    """
    if is_file_uri(path):
        path = local_file_uri_to_path(path)
    if (
        any((s in path) for s in _OS_ALT_SEPS)
        or ".." in path.split("/")
        or pathlib.PureWindowsPath(path).is_absolute()
        or pathlib.PurePosixPath(path).is_absolute()
    ):
        raise MlflowException(f"Invalid path: {path}", error_code=INVALID_PARAMETER_VALUE)

def validate_path_is_safe(path):
    """
    Validates that the specified path is safe to join with a trusted prefix. This is a security
    measure to prevent path traversal attacks.
    A valid path should:
        not contain separators other than '/'
        not contain .. to navigate to parent dir in path
        not be an absolute path
    """
    if is_file_uri(path):
        path = local_file_uri_to_path(path)
    if (
        any((s in path) for s in _OS_ALT_SEPS)
        or ".." in path.split("/")
        or pathlib.PureWindowsPath(path).is_absolute()
        or pathlib.PurePosixPath(path).is_absolute()
        or (is_windows() and len(path) >= 2 and path[1] == ":")
    ):
        raise MlflowException(f"Invalid path: {path}", error_code=INVALID_PARAMETER_VALUE)

--- a/mlflow/server/handlers.py
+++ b/mlflow/server/handlers.py
@@ -14,5 +14,6 @@
         or ".." in path.split("/")
         or pathlib.PureWindowsPath(path).is_absolute()
         or pathlib.PurePosixPath(path).is_absolute()
+        or (is_windows() and len(path) >= 2 and path[1] == ":")
     ):
         raise MlflowException(f"Invalid path: {path}", error_code=INVALID_PARAMETER_VALUE)


Slack Nebula through 1.1.0 contains a relative path vulnerability that allows a low-privileged attacker to execute code in the context of the root user via tun_darwin.go or tun_windows.go. A user can also use Nebula to execute arbitrary code in the user's own context, e.g., for user-level persistence or to bypass security controls. NOTE: the vendor states that this "requires a high degree of access and other preconditions that are tough to achieve."

func (c *Tun) Activate() error {
	var err error
	c.Interface, err = water.New(water.Config{
		DeviceType: water.TUN,
	})
	if err != nil {
		return fmt.Errorf("Activate failed: %v", err)
	}

	c.Device = c.Interface.Name()

	// TODO use syscalls instead of exec.Command
	if err = exec.Command("ifconfig", c.Device, c.Cidr.String(), c.Cidr.IP.String()).Run(); err != nil {
		return fmt.Errorf("failed to run 'ifconfig': %s", err)
	}
	if err = exec.Command("route", "-n", "add", "-net", c.Cidr.String(), "-interface", c.Device).Run(); err != nil {
		return fmt.Errorf("failed to run 'route add': %s", err)
	}
	if err = exec.Command("ifconfig", c.Device, "mtu", strconv.Itoa(c.MTU)).Run(); err != nil {
		return fmt.Errorf("failed to run 'ifconfig': %s", err)
	}
	// Unsafe path routes
	for _, r := range c.UnsafeRoutes {
		if err = exec.Command("route", "-n", "add", "-net", r.route.String(), "-interface", c.Device).Run(); err != nil {
			return fmt.Errorf("failed to run 'route add' for unsafe_route %s: %s", r.route.String(), err)
		}
	}

	return nil
}

func (c *Tun) Activate() error {
	var err error
	c.Interface, err = water.New(water.Config{
		DeviceType: water.TUN,
		PlatformSpecificParams: water.PlatformSpecificParams{
			ComponentID: "tap0901",
			Network:     c.Cidr.String(),
		},
	})
	if err != nil {
		return fmt.Errorf("Activate failed: %v", err)
	}

	c.Device = c.Interface.Name()

	// TODO use syscalls instead of exec.Command
	err = exec.Command(
		"netsh", "interface", "ipv4", "set", "address",
		fmt.Sprintf("name=%s", c.Device),
		"source=static",
		fmt.Sprintf("addr=%s", c.Cidr.IP),
		fmt.Sprintf("mask=%s", net.IP(c.Cidr.Mask)),
		"gateway=none",
	).Run()
	if err != nil {
		return fmt.Errorf("failed to run 'netsh' to set address: %s", err)
	}
	err = exec.Command(
		"netsh", "interface", "ipv4", "set", "interface",
		c.Device,
		fmt.Sprintf("mtu=%d", c.MTU),
	).Run()
	if err != nil {
		return fmt.Errorf("failed to run 'netsh' to set MTU: %s", err)
	}

	return nil
}

func (c *Tun) Activate() error {
	var err error
	c.Interface, err = water.New(water.Config{
		DeviceType: water.TUN,
	})
	if err != nil {
		return fmt.Errorf("Activate failed: %v", err)
	}

	c.Device = c.Interface.Name()

	// TODO use syscalls instead of exec.Command
	if err = exec.Command("/sbin/ifconfig", c.Device, c.Cidr.String(), c.Cidr.IP.String()).Run(); err != nil {
		return fmt.Errorf("failed to run 'ifconfig': %s", err)
	}
	if err = exec.Command("/sbin/route", "-n", "add", "-net", c.Cidr.String(), "-interface", c.Device).Run(); err != nil {
		return fmt.Errorf("failed to run 'route add': %s", err)
	}
	if err = exec.Command("/sbin/ifconfig", c.Device, "mtu", strconv.Itoa(c.MTU)).Run(); err != nil {
		return fmt.Errorf("failed to run 'ifconfig': %s", err)
	}
	// Unsafe path routes
	for _, r := range c.UnsafeRoutes {
		if err = exec.Command("/sbin/route", "-n", "add", "-net", r.route.String(), "-interface", c.Device).Run(); err != nil {
			return fmt.Errorf("failed to run 'route add' for unsafe_route %s: %s", r.route.String(), err)
		}
	}

	return nil
}

func (c *Tun) Activate() error {
	var err error
	c.Interface, err = water.New(water.Config{
		DeviceType: water.TUN,
		PlatformSpecificParams: water.PlatformSpecificParams{
			ComponentID: "tap0901",
			Network:     c.Cidr.String(),
		},
	})
	if err != nil {
		return fmt.Errorf("Activate failed: %v", err)
	}

	c.Device = c.Interface.Name()

	// TODO use syscalls instead of exec.Command
	err = exec.Command(
		`C:\Windows\System32\netsh.exe`, "interface", "ipv4", "set", "address",
		fmt.Sprintf("name=%s", c.Device),
		"source=static",
		fmt.Sprintf("addr=%s", c.Cidr.IP),
		fmt.Sprintf("mask=%s", net.IP(c.Cidr.Mask)),
		"gateway=none",
	).Run()
	if err != nil {
		return fmt.Errorf("failed to run 'netsh' to set address: %s", err)
	}
	err = exec.Command(
		`C:\Windows\System32\netsh.exe`, "interface", "ipv4", "set", "interface",
		c.Device,
		fmt.Sprintf("mtu=%d", c.MTU),
	).Run()
	if err != nil {
		return fmt.Errorf("failed to run 'netsh' to set MTU: %s", err)
	}

	return nil
}

--- a/tun_darwin.go
+++ b/tun_darwin.go
@@ -10,18 +10,18 @@
 	c.Device = c.Interface.Name()

 	// TODO use syscalls instead of exec.Command
-	if err = exec.Command("ifconfig", c.Device, c.Cidr.String(), c.Cidr.IP.String()).Run(); err != nil {
+	if err = exec.Command("/sbin/ifconfig", c.Device, c.Cidr.String(), c.Cidr.IP.String()).Run(); err != nil {
 		return fmt.Errorf("failed to run 'ifconfig': %s", err)
 	}
-	if err = exec.Command("route", "-n", "add", "-net", c.Cidr.String(), "-interface", c.Device).Run(); err != nil {
+	if err = exec.Command("/sbin/route", "-n", "add", "-net", c.Cidr.String(), "-interface", c.Device).Run(); err != nil {
 		return fmt.Errorf("failed to run 'route add': %s", err)
 	}
-	if err = exec.Command("ifconfig", c.Device, "mtu", strconv.Itoa(c.MTU)).Run(); err != nil {
+	if err = exec.Command("/sbin/ifconfig", c.Device, "mtu", strconv.Itoa(c.MTU)).Run(); err != nil {
 		return fmt.Errorf("failed to run 'ifconfig': %s", err)
 	}
 	// Unsafe path routes
 	for _, r := range c.UnsafeRoutes {
-		if err = exec.Command("route", "-n", "add", "-net", r.route.String(), "-interface", c.Device).Run(); err != nil {
+		if err = exec.Command("/sbin/route", "-n", "add", "-net", r.route.String(), "-interface", c.Device).Run(); err != nil {
 			return fmt.Errorf("failed to run 'route add' for unsafe_route %s: %s", r.route.String(), err)
 		}
 	}

--- a/tun_windows.go
+++ b/tun_windows.go
@@ -15,7 +15,7 @@

 	// TODO use syscalls instead of exec.Command
 	err = exec.Command(
-		"netsh", "interface", "ipv4", "set", "address",
+		`C:\Windows\System32\netsh.exe`, "interface", "ipv4", "set", "address",
 		fmt.Sprintf("name=%s", c.Device),
 		"source=static",
 		fmt.Sprintf("addr=%s", c.Cidr.IP),
@@ -26,7 +26,7 @@
 		return fmt.Errorf("failed to run 'netsh' to set address: %s", err)
 	}
 	err = exec.Command(
-		"netsh", "interface", "ipv4", "set", "interface",
+		`C:\Windows\System32\netsh.exe`, "interface", "ipv4", "set", "interface",
 		c.Device,
 		fmt.Sprintf("mtu=%d", c.MTU),
 	).Run()


Improper Input Validation in GitHub repository usememos/memos prior to 0.13.2.

Improper Input Validation

The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.

func JWTMiddleware(server *Server, next echo.HandlerFunc, secret string) echo.HandlerFunc {
	return func(c echo.Context) error {
		path := c.Request().URL.Path
		method := c.Request().Method

		if server.defaultAuthSkipper(c) {
			return next(c)
		}

		// Skip validation for server status endpoints.
		if common.HasPrefixes(path, "/api/ping", "/api/v1/idp", "/api/user/:id") && method == http.MethodGet {
			return next(c)
		}

		token := findAccessToken(c)
		if token == "" {
			// Allow the user to access the public endpoints.
			if common.HasPrefixes(path, "/o") {
				return next(c)
			}
			// When the request is not authenticated, we allow the user to access the memo endpoints for those public memos.
			if common.HasPrefixes(path, "/api/status", "/api/memo") && method == http.MethodGet {
				return next(c)
			}
			return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token")
		}

		claims := &Claims{}
		accessToken, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
			if t.Method.Alg() != jwt.SigningMethodHS256.Name {
				return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
			}
			if kid, ok := t.Header["kid"].(string); ok {
				if kid == "v1" {
					return []byte(secret), nil
				}
			}
			return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
		})
		if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) {
			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q.", claims.Audience, auth.AccessTokenAudienceName))
		}
		generateToken := time.Until(claims.ExpiresAt.Time) < auth.RefreshThresholdDuration
		if err != nil {
			var ve *jwt.ValidationError
			if errors.As(err, &ve) {
				// If expiration error is the only error, we will clear the err
				// and generate new access token and refresh token
				if ve.Errors == jwt.ValidationErrorExpired {
					generateToken = true
				}
			} else {
				return echo.NewHTTPError(http.StatusUnauthorized, errors.Wrap(err, "Invalid or expired access token"))
			}
		}

		// We either have a valid access token or we will attempt to generate new access token and refresh token
		ctx := c.Request().Context()
		userID, err := strconv.Atoi(claims.Subject)
		if err != nil {
			return echo.NewHTTPError(http.StatusUnauthorized, "Malformed ID in the token.")
		}

		// Even if there is no error, we still need to make sure the user still exists.
		user, err := server.Store.GetUser(ctx, &store.FindUserMessage{
			ID: &userID,
		})
		if err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to find user ID: %d", userID)).SetInternal(err)
		}
		if user == nil {
			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Failed to find user ID: %d", userID))
		}

		if generateToken {
			generateTokenFunc := func() error {
				rc, err := c.Cookie(auth.RefreshTokenCookieName)
				if err != nil {
					return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Missing refresh token.")
				}

				// Parses token and checks if it's valid.
				refreshTokenClaims := &Claims{}
				refreshToken, err := jwt.ParseWithClaims(rc.Value, refreshTokenClaims, func(t *jwt.Token) (any, error) {
					if t.Method.Alg() != jwt.SigningMethodHS256.Name {
						return nil, errors.Errorf("unexpected refresh token signing method=%v, expected %v", t.Header["alg"], jwt.SigningMethodHS256)
					}

					if kid, ok := t.Header["kid"].(string); ok {
						if kid == "v1" {
							return []byte(secret), nil
						}
					}
					return nil, errors.Errorf("unexpected refresh token kid=%v", t.Header["kid"])
				})
				if err != nil {
					if err == jwt.ErrSignatureInvalid {
						return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Invalid refresh token signature.")
					}
					return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
				}

				if !audienceContains(refreshTokenClaims.Audience, auth.RefreshTokenAudienceName) {
					return echo.NewHTTPError(http.StatusUnauthorized,
						fmt.Sprintf("Invalid refresh token, audience mismatch, got %q, expected %q. you may send request to the wrong environment",
							refreshTokenClaims.Audience,
							auth.RefreshTokenAudienceName,
						))
				}

				// If we have a valid refresh token, we will generate new access token and refresh token
				if refreshToken != nil && refreshToken.Valid {
					if err := auth.GenerateTokensAndSetCookies(c, user, secret); err != nil {
						return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
					}
				}

				return nil
			}

			// It may happen that we still have a valid access token, but we encounter issue when trying to generate new token
			// In such case, we won't return the error.
			if err := generateTokenFunc(); err != nil && !accessToken.Valid {
				return err
			}
		}

		// Stores userID into context.
		c.Set(getUserIDContextKey(), userID)
		return next(c)
	}
}

func JWTMiddleware(server *Server, next echo.HandlerFunc, secret string) echo.HandlerFunc {
	return func(c echo.Context) error {
		path := c.Request().URL.Path
		method := c.Request().Method

		if server.defaultAuthSkipper(c) {
			return next(c)
		}

		// Skip validation for server status endpoints.
		if common.HasPrefixes(path, "/api/ping", "/api/v1/idp", "/api/user/:id") && method == http.MethodGet {
			return next(c)
		}

		token := findAccessToken(c)
		if token == "" {
			// Allow the user to access the public endpoints.
			if common.HasPrefixes(path, "/o") {
				return next(c)
			}
			// When the request is not authenticated, we allow the user to access the memo endpoints for those public memos.
			if common.HasPrefixes(path, "/api/status", "/api/memo") && method == http.MethodGet {
				return next(c)
			}
			return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token")
		}

		claims := &Claims{}
		accessToken, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
			if t.Method.Alg() != jwt.SigningMethodHS256.Name {
				return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
			}
			if kid, ok := t.Header["kid"].(string); ok {
				if kid == "v1" {
					return []byte(secret), nil
				}
			}
			return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
		})

		if !accessToken.Valid {
			return echo.NewHTTPError(http.StatusUnauthorized, "Invalid access token.")
		}

		if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) {
			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q.", claims.Audience, auth.AccessTokenAudienceName))
		}
		generateToken := time.Until(claims.ExpiresAt.Time) < auth.RefreshThresholdDuration
		if err != nil {
			var ve *jwt.ValidationError
			if errors.As(err, &ve) {
				// If expiration error is the only error, we will clear the err
				// and generate new access token and refresh token
				if ve.Errors == jwt.ValidationErrorExpired {
					generateToken = true
				}
			} else {
				return echo.NewHTTPError(http.StatusUnauthorized, errors.Wrap(err, "Invalid or expired access token"))
			}
		}

		// We either have a valid access token or we will attempt to generate new access token and refresh token
		ctx := c.Request().Context()
		userID, err := strconv.Atoi(claims.Subject)
		if err != nil {
			return echo.NewHTTPError(http.StatusUnauthorized, "Malformed ID in the token.")
		}

		// Even if there is no error, we still need to make sure the user still exists.
		user, err := server.Store.GetUser(ctx, &store.FindUserMessage{
			ID: &userID,
		})
		if err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to find user ID: %d", userID)).SetInternal(err)
		}
		if user == nil {
			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Failed to find user ID: %d", userID))
		}

		if generateToken {
			generateTokenFunc := func() error {
				rc, err := c.Cookie(auth.RefreshTokenCookieName)
				if err != nil {
					return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Missing refresh token.")
				}

				// Parses token and checks if it's valid.
				refreshTokenClaims := &Claims{}
				refreshToken, err := jwt.ParseWithClaims(rc.Value, refreshTokenClaims, func(t *jwt.Token) (any, error) {
					if t.Method.Alg() != jwt.SigningMethodHS256.Name {
						return nil, errors.Errorf("unexpected refresh token signing method=%v, expected %v", t.Header["alg"], jwt.SigningMethodHS256)
					}

					if kid, ok := t.Header["kid"].(string); ok {
						if kid == "v1" {
							return []byte(secret), nil
						}
					}
					return nil, errors.Errorf("unexpected refresh token kid=%v", t.Header["kid"])
				})
				if err != nil {
					if err == jwt.ErrSignatureInvalid {
						return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Invalid refresh token signature.")
					}
					return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
				}

				if !audienceContains(refreshTokenClaims.Audience, auth.RefreshTokenAudienceName) {
					return echo.NewHTTPError(http.StatusUnauthorized,
						fmt.Sprintf("Invalid refresh token, audience mismatch, got %q, expected %q. you may send request to the wrong environment",
							refreshTokenClaims.Audience,
							auth.RefreshTokenAudienceName,
						))
				}

				// If we have a valid refresh token, we will generate new access token and refresh token
				if refreshToken != nil && refreshToken.Valid {
					if err := auth.GenerateTokensAndSetCookies(c, user, secret); err != nil {
						return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
					}
				}

				return nil
			}

			// It may happen that we still have a valid access token, but we encounter issue when trying to generate new token
			// In such case, we won't return the error.
			if err := generateTokenFunc(); err != nil && !accessToken.Valid {
				return err
			}
		}

		// Stores userID into context.
		c.Set(getUserIDContextKey(), userID)
		return next(c)
	}
}

--- a/server/jwt.go
+++ b/server/jwt.go
@@ -37,6 +37,11 @@
 			}
 			return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
 		})
+
+		if !accessToken.Valid {
+			return echo.NewHTTPError(http.StatusUnauthorized, "Invalid access token.")
+		}
+
 		if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) {
 			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q.", claims.Audience, auth.AccessTokenAudienceName))
 		}


privacyIDEA version 2.23.1 and earlier contains a Improper Input Validation vulnerability in token validation api that can result in Denial-of-Service. This attack appear to be exploitable via http request with user=<space>&pass= to /validate/check url. This vulnerability appears to have been fixed in 2.23.2.

    def __call__(self, func):
        @functools.wraps(func)
        def check_user_or_serial_in_request_wrapper(*args, **kwds):
            user = self.request.all_data.get("user")
            serial = self.request.all_data.get("serial")
            if not serial and not user:
                raise ParameterError(_("You need to specify a serial or a user."))
            f_result = func(*args, **kwds)
            return f_result

    def __call__(self, func):
        @functools.wraps(func)
        def check_user_or_serial_in_request_wrapper(*args, **kwds):
            user = self.request.all_data.get("user", "").strip()
            serial = self.request.all_data.get("serial", "").strip()
            if not serial and not user:
                raise ParameterError(_("You need to specify a serial or a user."))
            if "*" in serial:
                raise ParameterError(_("Invalid serial number."))
            if "%" in user:
                raise ParameterError(_("Invalid user."))

            f_result = func(*args, **kwds)
            return f_result

--- a/privacyidea/lib/decorators.py
+++ b/privacyidea/lib/decorators.py
@@ -1,9 +1,14 @@
     def __call__(self, func):
         @functools.wraps(func)
         def check_user_or_serial_in_request_wrapper(*args, **kwds):
-            user = self.request.all_data.get("user")
-            serial = self.request.all_data.get("serial")
+            user = self.request.all_data.get("user", "").strip()
+            serial = self.request.all_data.get("serial", "").strip()
             if not serial and not user:
                 raise ParameterError(_("You need to specify a serial or a user."))
+            if "*" in serial:
+                raise ParameterError(_("Invalid serial number."))
+            if "%" in user:
+                raise ParameterError(_("Invalid user."))
+
             f_result = func(*args, **kwds)
             return f_result


In the npm package semantic-release before version 17.2.3, secrets that would normally be masked by `semantic-release` can be accidentally disclosed if they contain characters that become encoded when included in a URL. Secrets that do not contain characters that become encoded when included in a URL are already masked properly. The issue is fixed in version 17.2.3.

Improper Encoding or Escaping of Output

The product prepares a structured message for communication with another component, but encoding or escaping of the data is either missing or done incorrectly. As a result, the intended structure of the message is not preserved.

module.exports = (env) => {
  const toReplace = Object.keys(env).filter((envVar) => {
    // https://github.com/semantic-release/semantic-release/issues/1558
    if (envVar === 'GOPRIVATE') {
      return false;
    }

    return /token|password|credential|secret|private/i.test(envVar) && size(env[envVar].trim()) >= SECRET_MIN_SIZE;
  });

  const regexp = new RegExp(toReplace.map((envVar) => escapeRegExp(env[envVar])).join('|'), 'g');
  return (output) =>
    output && isString(output) && toReplace.length > 0 ? output.toString().replace(regexp, SECRET_REPLACEMENT) : output;
};

module.exports = (env) => {
  const toReplace = Object.keys(env).filter((envVar) => {
    // https://github.com/semantic-release/semantic-release/issues/1558
    if (envVar === 'GOPRIVATE') {
      return false;
    }

    return /token|password|credential|secret|private/i.test(envVar) && size(env[envVar].trim()) >= SECRET_MIN_SIZE;
  });

  const regexp = new RegExp(
    toReplace
      .map((envVar) => `${escapeRegExp(env[envVar])}|${encodeURI(escapeRegExp(env[envVar]))}`)
      .join('|'),
    'g'
  );
  return (output) =>
    output && isString(output) && toReplace.length > 0 ? output.toString().replace(regexp, SECRET_REPLACEMENT) : output;
};

--- a/lib/hide-sensitive.js
+++ b/lib/hide-sensitive.js
@@ -8,7 +8,12 @@
     return /token|password|credential|secret|private/i.test(envVar) && size(env[envVar].trim()) >= SECRET_MIN_SIZE;
   });

-  const regexp = new RegExp(toReplace.map((envVar) => escapeRegExp(env[envVar])).join('|'), 'g');
+  const regexp = new RegExp(
+    toReplace
+      .map((envVar) => `${escapeRegExp(env[envVar])}|${encodeURI(escapeRegExp(env[envVar]))}`)
+      .join('|'),
+    'g'
+  );
   return (output) =>
     output && isString(output) && toReplace.length > 0 ? output.toString().replace(regexp, SECRET_REPLACEMENT) : output;
 };

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2020-26226:latest
# bash /workspace/fix-run.sh
set -e

cd /workspace/semantic-release
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
npx ava test/hide-sensitive.test.js:27

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2020-26226:latest
# bash /workspace/unit_test.sh
set -e

cd /workspace/semantic-release
git apply --whitespace=nowarn /workspace/fix.patch
npx ava test/hide-sensitive.test.js


Misskey is an open source, federated social media platform. Starting in version 12.109.0 and prior to version 2025.2.0-alpha.0, a login token named `token` is stored in a cookie for authentication purposes in Bull Dashboard, but this remains undeleted even after logout is performed. The primary affected users will be users who have logged into Misskey using a public PC or someone else's device, but it's possible that users who have logged out of Misskey before lending their PC to someone else could also be affected. Version 2025.2.0-alpha.0 contains a fix for this issue.

Insufficient Session Expiration

According to WASC, "Insufficient Session Expiration is when a web site permits an attacker to reuse old session credentials or session IDs for authorization."

export async function signout() {
	if (!$i) return;

	waiting();
	miLocalStorage.removeItem('account');
	await removeAccount($i.id);
	const accounts = await getAccounts();

	//#region Remove service worker registration
	try {
		if (navigator.serviceWorker.controller) {
			const registration = await navigator.serviceWorker.ready;
			const push = await registration.pushManager.getSubscription();
			if (push) {
				await window.fetch(`${apiUrl}/sw/unregister`, {
					method: 'POST',
					body: JSON.stringify({
						i: $i.token,
						endpoint: push.endpoint,
					}),
					headers: {
						'Content-Type': 'application/json',
					},
				});
			}
		}

		if (accounts.length === 0) {
			await navigator.serviceWorker.getRegistrations()
				.then(registrations => {
					return Promise.all(registrations.map(registration => registration.unregister()));
				});
		}
	} catch (err) {}
	//#endregion

	if (accounts.length > 0) login(accounts[0].token);
	else unisonReload('/');
}

export async function signout() {
	if (!$i) return;

	waiting();
	document.cookie.split(';').forEach((cookie) => {
		const cookieName = cookie.split('=')[0].trim();
		if (cookieName === 'token') {
			document.cookie = `${cookieName}=; max-age=0; path=/`;
		}
	});
	miLocalStorage.removeItem('account');
	await removeAccount($i.id);
	const accounts = await getAccounts();

	//#region Remove service worker registration
	try {
		if (navigator.serviceWorker.controller) {
			const registration = await navigator.serviceWorker.ready;
			const push = await registration.pushManager.getSubscription();
			if (push) {
				await window.fetch(`${apiUrl}/sw/unregister`, {
					method: 'POST',
					body: JSON.stringify({
						i: $i.token,
						endpoint: push.endpoint,
					}),
					headers: {
						'Content-Type': 'application/json',
					},
				});
			}
		}

		if (accounts.length === 0) {
			await navigator.serviceWorker.getRegistrations()
				.then(registrations => {
					return Promise.all(registrations.map(registration => registration.unregister()));
				});
		}
	} catch (err) {}
	//#endregion

	if (accounts.length > 0) login(accounts[0].token);
	else unisonReload('/');
}

--- a/packages/frontend/src/account.ts
+++ b/packages/frontend/src/account.ts
@@ -2,6 +2,12 @@
 	if (!$i) return;

 	waiting();
+	document.cookie.split(';').forEach((cookie) => {
+		const cookieName = cookie.split('=')[0].trim();
+		if (cookieName === 'token') {
+			document.cookie = `${cookieName}=; max-age=0; path=/`;
+		}
+	});
 	miLocalStorage.removeItem('account');
 	await removeAccount($i.id);
 	const accounts = await getAccounts();


Syncthing is a continuous file synchronization program. In Syncthing before version 1.15.0, the relay server `strelaysrv` can be caused to crash and exit by sending a relay message with a negative length field. Similarly, Syncthing itself can crash for the same reason if given a malformed message from a malicious relay server when attempting to join the relay. Relay joins are essentially random (from a subset of low latency relays) and Syncthing will by default restart when crashing, at which point it's likely to pick another non-malicious relay. This flaw is fixed in version 1.15.0.

func ReadMessage(r io.Reader) (interface{}, error) {
	var header header

	buf := make([]byte, header.XDRSize())
	if _, err := io.ReadFull(r, buf); err != nil {
		return nil, err
	}

	if err := header.UnmarshalXDR(buf); err != nil {
		return nil, err
	}

	if header.magic != magic {
		return nil, errors.New("magic mismatch")
	}

	buf = make([]byte, int(header.messageLength))
	if _, err := io.ReadFull(r, buf); err != nil {
		return nil, err
	}

	switch header.messageType {
	case messageTypePing:
		var msg Ping
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypePong:
		var msg Pong
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypeJoinRelayRequest:
		var msg JoinRelayRequest
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypeJoinSessionRequest:
		var msg JoinSessionRequest
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypeResponse:
		var msg Response
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypeConnectRequest:
		var msg ConnectRequest
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypeSessionInvitation:
		var msg SessionInvitation
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypeRelayFull:
		var msg RelayFull
		err := msg.UnmarshalXDR(buf)
		return msg, err
	}

	return nil, errors.New("unknown message type")
}

func ReadMessage(r io.Reader) (interface{}, error) {
	var header header

	buf := make([]byte, header.XDRSize())
	if _, err := io.ReadFull(r, buf); err != nil {
		return nil, err
	}

	if err := header.UnmarshalXDR(buf); err != nil {
		return nil, err
	}

	if header.magic != magic {
		return nil, errors.New("magic mismatch")
	}
	if header.messageLength < 0 || header.messageLength > 1024 {
		return nil, fmt.Errorf("bad length (%d)", header.messageLength)
	}

	buf = make([]byte, int(header.messageLength))
	if _, err := io.ReadFull(r, buf); err != nil {
		return nil, err
	}

	switch header.messageType {
	case messageTypePing:
		var msg Ping
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypePong:
		var msg Pong
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypeJoinRelayRequest:
		var msg JoinRelayRequest
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypeJoinSessionRequest:
		var msg JoinSessionRequest
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypeResponse:
		var msg Response
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypeConnectRequest:
		var msg ConnectRequest
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypeSessionInvitation:
		var msg SessionInvitation
		err := msg.UnmarshalXDR(buf)
		return msg, err
	case messageTypeRelayFull:
		var msg RelayFull
		err := msg.UnmarshalXDR(buf)
		return msg, err
	}

	return nil, errors.New("unknown message type")
}

--- a/lib/relay/protocol/protocol.go
+++ b/lib/relay/protocol/protocol.go
@@ -12,6 +12,9 @@

 	if header.magic != magic {
 		return nil, errors.New("magic mismatch")
+	}
+	if header.messageLength < 0 || header.messageLength > 1024 {
+		return nil, fmt.Errorf("bad length (%d)", header.messageLength)
 	}

 	buf = make([]byte, int(header.messageLength))


The package git-pull-or-clone before 2.0.2 are vulnerable to Command Injection due to the use of the --upload-pack feature of git which is also supported for git clone. The source includes the use of the secure child process API spawn(). However, the outpath parameter passed to it may be a command-line argument to the git clone command and result in arbitrary command injection.

Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')

The product constructs a string for a command to be executed by a separate component
in another control sphere, but it does not properly delimit the
intended arguments, options, or switches within that command string.

  function gitClone () {
    // --depth implies --single-branch
    const flag = depth < Infinity ? '--depth=' + depth : '--single-branch'
    const args = ['clone', flag, url, outPath]
    debug('git ' + args.join(' '))
    spawn('git', args, {}, function (err) {
      if (err) err.message += ' (git clone) (' + url + ')'
      cb(err)
    })
  }

  function gitClone () {
    // --depth implies --single-branch
    const flag = depth < Infinity ? '--depth=' + depth : '--single-branch'
    const args = ['clone', flag, '--', url, outPath]
    debug('git ' + args.join(' '))
    spawn('git', args, {}, function (err) {
      if (err) err.message += ' (git clone) (' + url + ')'
      cb(err)
    })
  }

--- a/index.js
+++ b/index.js
@@ -1,7 +1,7 @@
   function gitClone () {
     // --depth implies --single-branch
     const flag = depth < Infinity ? '--depth=' + depth : '--single-branch'
-    const args = ['clone', flag, url, outPath]
+    const args = ['clone', flag, '--', url, outPath]
     debug('git ' + args.join(' '))
     spawn('git', args, {}, function (err) {
       if (err) err.message += ' (git clone) (' + url + ')'


1Panel is an open source Linux server operation and maintenance management panel. Prior to  v1.10.3-lts, there are many command injections in the project, and some of them are not well filtered, leading to arbitrary file writes, and ultimately leading to RCEs. The mirror configuration write symbol `>` can be used to achieve arbitrary file writing. This vulnerability is fixed in  v1.10.3-lts.

func CheckIllegal(args ...string) bool {
	if args == nil {
		return false
	}
	for _, arg := range args {
		if strings.Contains(arg, "&") || strings.Contains(arg, "|") || strings.Contains(arg, ";") ||
			strings.Contains(arg, "$") || strings.Contains(arg, "'") || strings.Contains(arg, "`") ||
			strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") ||
			strings.Contains(arg, "\n") || strings.Contains(arg, "\r") {
			return true
		}
	}
	return false
}

func CheckIllegal(args ...string) bool {
	if args == nil {
		return false
	}
	for _, arg := range args {
		if strings.Contains(arg, "&") || strings.Contains(arg, "|") || strings.Contains(arg, ";") ||
			strings.Contains(arg, "$") || strings.Contains(arg, "'") || strings.Contains(arg, "`") ||
			strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") ||
			strings.Contains(arg, "\n") || strings.Contains(arg, "\r") || strings.Contains(arg, ">") {
			return true
		}
	}
	return false
}

--- a/backend/utils/cmd/cmd.go
+++ b/backend/utils/cmd/cmd.go
@@ -6,7 +6,7 @@
 		if strings.Contains(arg, "&") || strings.Contains(arg, "|") || strings.Contains(arg, ";") ||
 			strings.Contains(arg, "$") || strings.Contains(arg, "'") || strings.Contains(arg, "`") ||
 			strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") ||
-			strings.Contains(arg, "\n") || strings.Contains(arg, "\r") {
+			strings.Contains(arg, "\n") || strings.Contains(arg, "\r") || strings.Contains(arg, ">") {
 			return true
 		}
 	}


flask-oidc version 0.1.2 and earlier is vulnerable to an open redirect

URL Redirection to Untrusted Site ('Open Redirect')

The web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a redirect.

    def init_app(self, app):
        """
        Do setup that requires a Flask app.
        """
        self.app = app

        # Set some default configuration options
        app.config.setdefault('OIDC_SCOPES', ['openid', 'email'])
        app.config.setdefault('OIDC_GOOGLE_APPS_DOMAIN', None)
        app.config.setdefault('OIDC_ID_TOKEN_COOKIE_NAME', 'oidc_id_token')
        app.config.setdefault('OIDC_ID_TOKEN_COOKIE_TTL', 7 * 86400)  # 7 days
        # should ONLY be turned off for local debugging
        app.config.setdefault('OIDC_ID_TOKEN_COOKIE_SECURE', True)
        app.config.setdefault('OIDC_VALID_ISSUERS',
                              ['accounts.google.com',
                               'https://accounts.google.com'])
        app.config.setdefault('OIDC_CLOCK_SKEW', 60)  # 1 minute
        app.config.setdefault('OIDC_REQUIRE_VERIFIED_EMAIL', True)

        # register callback route and cookie-setting decorator
        app.route('/oidc_callback')(self.oidc_callback)
        app.before_request(self.before_request)
        app.after_request(self.after_request)

        # load client_secrets.json
        self.flow = flow_from_clientsecrets(
            app.config['OIDC_CLIENT_SECRETS'],
            scope=app.config['OIDC_SCOPES'])
        assert isinstance(self.flow, OAuth2WebServerFlow)

        # create a cookie signer using the Flask secret key
        self.cookie_serializer = TimedJSONWebSignatureSerializer(
            app.config['SECRET_KEY'])

        try:
            self.credentials_store = app.config['OIDC_CREDENTIALS_STORE']
        except KeyError:
            pass

    def redirect_to_auth_server(self, destination):
        """
        Set a CSRF token in the session, and redirect to the IdP.
        :param destination: the page that the user was going to,
                            before we noticed they weren't logged in
        :return: a redirect response
        """
        csrf_token = b64encode(self.urandom(24)).decode('utf-8')
        session['oidc_csrf_token'] = csrf_token
        state = {
            'csrf_token': csrf_token,
            'destination': destination,
        }
        extra_params = {
            'state': json.dumps(state),
        }
        flow = self.flow_for_request()
        auth_url = '{url}&{extra_params}'.format(
            url=flow.step1_get_authorize_url(),
            extra_params=urlencode(extra_params))
        # if the user has an ID token, it's invalid, or we wouldn't be here
        self.set_cookie_id_token(None)
        return redirect(auth_url)

    def oidc_callback(self):
        """
        Exchange the auth code for actual credentials,
        then redirect to the originally requested page.
        """
        # retrieve session and callback variables
        try:
            session_csrf_token = session.pop('oidc_csrf_token')

            state = json.loads(request.args['state'])
            csrf_token = state['csrf_token']
            destination = state['destination']

            code = request.args['code']
        except (KeyError, ValueError):
            logger.debug("Can't retrieve CSRF token, state, or code",
                         exc_info=True)
            return self.oidc_error()

        # check callback CSRF token passed to IdP
        # against session CSRF token held by user
        if csrf_token != session_csrf_token:
            logger.debug("CSRF token mismatch")
            return self.oidc_error()

        # make a request to IdP to exchange the auth code for OAuth credentials
        flow = self.flow_for_request()
        credentials = flow.step2_exchange(code, http=self.http)
        id_token = credentials.id_token
        if not self.is_id_token_valid(id_token):
            logger.debug("Invalid ID token")
            if id_token.get('hd') != self.app.config['OIDC_GOOGLE_APPS_DOMAIN']:
                return self.oidc_error(
                    "You must log in with an account from the {0} domain."
                    .format(self.app.config['OIDC_GOOGLE_APPS_DOMAIN']),
                    self.WRONG_GOOGLE_APPS_DOMAIN)
            return self.oidc_error()

        # store credentials by subject
        # when Google is the IdP, the subject is their G+ account number
        self.credentials_store[id_token['sub']] = credentials

        # set a persistent signed cookie containing the ID token
        # and redirect to the final destination
        # TODO: validate redirect destination
        response = redirect(destination)
        self.set_cookie_id_token(id_token)
        return response

    def init_app(self, app):
        """
        Do setup that requires a Flask app.
        """
        self.app = app

        # Set some default configuration options
        app.config.setdefault('OIDC_SCOPES', ['openid', 'email'])
        app.config.setdefault('OIDC_GOOGLE_APPS_DOMAIN', None)
        app.config.setdefault('OIDC_ID_TOKEN_COOKIE_NAME', 'oidc_id_token')
        app.config.setdefault('OIDC_ID_TOKEN_COOKIE_TTL', 7 * 86400)  # 7 days
        # should ONLY be turned off for local debugging
        app.config.setdefault('OIDC_ID_TOKEN_COOKIE_SECURE', True)
        app.config.setdefault('OIDC_VALID_ISSUERS',
                              ['accounts.google.com',
                               'https://accounts.google.com'])
        app.config.setdefault('OIDC_CLOCK_SKEW', 60)  # 1 minute
        app.config.setdefault('OIDC_REQUIRE_VERIFIED_EMAIL', True)

        # register callback route and cookie-setting decorator
        app.route('/oidc_callback')(self.oidc_callback)
        app.before_request(self.before_request)
        app.after_request(self.after_request)

        # load client_secrets.json
        self.flow = flow_from_clientsecrets(
            app.config['OIDC_CLIENT_SECRETS'],
            scope=app.config['OIDC_SCOPES'])
        assert isinstance(self.flow, OAuth2WebServerFlow)

        # create signers using the Flask secret key
        self.destination_serializer = JSONWebSignatureSerializer(
            app.config['SECRET_KEY'])
        self.cookie_serializer = TimedJSONWebSignatureSerializer(
            app.config['SECRET_KEY'])

        try:
            self.credentials_store = app.config['OIDC_CREDENTIALS_STORE']
        except KeyError:
            pass

    def redirect_to_auth_server(self, destination):
        """
        Set a CSRF token in the session, and redirect to the IdP.
        :param destination: the page that the user was going to,
                            before we noticed they weren't logged in
        :return: a redirect response
        """
        destination = self.destination_serializer.dumps(destination)
        csrf_token = b64encode(self.urandom(24)).decode('utf-8')
        session['oidc_csrf_token'] = csrf_token
        state = {
            'csrf_token': csrf_token,
            'destination': destination,
        }
        extra_params = {
            'state': json.dumps(state),
        }
        flow = self.flow_for_request()
        auth_url = '{url}&{extra_params}'.format(
            url=flow.step1_get_authorize_url(),
            extra_params=urlencode(extra_params))
        # if the user has an ID token, it's invalid, or we wouldn't be here
        self.set_cookie_id_token(None)
        return redirect(auth_url)

    def oidc_callback(self):
        """
        Exchange the auth code for actual credentials,
        then redirect to the originally requested page.
        """
        # retrieve session and callback variables
        try:
            session_csrf_token = session.pop('oidc_csrf_token')

            state = json.loads(request.args['state'])
            csrf_token = state['csrf_token']
            destination = state['destination']

            code = request.args['code']
        except (KeyError, ValueError):
            logger.debug("Can't retrieve CSRF token, state, or code",
                         exc_info=True)
            return self.oidc_error()

        # check callback CSRF token passed to IdP
        # against session CSRF token held by user
        if csrf_token != session_csrf_token:
            logger.debug("CSRF token mismatch")
            return self.oidc_error()

        # make a request to IdP to exchange the auth code for OAuth credentials
        flow = self.flow_for_request()
        credentials = flow.step2_exchange(code, http=self.http)
        id_token = credentials.id_token
        if not self.is_id_token_valid(id_token):
            logger.debug("Invalid ID token")
            if id_token.get('hd') != self.app.config['OIDC_GOOGLE_APPS_DOMAIN']:
                return self.oidc_error(
                    "You must log in with an account from the {0} domain."
                    .format(self.app.config['OIDC_GOOGLE_APPS_DOMAIN']),
                    self.WRONG_GOOGLE_APPS_DOMAIN)
            return self.oidc_error()

        # store credentials by subject
        # when Google is the IdP, the subject is their G+ account number
        self.credentials_store[id_token['sub']] = credentials

        # Check whether somebody messed with the destination
        destination = destination
        try:
            response = redirect(self.destination_serializer.loads(destination))
        except BadSignature:
            logger.error('Destination signature did not match. Rogue IdP?')
            response = redirect('/')

        # set a persistent signed cookie containing the ID token
        # and redirect to the final destination
        self.set_cookie_id_token(id_token)
        return response

--- a/flask_oidc/__init__.py
+++ b/flask_oidc/__init__.py
@@ -28,7 +28,9 @@
             scope=app.config['OIDC_SCOPES'])
         assert isinstance(self.flow, OAuth2WebServerFlow)

-        # create a cookie signer using the Flask secret key
+        # create signers using the Flask secret key
+        self.destination_serializer = JSONWebSignatureSerializer(
+            app.config['SECRET_KEY'])
         self.cookie_serializer = TimedJSONWebSignatureSerializer(
             app.config['SECRET_KEY'])

--- a/flask_oidc/__init__.py
+++ b/flask_oidc/__init__.py
@@ -5,6 +5,7 @@
                             before we noticed they weren't logged in
         :return: a redirect response
         """
+        destination = self.destination_serializer.dumps(destination)
         csrf_token = b64encode(self.urandom(24)).decode('utf-8')
         session['oidc_csrf_token'] = csrf_token
         state = {

--- a/flask_oidc/__init__.py
+++ b/flask_oidc/__init__.py
@@ -40,9 +40,15 @@
         # when Google is the IdP, the subject is their G+ account number
         self.credentials_store[id_token['sub']] = credentials

+        # Check whether somebody messed with the destination
+        destination = destination
+        try:
+            response = redirect(self.destination_serializer.loads(destination))
+        except BadSignature:
+            logger.error('Destination signature did not match. Rogue IdP?')
+            response = redirect('/')
+
         # set a persistent signed cookie containing the ID token
         # and redirect to the final destination
-        # TODO: validate redirect destination
-        response = redirect(destination)
         self.set_cookie_id_token(id_token)
         return response


ChuanhuChatGPT is a graphical user interface for ChatGPT and many large language models. A vulnerability in versions 20230526 and prior allows unauthorized access to the config.json file of the privately deployed ChuanghuChatGPT project, when authentication is not configured. The attacker can exploit this vulnerability to steal the API keys in the configuration file. The vulnerability has been fixed in commit bfac445. As a workaround, setting up access authentication can help mitigate the vulnerability.

Missing Authentication for Critical Function

The product does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources.

if __name__ == "__main__":
    reload_javascript()
    demo.queue(concurrency_count=CONCURRENT_COUNT).launch(
        server_name=server_name,
        server_port=server_port,
        share=share,
        auth=auth_list if authflag else None,
        favicon_path="./assets/favicon.ico",
        inbrowser=not dockerflag, # \u7981\u6b62\u5728docker\u4e0b\u5f00\u542finbrowser
    )

if __name__ == "__main__":
    reload_javascript()
    demo.queue(concurrency_count=CONCURRENT_COUNT).launch(
        blocked_paths=["config.json"],
        server_name=server_name,
        server_port=server_port,
        share=share,
        auth=auth_list if authflag else None,
        favicon_path="./assets/favicon.ico",
        inbrowser=not dockerflag, # \u7981\u6b62\u5728docker\u4e0b\u5f00\u542finbrowser
    )

--- a/ChuanhuChatbot.py
+++ b/ChuanhuChatbot.py
@@ -1,6 +1,7 @@
 if __name__ == "__main__":
     reload_javascript()
     demo.queue(concurrency_count=CONCURRENT_COUNT).launch(
+        blocked_paths=["config.json"],
         server_name=server_name,
         server_port=server_port,
         share=share,


Ansible before version 2.2.0 fails to properly sanitize fact variables sent from the Ansible controller. An attacker with the ability to create special variables on the controller could execute arbitrary commands on Ansible clients as the user Ansible runs as.

    def all(self, *args, **kwargs):
        ''' instantiates all plugins with the same arguments '''

        class_only = kwargs.pop('class_only', False)
        all_matches = []
        found_in_cache = True

        for i in self._get_paths():
            all_matches.extend(glob.glob(os.path.join(i, "*.py")))

        for path in sorted(all_matches, key=lambda match: os.path.basename(match)):
            name, _ = os.path.splitext(path)
            if '__init__' in name:
                continue

            if path not in self._module_cache:
                self._module_cache[path] = self._load_module_source(name, path)
                found_in_cache = False

            try:
                obj = getattr(self._module_cache[path], self.class_name)
            except AttributeError as e:
                display.warning("Skipping plugin (%s) as it seems to be invalid: %s" % (path, to_text(e)))
                continue

            if self.base_class:
                # The import path is hardcoded and should be the right place,
                # so we are not expecting an ImportError.
                module = __import__(self.package, fromlist=[self.base_class])
                # Check whether this obj has the required base class.
                try:
                    plugin_class = getattr(module, self.base_class)
                except AttributeError:
                    continue
                if not issubclass(obj, plugin_class):
                    continue

            self._display_plugin_load(self.class_name, name, self._searched_paths, path,
                                      found_in_cache=found_in_cache, class_only=class_only)
            if not class_only:
                obj = obj(*args, **kwargs)

            # set extra info on the module, in case we want it later
            setattr(obj, '_original_path', path)
            yield obj

    def all(self, *args, **kwargs):
        ''' instantiates all plugins with the same arguments '''

        path_only = kwargs.pop('path_only', False)
        class_only = kwargs.pop('class_only', False)
        all_matches = []
        found_in_cache = True

        for i in self._get_paths():
            all_matches.extend(glob.glob(os.path.join(i, "*.py")))

        for path in sorted(all_matches, key=lambda match: os.path.basename(match)):
            name, _ = os.path.splitext(path)
            if '__init__' in name:
                continue

            if path_only:
                yield path
                continue

            if path not in self._module_cache:
                self._module_cache[path] = self._load_module_source(name, path)
                found_in_cache = False

            try:
                obj = getattr(self._module_cache[path], self.class_name)
            except AttributeError as e:
                display.warning("Skipping plugin (%s) as it seems to be invalid: %s" % (path, to_text(e)))
                continue

            if self.base_class:
                # The import path is hardcoded and should be the right place,
                # so we are not expecting an ImportError.
                module = __import__(self.package, fromlist=[self.base_class])
                # Check whether this obj has the required base class.
                try:
                    plugin_class = getattr(module, self.base_class)
                except AttributeError:
                    continue
                if not issubclass(obj, plugin_class):
                    continue

            self._display_plugin_load(self.class_name, name, self._searched_paths, path,
                                      found_in_cache=found_in_cache, class_only=class_only)
            if not class_only:
                obj = obj(*args, **kwargs)

            # set extra info on the module, in case we want it later
            setattr(obj, '_original_path', path)
            yield obj

--- a/lib/ansible/plugins/__init__.py
+++ b/lib/ansible/plugins/__init__.py
@@ -1,6 +1,7 @@
     def all(self, *args, **kwargs):
         ''' instantiates all plugins with the same arguments '''

+        path_only = kwargs.pop('path_only', False)
         class_only = kwargs.pop('class_only', False)
         all_matches = []
         found_in_cache = True
@@ -11,6 +12,10 @@
         for path in sorted(all_matches, key=lambda match: os.path.basename(match)):
             name, _ = os.path.splitext(path)
             if '__init__' in name:
+                continue
+
+            if path_only:
+                yield path
                 continue

             if path not in self._module_cache:


SFTPGo is an open source, event-driven file transfer solution. SFTPGo supports execution of a defined set of commands via SSH. Besides a set of default commands some optional commands can be activated, one of them being `rsync`. It is disabled in the default configuration and it is limited to the local filesystem, it does not work with cloud/remote storage backends. Due to missing sanitization of the client provided `rsync` command, an authenticated remote user can use some options of the rsync command to read or write files with the permissions of the SFTPGo server process. This issue was fixed in version v2.6.5 by checking the client provided arguments. Users are advised to upgrade. There are no known workarounds for this vulnerability.

func (c *sshCommand) getSystemCommand() (systemCommand, error) {
	command := systemCommand{
		cmd:            nil,
		fs:             nil,
		fsPath:         "",
		quotaCheckPath: "",
	}
	if err := common.CheckClosing(); err != nil {
		return command, err
	}
	args := make([]string, len(c.args))
	copy(args, c.args)
	var fsPath, quotaPath string
	sshPath := c.getDestPath()
	fs, err := c.connection.User.GetFilesystemForPath(sshPath, c.connection.ID)
	if err != nil {
		return command, err
	}
	if len(c.args) > 0 {
		var err error
		fsPath, err = fs.ResolvePath(sshPath)
		if err != nil {
			return command, c.connection.GetFsError(fs, err)
		}
		quotaPath = sshPath
		fi, err := fs.Stat(fsPath)
		if err == nil && fi.IsDir() {
			// if the target is an existing dir the command will write inside this dir
			// so we need to check the quota for this directory and not its parent dir
			quotaPath = path.Join(sshPath, "fakecontent")
		}
		if strings.HasSuffix(sshPath, "/") && !strings.HasSuffix(fsPath, string(os.PathSeparator)) {
			fsPath += string(os.PathSeparator)
			c.connection.Log(logger.LevelDebug, "path separator added to fsPath %q", fsPath)
		}
		args = args[:len(args)-1]
		args = append(args, fsPath)
	}
	if err := c.isSystemCommandAllowed(); err != nil {
		return command, errUnsupportedConfig
	}
	if c.command == "rsync" {
		// we cannot avoid that rsync creates symlinks so if the user has the permission
		// to create symlinks we add the option --safe-links to the received rsync command if
		// it is not already set. This should prevent to create symlinks that point outside
		// the home dir.
		// If the user cannot create symlinks we add the option --munge-links, if it is not
		// already set. This should make symlinks unusable (but manually recoverable)
		if c.connection.User.HasPerm(dataprovider.PermCreateSymlinks, c.getDestPath()) {
			if !util.Contains(args, "--safe-links") {
				args = append([]string{"--safe-links"}, args...)
			}
		} else {
			if !util.Contains(args, "--munge-links") {
				args = append([]string{"--munge-links"}, args...)
			}
		}
	}
	c.connection.Log(logger.LevelDebug, "new system command %q, with args: %+v fs path %q quota check path %q",
		c.command, args, fsPath, quotaPath)
	cmd := exec.Command(c.command, args...)
	uid := c.connection.User.GetUID()
	gid := c.connection.User.GetGID()
	cmd = wrapCmd(cmd, uid, gid)
	command.cmd = cmd
	command.fsPath = fsPath
	command.quotaCheckPath = quotaPath
	command.fs = fs
	return command, nil
}

func (c *sshCommand) getSystemCommand() (systemCommand, error) {
	command := systemCommand{
		cmd:            nil,
		fs:             nil,
		fsPath:         "",
		quotaCheckPath: "",
	}
	if err := common.CheckClosing(); err != nil {
		return command, err
	}
	args := make([]string, len(c.args))
	copy(args, c.args)
	var fsPath, quotaPath string
	sshPath := c.getDestPath()
	fs, err := c.connection.User.GetFilesystemForPath(sshPath, c.connection.ID)
	if err != nil {
		return command, err
	}
	if len(c.args) > 0 {
		var err error
		fsPath, err = fs.ResolvePath(sshPath)
		if err != nil {
			return command, c.connection.GetFsError(fs, err)
		}
		quotaPath = sshPath
		fi, err := fs.Stat(fsPath)
		if err == nil && fi.IsDir() {
			// if the target is an existing dir the command will write inside this dir
			// so we need to check the quota for this directory and not its parent dir
			quotaPath = path.Join(sshPath, "fakecontent")
		}
		if strings.HasSuffix(sshPath, "/") && !strings.HasSuffix(fsPath, string(os.PathSeparator)) {
			fsPath += string(os.PathSeparator)
			c.connection.Log(logger.LevelDebug, "path separator added to fsPath %q", fsPath)
		}
		args = args[:len(args)-1]
		args = append(args, fsPath)
	}
	if err := c.isSystemCommandAllowed(); err != nil {
		return command, errUnsupportedConfig
	}
	if c.command == "rsync" {
		if !canAcceptRsyncArgs(args) {
			c.connection.Log(logger.LevelWarn, "invalid rsync command, args: %+v", args)
			return command, errors.New("invalid or unsupported rsync command")
		}
		// we cannot avoid that rsync creates symlinks so if the user has the permission
		// to create symlinks we add the option --safe-links to the received rsync command if
		// it is not already set. This should prevent to create symlinks that point outside
		// the home dir.
		// If the user cannot create symlinks we add the option --munge-links, if it is not
		// already set. This should make symlinks unusable (but manually recoverable)
		if c.connection.User.HasPerm(dataprovider.PermCreateSymlinks, c.getDestPath()) {
			if !util.Contains(args, "--safe-links") {
				args = slices.Insert(args, len(args)-2, "--safe-links")
			}
		} else {
			if !util.Contains(args, "--munge-links") {
				args = slices.Insert(args, len(args)-2, "--munge-links")
			}
		}
	}
	c.connection.Log(logger.LevelDebug, "new system command %q, with args: %+v fs path %q quota check path %q",
		c.command, args, fsPath, quotaPath)
	cmd := exec.Command(c.command, args...)
	uid := c.connection.User.GetUID()
	gid := c.connection.User.GetGID()
	cmd = wrapCmd(cmd, uid, gid)
	command.cmd = cmd
	command.fsPath = fsPath
	command.quotaCheckPath = quotaPath
	command.fs = fs
	return command, nil
}

--- a/internal/sftpd/ssh_cmd.go
+++ b/internal/sftpd/ssh_cmd.go
@@ -40,6 +40,10 @@
 		return command, errUnsupportedConfig
 	}
 	if c.command == "rsync" {
+		if !canAcceptRsyncArgs(args) {
+			c.connection.Log(logger.LevelWarn, "invalid rsync command, args: %+v", args)
+			return command, errors.New("invalid or unsupported rsync command")
+		}
 		// we cannot avoid that rsync creates symlinks so if the user has the permission
 		// to create symlinks we add the option --safe-links to the received rsync command if
 		// it is not already set. This should prevent to create symlinks that point outside
@@ -48,11 +52,11 @@
 		// already set. This should make symlinks unusable (but manually recoverable)
 		if c.connection.User.HasPerm(dataprovider.PermCreateSymlinks, c.getDestPath()) {
 			if !util.Contains(args, "--safe-links") {
-				args = append([]string{"--safe-links"}, args...)
+				args = slices.Insert(args, len(args)-2, "--safe-links")
 			}
 		} else {
 			if !util.Contains(args, "--munge-links") {
-				args = append([]string{"--munge-links"}, args...)
+				args = slices.Insert(args, len(args)-2, "--munge-links")
 			}
 		}
 	}

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2025-24366:latest
# bash /workspace/fix-run.sh
set -e

cd /workspace/sftpgo
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
cd ./internal/sftpd && go test  -timeout 30s -run ^TestRsyncOptions$ github.com/drakkan/sftpgo/v2/internal/sftpd


RestrictedPython is a tool that helps to define a subset of the Python language which allows to provide a program input into a trusted environment. Via a type confusion bug in versions of the CPython interpreter starting in 3.11 and prior to 3.13.2 when using `try/except*`, RestrictedPython starting in version 6.0 and prior to version 8.0 could be bypassed. The issue is patched in version 8.0 of RestrictedPython by removing support for `try/except*` clauses. No known workarounds are available.

Access of Resource Using Incompatible Type ('Type Confusion')

The product allocates or initializes a resource such as a pointer, object, or variable using one type, but it later accesses that resource using a type that is incompatible with the original type.

    def visit_TryStar(self, node):
        """Allow `ExceptionGroup` without restrictions."""
        return self.node_contents_visit(node)

if IS_PY311_OR_GREATER:
    _safe_exceptions.append("ExceptionGroup")

    def visit_TryStar(self, node):
        """Disallow `ExceptionGroup` due to a potential sandbox escape."""
        self.not_allowed(node)

--- a/src/RestrictedPython/transformer.py
+++ b/src/RestrictedPython/transformer.py
@@ -1,3 +1,3 @@
     def visit_TryStar(self, node):
-        """Allow `ExceptionGroup` without restrictions."""
-        return self.node_contents_visit(node)
+        """Disallow `ExceptionGroup` due to a potential sandbox escape."""
+        self.not_allowed(node)

--- a/src/RestrictedPython/Guards.py
+++ /dev/null
@@ -1,2 +0,0 @@
-if IS_PY311_OR_GREATER:
-    _safe_exceptions.append("ExceptionGroup")


dns-sync is a sync/blocking dns resolver. If untrusted user input is allowed into the resolve() method then command injection is possible.

    resolve: function resolve(hostname) {
        var output,
            nodeBinary = process.execPath,
            scriptPath = path.join(__dirname, "../scripts/dns-lookup-script"),
            response,
            cmd = util.format('"%s" "%s" %s', nodeBinary, scriptPath, hostname);

        response = shell.exec(cmd, {silent: true});
        if (response && response.code === 0) {
            output = response.output;
            if (output && net.isIP(output)) {
                return output;
            }
        }
        debug('hostname', "fail to resolve hostname " + hostname);
        return null;
    }

    resolve: function resolve(hostname) {
        var output,
            nodeBinary = process.execPath;

        if (!isValidHostName(hostname)) {
            console.error('Invalid hostname:', hostname);
            return null;
        }

        var scriptPath = path.join(__dirname, "../scripts/dns-lookup-script"),
            response,
            cmd = util.format('"%s" "%s" %s', nodeBinary, scriptPath, hostname);

        response = shell.exec(cmd, {silent: true});
        if (response && response.code === 0) {
            output = response.output;
            if (output && net.isIP(output)) {
                return output;
            }
        }
        debug('hostname', "fail to resolve hostname " + hostname);
        return null;
    }

var ValidHostnameRegex = new RegExp("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$");

function isValidHostName(hostname) {
    return ValidHostnameRegex.test(hostname);
}

--- a/lib/dns-sync.js
+++ b/lib/dns-sync.js
@@ -1,7 +1,13 @@
     resolve: function resolve(hostname) {
         var output,
-            nodeBinary = process.execPath,
-            scriptPath = path.join(__dirname, "../scripts/dns-lookup-script"),
+            nodeBinary = process.execPath;
+
+        if (!isValidHostName(hostname)) {
+            console.error('Invalid hostname:', hostname);
+            return null;
+        }
+
+        var scriptPath = path.join(__dirname, "../scripts/dns-lookup-script"),
             response,
             cmd = util.format('"%s" "%s" %s', nodeBinary, scriptPath, hostname);

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2017-16100:latest
# bash /workspace/fix-run.sh
set -e

cd /workspace/node-dns-sync
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
npx mocha --grep "poc"

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2017-16100:latest
# bash /workspace/unit_test.sh
set -e

cd /workspace/node-dns-sync
git apply --whitespace=nowarn /workspace/fix.patch
npx mocha --timeout 50000 test/


An issue was discovered in urllib3 before 1.26.5. When provided with a URL containing many @ characters in the authority component, the authority regular expression exhibits catastrophic backtracking, causing a denial of service if a URL were passed as a parameter or redirected to via an HTTP redirect.

Uncontrolled Resource Consumption

The product does not properly control the allocation and maintenance of a limited resource.

def parse_url(url):
    """
    Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is
    performed to parse incomplete urls. Fields not provided will be None.
    This parser is RFC 3986 compliant.

    The parser logic and helper functions are based heavily on
    work done in the ``rfc3986`` module.

    :param str url: URL to parse into a :class:`.Url` namedtuple.

    Partly backwards-compatible with :mod:`urlparse`.

    Example::

        >>> parse_url('http://google.com/mail/')
        Url(scheme='http', host='google.com', port=None, path='/mail/', ...)
        >>> parse_url('google.com:80')
        Url(scheme=None, host='google.com', port=80, path=None, ...)
        >>> parse_url('/foo?bar')
        Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...)
    """
    if not url:
        # Empty
        return Url()

    source_url = url
    if not SCHEME_RE.search(url):
        url = "//" + url

    try:
        scheme, authority, path, query, fragment = URI_RE.match(url).groups()
        normalize_uri = scheme is None or scheme.lower() in NORMALIZABLE_SCHEMES

        if scheme:
            scheme = scheme.lower()

        if authority:
            auth, host, port = SUBAUTHORITY_RE.match(authority).groups()
            if auth and normalize_uri:
                auth = _encode_invalid_chars(auth, USERINFO_CHARS)
            if port == "":
                port = None
        else:
            auth, host, port = None, None, None

        if port is not None:
            port = int(port)
            if not (0 <= port <= 65535):
                raise LocationParseError(url)

        host = _normalize_host(host, scheme)

        if normalize_uri and path:
            path = _remove_path_dot_segments(path)
            path = _encode_invalid_chars(path, PATH_CHARS)
        if normalize_uri and query:
            query = _encode_invalid_chars(query, QUERY_CHARS)
        if normalize_uri and fragment:
            fragment = _encode_invalid_chars(fragment, FRAGMENT_CHARS)

    except (ValueError, AttributeError):
        return six.raise_from(LocationParseError(source_url), None)

    # For the sake of backwards compatibility we put empty
    # string values for path if there are any defined values
    # beyond the path in the URL.
    # TODO: Remove this when we break backwards compatibility.
    if not path:
        if query is not None or fragment is not None:
            path = ""
        else:
            path = None

    # Ensure that each part of the URL is a `str` for
    # backwards compatibility.
    if isinstance(url, six.text_type):
        ensure_func = six.ensure_text
    else:
        ensure_func = six.ensure_str

    def ensure_type(x):
        return x if x is None else ensure_func(x)

    return Url(
        scheme=ensure_type(scheme),
        auth=ensure_type(auth),
        host=ensure_type(host),
        port=port,
        path=ensure_type(path),
        query=ensure_type(query),
        fragment=ensure_type(fragment),
    )

def parse_url(url):
    """
    Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is
    performed to parse incomplete urls. Fields not provided will be None.
    This parser is RFC 3986 compliant.

    The parser logic and helper functions are based heavily on
    work done in the ``rfc3986`` module.

    :param str url: URL to parse into a :class:`.Url` namedtuple.

    Partly backwards-compatible with :mod:`urlparse`.

    Example::

        >>> parse_url('http://google.com/mail/')
        Url(scheme='http', host='google.com', port=None, path='/mail/', ...)
        >>> parse_url('google.com:80')
        Url(scheme=None, host='google.com', port=80, path=None, ...)
        >>> parse_url('/foo?bar')
        Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...)
    """
    if not url:
        # Empty
        return Url()

    source_url = url
    if not SCHEME_RE.search(url):
        url = "//" + url

    try:
        scheme, authority, path, query, fragment = URI_RE.match(url).groups()
        normalize_uri = scheme is None or scheme.lower() in NORMALIZABLE_SCHEMES

        if scheme:
            scheme = scheme.lower()

        if authority:
            auth, _, host_port = authority.rpartition("@")
            auth = auth or None
            host, port = _HOST_PORT_RE.match(host_port).groups()
            if auth and normalize_uri:
                auth = _encode_invalid_chars(auth, USERINFO_CHARS)
            if port == "":
                port = None
        else:
            auth, host, port = None, None, None

        if port is not None:
            port = int(port)
            if not (0 <= port <= 65535):
                raise LocationParseError(url)

        host = _normalize_host(host, scheme)

        if normalize_uri and path:
            path = _remove_path_dot_segments(path)
            path = _encode_invalid_chars(path, PATH_CHARS)
        if normalize_uri and query:
            query = _encode_invalid_chars(query, QUERY_CHARS)
        if normalize_uri and fragment:
            fragment = _encode_invalid_chars(fragment, FRAGMENT_CHARS)

    except (ValueError, AttributeError):
        return six.raise_from(LocationParseError(source_url), None)

    # For the sake of backwards compatibility we put empty
    # string values for path if there are any defined values
    # beyond the path in the URL.
    # TODO: Remove this when we break backwards compatibility.
    if not path:
        if query is not None or fragment is not None:
            path = ""
        else:
            path = None

    # Ensure that each part of the URL is a `str` for
    # backwards compatibility.
    if isinstance(url, six.text_type):
        ensure_func = six.ensure_text
    else:
        ensure_func = six.ensure_str

    def ensure_type(x):
        return x if x is None else ensure_func(x)

    return Url(
        scheme=ensure_type(scheme),
        auth=ensure_type(auth),
        host=ensure_type(host),
        port=port,
        path=ensure_type(path),
        query=ensure_type(query),
        fragment=ensure_type(fragment),
    )

--- a/src/urllib3/util/url.py
+++ b/src/urllib3/util/url.py
@@ -36,7 +36,9 @@
             scheme = scheme.lower()

         if authority:
-            auth, host, port = SUBAUTHORITY_RE.match(authority).groups()
+            auth, _, host_port = authority.rpartition("@")
+            auth = auth or None
+            host, port = _HOST_PORT_RE.match(host_port).groups()
             if auth and normalize_uri:
                 auth = _encode_invalid_chars(auth, USERINFO_CHARS)
             if port == "":


Goutil is a collection of miscellaneous functionality for the go language. In versions prior to 0.6.0 when users use fsutil.Unzip to unzip zip files from a malicious attacker, they may be vulnerable to path traversal. This vulnerability is known as a ZipSlip. This issue has been fixed in version 0.6.0, users are advised to upgrade. There are no known workarounds for this issue.

func Unzip(archive, targetDir string) (err error) {
	reader, err := zip.OpenReader(archive)
	if err != nil {
		return err
	}

	if err = os.MkdirAll(targetDir, DefaultDirPerm); err != nil {
		return
	}

	for _, file := range reader.File {
		fullPath := filepath.Join(targetDir, file.Name)
		if file.FileInfo().IsDir() {
			err = os.MkdirAll(fullPath, file.Mode())
			if err != nil {
				return err
			}
			continue
		}

		fileReader, err := file.Open()
		if err != nil {
			return err
		}

		targetFile, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
		if err != nil {
			_ = fileReader.Close()
			return err
		}

		_, err = io.Copy(targetFile, fileReader)

		// close all
		_ = fileReader.Close()
		targetFile.Close()

		if err != nil {
			return err
		}
	}

	return
}

func Unzip(archive, targetDir string) (err error) {
	reader, err := zip.OpenReader(archive)
	if err != nil {
		return err
	}

	if err = os.MkdirAll(targetDir, DefaultDirPerm); err != nil {
		return
	}

	for _, file := range reader.File {

		if strings.Contains(file.Name, "..") {
			return fmt.Errorf("illegal file path in zip: %v", file.Name)
		}

		fullPath := filepath.Join(targetDir, file.Name)

		if file.FileInfo().IsDir() {
			err = os.MkdirAll(fullPath, file.Mode())
			if err != nil {
				return err
			}
			continue
		}

		fileReader, err := file.Open()
		if err != nil {
			return err
		}

		targetFile, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
		if err != nil {
			_ = fileReader.Close()
			return err
		}

		_, err = io.Copy(targetFile, fileReader)

		// close all
		_ = fileReader.Close()
		targetFile.Close()

		if err != nil {
			return err
		}
	}

	return
}

--- a/fsutil/operate.go
+++ b/fsutil/operate.go
@@ -9,7 +9,13 @@
 	}

 	for _, file := range reader.File {
+
+		if strings.Contains(file.Name, "..") {
+			return fmt.Errorf("illegal file path in zip: %v", file.Name)
+		}
+
 		fullPath := filepath.Join(targetDir, file.Name)
+
 		if file.FileInfo().IsDir() {
 			err = os.MkdirAll(fullPath, file.Mode())
 			if err != nil {


Bazarr manages and downloads subtitles. In version 1.2.4, the proxy method in bazarr/bazarr/app/ui.py does not validate the user-controlled protocol and url variables and passes them to requests.get() without any sanitization, which leads to a blind server-side request forgery (SSRF). This issue allows for crafting GET requests to internal and external resources on behalf of the server. 1.3.1 contains a partial fix, which limits the vulnerability to HTTP/HTTPS protocols.

Server-Side Request Forgery (SSRF)

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

def backup_download(filename):
    return send_file(os.path.join(settings.backup.folder, filename), max_age=0, as_attachment=True)

def swaggerui_static(filename):
    return send_file(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'libs', 'flask_restx',
                     'static', filename))

def proxy(protocol, url):
    url = protocol + '://' + unquote(url)
    params = request.args
    try:
        result = requests.get(url, params, allow_redirects=False, verify=False, timeout=5, headers=headers)
    except Exception as e:
        return dict(status=False, error=repr(e))
    else:
        if result.status_code == 200:
            try:
                version = result.json()['version']
                return dict(status=True, version=version)
            except Exception:
                return dict(status=False, error='Error Occurred. Check your settings.')
        elif result.status_code == 401:
            return dict(status=False, error='Access Denied. Check API key.')
        elif result.status_code == 404:
            return dict(status=False, error='Cannot get version. Maybe unsupported legacy API call?')
        elif 300 <= result.status_code <= 399:
            return dict(status=False, error='Wrong URL Base.')
        else:
            return dict(status=False, error=result.raise_for_status())

def backup_download(filename):
    fullpath = os.path.normpath(os.path.join(settings.backup.folder, filename))
    if not fullpath.startswith(settings.backup.folder):
        return '', 404
    else:
        return send_file(fullpath, max_age=0, as_attachment=True)

def swaggerui_static(filename):
    basepath = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'libs', 'flask_restx',
                            'static')
    fullpath = os.path.join(basepath, filename)
    if not fullpath.startswith(basepath):
        return '', 404
    else:
        return send_file(fullpath)

def proxy(protocol, url):
    if protocol.lower not in ['http', 'https']:
        return dict(status=False, error='Unsupported protocol')
    url = protocol + '://' + unquote(url)
    params = request.args
    try:
        result = requests.get(url, params, allow_redirects=False, verify=False, timeout=5, headers=headers)
    except Exception as e:
        return dict(status=False, error=repr(e))
    else:
        if result.status_code == 200:
            try:
                version = result.json()['version']
                return dict(status=True, version=version)
            except Exception:
                return dict(status=False, error='Error Occurred. Check your settings.')
        elif result.status_code == 401:
            return dict(status=False, error='Access Denied. Check API key.')
        elif result.status_code == 404:
            return dict(status=False, error='Cannot get version. Maybe unsupported legacy API call?')
        elif 300 <= result.status_code <= 399:
            return dict(status=False, error='Wrong URL Base.')
        else:
            return dict(status=False, error=result.raise_for_status())

--- a/bazarr/app/ui.py
+++ b/bazarr/app/ui.py
@@ -1,2 +1,6 @@
 def backup_download(filename):
-    return send_file(os.path.join(settings.backup.folder, filename), max_age=0, as_attachment=True)
+    fullpath = os.path.normpath(os.path.join(settings.backup.folder, filename))
+    if not fullpath.startswith(settings.backup.folder):
+        return '', 404
+    else:
+        return send_file(fullpath, max_age=0, as_attachment=True)

--- a/bazarr/app/ui.py
+++ b/bazarr/app/ui.py
@@ -1,3 +1,8 @@
 def swaggerui_static(filename):
-    return send_file(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'libs', 'flask_restx',
-                     'static', filename))
+    basepath = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'libs', 'flask_restx',
+                            'static')
+    fullpath = os.path.join(basepath, filename)
+    if not fullpath.startswith(basepath):
+        return '', 404
+    else:
+        return send_file(fullpath)

--- a/bazarr/app/ui.py
+++ b/bazarr/app/ui.py
@@ -1,4 +1,6 @@
 def proxy(protocol, url):
+    if protocol.lower not in ['http', 'https']:
+        return dict(status=False, error='Unsupported protocol')
     url = protocol + '://' + unquote(url)
     params = request.args
     try: