openwhyd is vulnerable to Improper Authorization

Improper Authorization

The product does not perform or incorrectly performs an authorization check when an actor attempts to access a resource or perform an action.

exports.handleRequest = function (request, form, response, ignorePassword) {
  form = form || {};
  if (form.password && !form.md5) form.md5 = md5(form.password);

  request.logToConsole('login.handleRequest', {
    action: form.action,
    email: form.email,
    md5: form.md5,
  });

  function renderJSON(json) {
    response[form.ajax == 'iframe' ? 'renderWrappedJSON' : 'renderJSON'](json);
  }

  // in case of successful login
  function renderRedirect(url, user) {
    request.session.whydUid = (user || {}).id;
    if (!form.ajax) response.renderHTML(loggingTemplate.htmlRedirect(url));
    else {
      var json = { redirect: url };
      if (form.includeUser) {
        userApi.fetchUserData(user, function (user) {
          json.user = user;
          renderJSON(json);
        });
      } else renderJSON(json);
    }
  }

  function renderForm(form) {
    delete request.session;
    if (form.ajax) renderJSON(form);
    else response.renderHTML(loggingTemplate.renderLoginPage(form));
  }

  if (form.action === 'logout') {
    request.session.destroy(function (err) {
      if (err) {
        console.error('error from request.session.destroy()', err);
        form.error = err;
      }
      console.log('logout result', form.error);
      renderForm(form);
    });
    return;
  } else if (form.email) {
    form.email = emailModel.normalize(form.email);

    userModel[form.email.indexOf('@') > -1 ? 'fetchByEmail' : 'fetchByHandle'](
      form.email,
      function (dbUser) {
        if (!dbUser) {
          form.error = "Are you sure? We don't recognize your email address!";
        } else if (form.action == 'forgot') {
          notifEmails.sendPasswordReset(dbUser._id, dbUser.pwd, form.redirect);
          /*form.error*/ form.ok =
            'We just sent you an email to reset your password, wait for it!';
        } else if (
          !ignorePassword &&
          !form.md5 /*(!form.password || form.password == "password")*/
        ) {
          form.error = 'Please enter your password below:';
        } else if (
          form.action == 'login' &&
          (ignorePassword || dbUser.pwd == form.md5)
        ) {
          // console.log('ok, user logged in as: ' + dbUser.name);
          // console.log('form.fbUid', form.fbUid);
          if (form.fbUid)
            userModel.update(dbUser._id, {
              $set: {
                fbId: form.fbUid,
                fbTok: form.fbTok, // access token provided on last facebook login
              },
            });
          renderRedirect(form.redirect || '/', dbUser);
          return; // prevent default response (renderForm)
        } else if (form.action != 'logout') {
          form.wrongPassword = 1;
          form.error = 'Your password seems wrong... Try again!';
        }

        console.log('login result', form.error);
        renderForm(form);
      }
    );
  } else renderForm(form);
};

http.ServerResponse.prototype.safeRedirect = function (url) {
  const fullURL = new URL(url, config.urlPrefix);
  if (`${fullURL.protocol}//${fullURL.host}` !== config.urlPrefix)
    return this.forbidden();
  this.redirect(url);
};

exports.handleRequest = function (request, form, response, ignorePassword) {
  form = form || {};
  if (form.password && !form.md5) form.md5 = md5(form.password);

  request.logToConsole('login.handleRequest', {
    action: form.action,
    email: form.email,
    md5: form.md5,
  });

  function renderJSON(json) {
    response[form.ajax == 'iframe' ? 'renderWrappedJSON' : 'renderJSON'](json);
  }

  // in case of successful login
  function renderRedirect(url, user) {
    request.session.whydUid = (user || {}).id;
    if (!form.ajax) response.renderHTML(loggingTemplate.htmlRedirect(url));
    else {
      var json = { redirect: url };
      if (form.includeUser) {
        userApi.fetchUserData(user, function (user) {
          json.user = user;
          renderJSON(json);
        });
      } else renderJSON(json);
    }
  }

  function renderForm(form) {
    delete request.session;
    if (form.ajax) renderJSON(form);
    else response.renderHTML(loggingTemplate.renderLoginPage(form));
  }

  if (form.action === 'logout') {
    request.session.destroy(function (err) {
      if (err) {
        console.error('error from request.session.destroy()', err);
        form.error = err;
      }
      console.log('logout result', form.error);
      renderForm(form);
    });
    return;
  } else if (form.email) {
    form.email = emailModel.normalize(form.email);

    userModel[form.email.indexOf('@') > -1 ? 'fetchByEmail' : 'fetchByHandle'](
      form.email,
      function (dbUser) {
        if (
          form.redirect &&
          snip.getSafeOpenwhydURL(form.redirect, config.urlPrefix) === false
        ) {
          form.error = 'Unsafe redirect target';
        } else if (!dbUser) {
          form.error = "Are you sure? We don't recognize your email address!";
        } else if (form.action == 'forgot') {
          notifEmails.sendPasswordReset(dbUser._id, dbUser.pwd, form.redirect);
          /*form.error*/ form.ok =
            'We just sent you an email to reset your password, wait for it!';
        } else if (
          !ignorePassword &&
          !form.md5 /*(!form.password || form.password == "password")*/
        ) {
          form.error = 'Please enter your password below:';
        } else if (
          form.action == 'login' &&
          (ignorePassword || dbUser.pwd == form.md5)
        ) {
          // console.log('ok, user logged in as: ' + dbUser.name);
          // console.log('form.fbUid', form.fbUid);
          if (form.fbUid)
            userModel.update(dbUser._id, {
              $set: {
                fbId: form.fbUid,
                fbTok: form.fbTok, // access token provided on last facebook login
              },
            });
          renderRedirect(form.redirect || '/', dbUser);
          return; // prevent default response (renderForm)
        } else if (form.action != 'logout') {
          form.wrongPassword = 1;
          form.error = 'Your password seems wrong... Try again!';
        }

        console.log('login result', form.error);
        renderForm(form);
      }
    );
  } else renderForm(form);
};

http.ServerResponse.prototype.safeRedirect = function (url) {
  const safeURL = snip.getSafeOpenwhydURL(url, config.urlPrefix);
  if (safeURL === false) return this.forbidden();
  this.redirect(url);
};

const snip = require('../../snip.js');
const config = require('../../models/config.js');

exports.getSafeOpenwhydURL = function (url, safeUrlPrefix) {
  if (typeof url !== 'string' || url.includes('<script')) return false;
  const fullURL = new URL(url, safeUrlPrefix);
  if (`${fullURL.protocol}//${fullURL.host}` !== safeUrlPrefix) return false;
  else return fullURL;
};

--- a/app/controllers/api/login.js
+++ b/app/controllers/api/login.js
@@ -49,7 +49,12 @@
     userModel[form.email.indexOf('@') > -1 ? 'fetchByEmail' : 'fetchByHandle'](
       form.email,
       function (dbUser) {
-        if (!dbUser) {
+        if (
+          form.redirect &&
+          snip.getSafeOpenwhydURL(form.redirect, config.urlPrefix) === false
+        ) {
+          form.error = 'Unsafe redirect target';
+        } else if (!dbUser) {
           form.error = "Are you sure? We don't recognize your email address!";
         } else if (form.action == 'forgot') {
           notifEmails.sendPasswordReset(dbUser._id, dbUser.pwd, form.redirect);

--- a/app/models/logging.js
+++ b/app/models/logging.js
@@ -1,6 +1,5 @@
 http.ServerResponse.prototype.safeRedirect = function (url) {
-  const fullURL = new URL(url, config.urlPrefix);
-  if (`${fullURL.protocol}//${fullURL.host}` !== config.urlPrefix)
-    return this.forbidden();
+  const safeURL = snip.getSafeOpenwhydURL(url, config.urlPrefix);
+  if (safeURL === false) return this.forbidden();
   this.redirect(url);
 };

--- /dev/null
+++ b/app/models/logging.js
@@ -0,0 +1,2 @@
+const snip = require('../../snip.js');
+const config = require('../../models/config.js');

--- /dev/null
+++ b/app/models/logging.js
@@ -0,0 +1,6 @@
+exports.getSafeOpenwhydURL = function (url, safeUrlPrefix) {
+  if (typeof url !== 'string' || url.includes('<script')) return false;
+  const fullURL = new URL(url, safeUrlPrefix);
+  if (`${fullURL.protocol}//${fullURL.host}` !== safeUrlPrefix) return false;
+  else return fullURL;
+};


lib/cmd.js in the node-windows package before 1.0.0-beta.6 for Node.js allows command injection via the PID parameter.

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.

  kill: function(pid,force,callback){
    if (!pid){
      throw new Error('PID is required for the kill operation.');
    }
    callback = callback || function(){};
    if (typeof force == 'function'){
      callback = force;
      force = false;
    }
    exec("taskkill /PID "+pid+(force==true?' /f':''),callback);
  },

  kill: function(pid,force,callback){
    if (!pid){
      throw new Error('PID is required for the kill operation.');
    }

    if (typeof isNaN(pid)) {
      throw new Error('PID must be a number.')
    }

    callback = callback || function(){};
    if (typeof force == 'function'){
      callback = force;
      force = false;
    }
    exec("taskkill /PID "+pid+(force==true?' /f':''),callback);
  },

--- a/lib/cmd.js
+++ b/lib/cmd.js
@@ -2,6 +2,11 @@
     if (!pid){
       throw new Error('PID is required for the kill operation.');
     }
+
+    if (typeof isNaN(pid)) {
+      throw new Error('PID must be a number.')
+    }
+
     callback = callback || function(){};
     if (typeof force == 'function'){
       callback = force;


Caddy v2.4 was discovered to contain an open redirect vulnerability. A remote unauthenticated attacker may exploit this vulnerability to redirect users to arbitrary web URLs by tricking the victim users to click on crafted links.

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.

func (m MatchPath) Match(r *http.Request) bool {
	// PathUnescape returns an error if the escapes aren't
	// well-formed, meaning the count % matches the RFC.
	// Return early if the escape is improper.
	unescapedPath, err := url.PathUnescape(r.URL.Path)
	if err != nil {
		return false
	}

	lowerPath := strings.ToLower(unescapedPath)

	// see #2917; Windows ignores trailing dots and spaces
	// when accessing files (sigh), potentially causing a
	// security risk (cry) if PHP files end up being served
	// as static files, exposing the source code, instead of
	// being matched by *.php to be treated as PHP scripts
	lowerPath = strings.TrimRight(lowerPath, ". ")

	// Clean the path, merges doubled slashes, etc.
	// This ensures maliciously crafted requests can't bypass
	// the path matcher. See #4407
	lowerPath = path.Clean(lowerPath)

	// Cleaning may remove the trailing slash, but we want to keep it
	if lowerPath != "/" && strings.HasSuffix(r.URL.Path, "/") {
		lowerPath = lowerPath + "/"
	}

	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)

	for _, matchPath := range m {
		matchPath = repl.ReplaceAll(matchPath, "")

		// special case: whole path is wildcard; this is unnecessary
		// as it matches all requests, which is the same as no matcher
		if matchPath == "*" {
			return true
		}

		// special case: first and last characters are wildcard,
		// treat it as a fast substring match
		if len(matchPath) > 1 &&
			strings.HasPrefix(matchPath, "*") &&
			strings.HasSuffix(matchPath, "*") {
			if strings.Contains(lowerPath, matchPath[1:len(matchPath)-1]) {
				return true
			}
			continue
		}

		// special case: first character is a wildcard,
		// treat it as a fast suffix match
		if strings.HasPrefix(matchPath, "*") {
			if strings.HasSuffix(lowerPath, matchPath[1:]) {
				return true
			}
			continue
		}

		// special case: last character is a wildcard,
		// treat it as a fast prefix match
		if strings.HasSuffix(matchPath, "*") {
			if strings.HasPrefix(lowerPath, matchPath[:len(matchPath)-1]) {
				return true
			}
			continue
		}

		// for everything else, try globular matching, which also
		// is exact matching if there are no glob/wildcard chars;
		// can ignore error here because we can't handle it anyway
		matches, _ := filepath.Match(matchPath, lowerPath)
		if matches {
			return true
		}
	}
	return false
}

func (m MatchPath) Match(r *http.Request) bool {
	// PathUnescape returns an error if the escapes aren't
	// well-formed, meaning the count % matches the RFC.
	// Return early if the escape is improper.
	unescapedPath, err := url.PathUnescape(r.URL.Path)
	if err != nil {
		return false
	}

	lowerPath := strings.ToLower(unescapedPath)

	// Clean the path, merges doubled slashes, etc.
	// This ensures maliciously crafted requests can't bypass
	// the path matcher. See #4407
	lowerPath = path.Clean(lowerPath)

	// see #2917; Windows ignores trailing dots and spaces
	// when accessing files (sigh), potentially causing a
	// security risk (cry) if PHP files end up being served
	// as static files, exposing the source code, instead of
	// being matched by *.php to be treated as PHP scripts
	lowerPath = strings.TrimRight(lowerPath, ". ")

	// Cleaning may remove the trailing slash, but we want to keep it
	if lowerPath != "/" && strings.HasSuffix(r.URL.Path, "/") {
		lowerPath = lowerPath + "/"
	}

	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)

	for _, matchPath := range m {
		matchPath = repl.ReplaceAll(matchPath, "")

		// special case: whole path is wildcard; this is unnecessary
		// as it matches all requests, which is the same as no matcher
		if matchPath == "*" {
			return true
		}

		// special case: first and last characters are wildcard,
		// treat it as a fast substring match
		if len(matchPath) > 1 &&
			strings.HasPrefix(matchPath, "*") &&
			strings.HasSuffix(matchPath, "*") {
			if strings.Contains(lowerPath, matchPath[1:len(matchPath)-1]) {
				return true
			}
			continue
		}

		// special case: first character is a wildcard,
		// treat it as a fast suffix match
		if strings.HasPrefix(matchPath, "*") {
			if strings.HasSuffix(lowerPath, matchPath[1:]) {
				return true
			}
			continue
		}

		// special case: last character is a wildcard,
		// treat it as a fast prefix match
		if strings.HasSuffix(matchPath, "*") {
			if strings.HasPrefix(lowerPath, matchPath[:len(matchPath)-1]) {
				return true
			}
			continue
		}

		// for everything else, try globular matching, which also
		// is exact matching if there are no glob/wildcard chars;
		// can ignore error here because we can't handle it anyway
		matches, _ := filepath.Match(matchPath, lowerPath)
		if matches {
			return true
		}
	}
	return false
}

--- a/modules/caddyhttp/matchers.go
+++ b/modules/caddyhttp/matchers.go
@@ -9,17 +9,17 @@

 	lowerPath := strings.ToLower(unescapedPath)

+	// Clean the path, merges doubled slashes, etc.
+	// This ensures maliciously crafted requests can't bypass
+	// the path matcher. See #4407
+	lowerPath = path.Clean(lowerPath)
+
 	// see #2917; Windows ignores trailing dots and spaces
 	// when accessing files (sigh), potentially causing a
 	// security risk (cry) if PHP files end up being served
 	// as static files, exposing the source code, instead of
 	// being matched by *.php to be treated as PHP scripts
 	lowerPath = strings.TrimRight(lowerPath, ". ")
-
-	// Clean the path, merges doubled slashes, etc.
-	// This ensures maliciously crafted requests can't bypass
-	// the path matcher. See #4407
-	lowerPath = path.Clean(lowerPath)

 	// Cleaning may remove the trailing slash, but we want to keep it
 	if lowerPath != "/" && strings.HasSuffix(r.URL.Path, "/") {


Open Container Initiative umoci before 0.4.7 allows attackers to overwrite arbitrary host paths via a crafted image that causes symlink traversal when "umoci unpack" or "umoci raw unpack" is used.

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 (te *TarExtractor) UnpackEntry(root string, hdr *tar.Header, r io.Reader) (Err error) {
	// Make the paths safe.
	hdr.Name = CleanPath(hdr.Name)
	root = filepath.Clean(root)

	log.WithFields(log.Fields{
		"root": root,
		"path": hdr.Name,
		"type": hdr.Typeflag,
	}).Debugf("unpacking entry")

	// Get directory and filename, but we have to safely get the directory
	// component of the path. SecureJoinVFS will evaluate the path itself,
	// which we don't want (we're clever enough to handle the actual path being
	// a symlink).
	unsafeDir, file := filepath.Split(hdr.Name)
	if filepath.Join("/", hdr.Name) == "/" {
		// If we got an entry for the root, then unsafeDir is the full path.
		unsafeDir, file = hdr.Name, "."
	}
	dir, err := securejoin.SecureJoinVFS(root, unsafeDir, te.fsEval)
	if err != nil {
		return errors.Wrap(err, "sanitise symlinks in root")
	}
	path := filepath.Join(dir, file)

	// Before we do anything, get the state of dir. Because we might be adding
	// or removing files, our parent directory might be modified in the
	// process. As a result, we want to be able to restore the old state
	// (because we only apply state that we find in the archive we're iterating
	// over). We can safely ignore an error here, because a non-existent
	// directory will be fixed by later archive entries.
	if dirFi, err := te.fsEval.Lstat(dir); err == nil && path != dir {
		// FIXME: This is really stupid.
		// #nosec G104
		link, _ := te.fsEval.Readlink(dir)
		dirHdr, err := tar.FileInfoHeader(dirFi, link)
		if err != nil {
			return errors.Wrap(err, "convert dirFi to dirHdr")
		}

		// More faking to trick restoreMetadata to actually restore the directory.
		dirHdr.Typeflag = tar.TypeDir
		dirHdr.Linkname = ""

		// os.Lstat doesn't get the list of xattrs by default. We need to fill
		// this explicitly. Note that while Go's "archive/tar" takes strings,
		// in Go strings can be arbitrary byte sequences so this doesn't
		// restrict the possible values.
		// TODO: Move this to a separate function so we can share it with
		//       tar_generate.go.
		xattrs, err := te.fsEval.Llistxattr(dir)
		if err != nil {
			if errors.Cause(err) != unix.ENOTSUP {
				return errors.Wrap(err, "get dirHdr.Xattrs")
			}
			if !te.enotsupWarned {
				log.Warnf("xattr{%s} ignoring ENOTSUP on llistxattr", dir)
				log.Warnf("xattr{%s} destination filesystem does not support xattrs, further warnings will be suppressed", path)
				te.enotsupWarned = true
			} else {
				log.Debugf("xattr{%s} ignoring ENOTSUP on clearxattrs", path)
			}
		}
		if len(xattrs) > 0 {
			dirHdr.Xattrs = map[string]string{}
			for _, xattr := range xattrs {
				value, err := te.fsEval.Lgetxattr(dir, xattr)
				if err != nil {
					return errors.Wrap(err, "get xattr")
				}
				dirHdr.Xattrs[xattr] = string(value)
			}
		}

		// Ensure that after everything we correctly re-apply the old metadata.
		// We don't map this header because we're restoring files that already
		// existed on the filesystem, not from a tar layer.
		defer func() {
			// Only overwrite the error if there wasn't one already.
			if err := te.restoreMetadata(dir, dirHdr); err != nil {
				if Err == nil {
					Err = errors.Wrap(err, "restore parent directory")
				}
			}
		}()
	}

	// Currently the spec doesn't specify what the hdr.Typeflag of whiteout
	// files is meant to be. We specifically only produce regular files
	// ('\x00') but it could be possible that someone produces a different
	// Typeflag, expecting that the path is the only thing that matters in a
	// whiteout entry.
	if strings.HasPrefix(file, whPrefix) {
		switch te.whiteoutMode {
		case OCIStandardWhiteout:
			return te.ociWhiteout(root, dir, file)
		case OverlayFSWhiteout:
			return te.overlayFSWhiteout(dir, file)
		default:
			return errors.Errorf("unknown whiteout mode %d", te.whiteoutMode)
		}
	}

	// Get information about the path. This has to be done after we've dealt
	// with whiteouts because it turns out that lstat(2) will return EPERM if
	// you try to stat a whiteout on AUFS.
	fi, err := te.fsEval.Lstat(path)
	if err != nil {
		// File doesn't exist, just switch fi to the file header.
		fi = hdr.FileInfo()
	}

	// Attempt to create the parent directory of the path we're unpacking.
	// We do a MkdirAll here because even though you need to have a tar entry
	// for every component of a new path, applyMetadata will correct any
	// inconsistencies.
	// FIXME: We have to make this consistent, since if the tar archive doesn't
	//        have entries for some of these components we won't be able to
	//        verify that we have consistent results during unpacking.
	if err := te.fsEval.MkdirAll(dir, 0777); err != nil {
		return errors.Wrap(err, "mkdir parent")
	}

	isDirlink := false
	// We remove whatever existed at the old path to clobber it so that
	// creating a new path will not break. The only exception is if the path is
	// a directory in both the layer and the current filesystem, in which case
	// we don't delete it for obvious reasons. In all other cases we clobber.
	//
	// Note that this will cause hard-links in the "lower" layer to not be able
	// to point to "upper" layer inodes even if the extracted type is the same
	// as the old one, however it is not clear whether this is something a user
	// would expect anyway. In addition, this will incorrectly deal with a
	// TarLink that is present before the "upper" entry in the layer but the
	// "lower" file still exists (so the hard-link would point to the old
	// inode). It's not clear if such an archive is actually valid though.
	if !fi.IsDir() || hdr.Typeflag != tar.TypeDir {
		// If we are in --keep-dirlinks mode and the existing fs object is a
		// symlink to a directory (with the pending object is a directory), we
		// don't remove the symlink (and instead allow subsequent objects to be
		// just written through the symlink into the directory). This is a very
		// specific usecase where layers that were generated independently from
		// each other (on different base filesystems) end up with weird things
		// like /lib64 being a symlink only sometimes but you never want to
		// delete libraries (not just the ones that were under the "real"
		// directory).
		//
		// TODO: This code should also handle a pending symlink entry where the
		//       existing object is a directory. I'm not sure how we could
		//       disambiguate this from a symlink-to-a-file but I imagine that
		//       this is something that would also be useful in the same vein
		//       as --keep-dirlinks (which currently only prevents clobbering
		//       in the opposite case).
		if te.keepDirlinks &&
			fi.Mode()&os.ModeSymlink == os.ModeSymlink && hdr.Typeflag == tar.TypeDir {
			isDirlink, err = te.isDirlink(root, path)
			if err != nil {
				return errors.Wrap(err, "check is dirlink")
			}
		}
		if !(isDirlink && te.keepDirlinks) {
			if err := te.fsEval.RemoveAll(path); err != nil {
				return errors.Wrap(err, "clobber old path")
			}
		}
	}

	// Now create or otherwise modify the state of the path. Right now, either
	// the type of path matches hdr or the path doesn't exist. Note that we
	// don't care about umasks or the initial mode here, since applyMetadata
	// will fix all of that for us.
	switch hdr.Typeflag {
	// regular file
	case tar.TypeReg, tar.TypeRegA:
		// Create a new file, then just copy the data.
		fh, err := te.fsEval.Create(path)
		if err != nil {
			return errors.Wrap(err, "create regular")
		}
		defer fh.Close()

		// We need to make sure that we copy all of the bytes.
		n, err := io.Copy(fh, r)
		if int64(n) != hdr.Size {
			if err != nil {
				err = errors.Wrapf(err, "short write")
			} else {
				err = io.ErrShortWrite
			}
		}
		if err != nil {
			return errors.Wrap(err, "unpack to regular file")
		}

		// Force close here so that we don't affect the metadata.
		if err := fh.Close(); err != nil {
			return errors.Wrap(err, "close unpacked regular file")
		}

	// directory
	case tar.TypeDir:
		if isDirlink {
			break
		}

		// Attempt to create the directory. We do a MkdirAll here because even
		// though you need to have a tar entry for every component of a new
		// path, applyMetadata will correct any inconsistencies.
		if err := te.fsEval.MkdirAll(path, 0777); err != nil {
			return errors.Wrap(err, "mkdirall")
		}

	// hard link, symbolic link
	case tar.TypeLink, tar.TypeSymlink:
		linkname := hdr.Linkname

		// Hardlinks and symlinks act differently when it comes to the scoping.
		// In both cases, we have to just unlink and then re-link the given
		// path. But the function used and the argument are slightly different.
		var linkFn func(string, string) error
		switch hdr.Typeflag {
		case tar.TypeLink:
			linkFn = te.fsEval.Link
			// Because hardlinks are inode-based we need to scope the link to
			// the rootfs using SecureJoinVFS. As before, we need to be careful
			// that we don't resolve the last part of the link path (in case
			// the user actually wanted to hardlink to a symlink).
			unsafeLinkDir, linkFile := filepath.Split(CleanPath(linkname))
			linkDir, err := securejoin.SecureJoinVFS(root, unsafeLinkDir, te.fsEval)
			if err != nil {
				return errors.Wrap(err, "sanitise hardlink target in root")
			}
			linkname = filepath.Join(linkDir, linkFile)
		case tar.TypeSymlink:
			linkFn = te.fsEval.Symlink
		}

		// Link the new one.
		if err := linkFn(linkname, path); err != nil {
			// FIXME: Currently this can break if tar hardlink entries occur
			//        before we hit the entry those hardlinks link to. I have a
			//        feeling that such archives are invalid, but the correct
			//        way of handling this is to delay link creation until the
			//        very end. Unfortunately this won't work with symlinks
			//        (which can link to directories).
			return errors.Wrap(err, "link")
		}

	// character device node, block device node
	case tar.TypeChar, tar.TypeBlock:
		// In rootless mode we have no choice but to fake this, since mknod(2)
		// doesn't work as an unprivileged user here.
		//
		// TODO: We need to add the concept of a fake block device in
		//       "user.rootlesscontainers", because this workaround suffers
		//       from the obvious issue that if the file is touched (even the
		//       metadata) then it will be incorrectly copied into the layer.
		//       This would break distribution images fairly badly.
		if te.partialRootless {
			log.Warnf("rootless{%s} creating empty file in place of device %d:%d", hdr.Name, hdr.Devmajor, hdr.Devminor)
			fh, err := te.fsEval.Create(path)
			if err != nil {
				return errors.Wrap(err, "create rootless block")
			}
			defer fh.Close()
			if err := fh.Chmod(0); err != nil {
				return errors.Wrap(err, "chmod 0 rootless block")
			}
			goto out
		}

		// Otherwise the handling is the same as a FIFO.
		fallthrough
	// fifo node
	case tar.TypeFifo:
		// We have to remove and then create the device. In the FIFO case we
		// could choose not to do so, but we do it anyway just to be on the
		// safe side.

		mode := system.Tarmode(hdr.Typeflag)
		dev := unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor))

		// Create the node.
		if err := te.fsEval.Mknod(path, os.FileMode(int64(mode)|hdr.Mode), dev); err != nil {
			return errors.Wrap(err, "mknod")
		}

	// We should never hit any other headers (Go abstracts them away from us),
	// and we can't handle any custom Tar extensions. So just error out.
	default:
		return fmt.Errorf("unpack entry: %s: unknown typeflag '\\x%x'", hdr.Name, hdr.Typeflag)
	}

out:
	// Apply the metadata, which will apply any mappings necessary. We don't
	// apply metadata for hardlinks, because hardlinks don't have any separate
	// metadata from their link (and the tar headers might not be filled).
	if hdr.Typeflag != tar.TypeLink {
		if err := te.applyMetadata(path, hdr); err != nil {
			return errors.Wrap(err, "apply hdr metadata")
		}
	}

	// Everything is done -- the path now exists. Add it (and all its
	// ancestors) to the set of upper paths. We first have to figure out the
	// proper path corresponding to hdr.Name though.
	upperPath, err := filepath.Rel(root, path)
	if err != nil {
		// Really shouldn't happen because of the guarantees of SecureJoinVFS.
		return errors.Wrap(err, "find relative-to-root [should never happen]")
	}
	for pth := upperPath; pth != filepath.Dir(pth); pth = filepath.Dir(pth) {
		te.upperPaths[pth] = struct{}{}
	}
	return nil
}

func (te *TarExtractor) UnpackEntry(root string, hdr *tar.Header, r io.Reader) (Err error) {
	// Make the paths safe.
	hdr.Name = CleanPath(hdr.Name)
	root = filepath.Clean(root)

	log.WithFields(log.Fields{
		"root": root,
		"path": hdr.Name,
		"type": hdr.Typeflag,
	}).Debugf("unpacking entry")

	// Get directory and filename, but we have to safely get the directory
	// component of the path. SecureJoinVFS will evaluate the path itself,
	// which we don't want (we're clever enough to handle the actual path being
	// a symlink).
	unsafeDir, file := filepath.Split(hdr.Name)
	if filepath.Join("/", hdr.Name) == "/" {
		// If we got an entry for the root, then unsafeDir is the full path.
		unsafeDir, file = hdr.Name, "."
		// If we're being asked to change the root type, bail because they may
		// change it to a symlink which we could inadvertently follow.
		if hdr.Typeflag != tar.TypeDir {
			return errors.New("malicious tar entry -- refusing to change type of root directory")
		}
	}
	dir, err := securejoin.SecureJoinVFS(root, unsafeDir, te.fsEval)
	if err != nil {
		return errors.Wrap(err, "sanitise symlinks in root")
	}
	path := filepath.Join(dir, file)

	// Before we do anything, get the state of dir. Because we might be adding
	// or removing files, our parent directory might be modified in the
	// process. As a result, we want to be able to restore the old state
	// (because we only apply state that we find in the archive we're iterating
	// over). We can safely ignore an error here, because a non-existent
	// directory will be fixed by later archive entries.
	if dirFi, err := te.fsEval.Lstat(dir); err == nil && path != dir {
		// FIXME: This is really stupid.
		// #nosec G104
		link, _ := te.fsEval.Readlink(dir)
		dirHdr, err := tar.FileInfoHeader(dirFi, link)
		if err != nil {
			return errors.Wrap(err, "convert dirFi to dirHdr")
		}

		// More faking to trick restoreMetadata to actually restore the directory.
		dirHdr.Typeflag = tar.TypeDir
		dirHdr.Linkname = ""

		// os.Lstat doesn't get the list of xattrs by default. We need to fill
		// this explicitly. Note that while Go's "archive/tar" takes strings,
		// in Go strings can be arbitrary byte sequences so this doesn't
		// restrict the possible values.
		// TODO: Move this to a separate function so we can share it with
		//       tar_generate.go.
		xattrs, err := te.fsEval.Llistxattr(dir)
		if err != nil {
			if errors.Cause(err) != unix.ENOTSUP {
				return errors.Wrap(err, "get dirHdr.Xattrs")
			}
			if !te.enotsupWarned {
				log.Warnf("xattr{%s} ignoring ENOTSUP on llistxattr", dir)
				log.Warnf("xattr{%s} destination filesystem does not support xattrs, further warnings will be suppressed", path)
				te.enotsupWarned = true
			} else {
				log.Debugf("xattr{%s} ignoring ENOTSUP on clearxattrs", path)
			}
		}
		if len(xattrs) > 0 {
			dirHdr.Xattrs = map[string]string{}
			for _, xattr := range xattrs {
				value, err := te.fsEval.Lgetxattr(dir, xattr)
				if err != nil {
					return errors.Wrap(err, "get xattr")
				}
				dirHdr.Xattrs[xattr] = string(value)
			}
		}

		// Ensure that after everything we correctly re-apply the old metadata.
		// We don't map this header because we're restoring files that already
		// existed on the filesystem, not from a tar layer.
		defer func() {
			// Only overwrite the error if there wasn't one already.
			if err := te.restoreMetadata(dir, dirHdr); err != nil {
				if Err == nil {
					Err = errors.Wrap(err, "restore parent directory")
				}
			}
		}()
	}

	// Currently the spec doesn't specify what the hdr.Typeflag of whiteout
	// files is meant to be. We specifically only produce regular files
	// ('\x00') but it could be possible that someone produces a different
	// Typeflag, expecting that the path is the only thing that matters in a
	// whiteout entry.
	if strings.HasPrefix(file, whPrefix) {
		switch te.whiteoutMode {
		case OCIStandardWhiteout:
			return te.ociWhiteout(root, dir, file)
		case OverlayFSWhiteout:
			return te.overlayFSWhiteout(dir, file)
		default:
			return errors.Errorf("unknown whiteout mode %d", te.whiteoutMode)
		}
	}

	// Get information about the path. This has to be done after we've dealt
	// with whiteouts because it turns out that lstat(2) will return EPERM if
	// you try to stat a whiteout on AUFS.
	fi, err := te.fsEval.Lstat(path)
	if err != nil {
		// File doesn't exist, just switch fi to the file header.
		fi = hdr.FileInfo()
	}

	// Attempt to create the parent directory of the path we're unpacking.
	// We do a MkdirAll here because even though you need to have a tar entry
	// for every component of a new path, applyMetadata will correct any
	// inconsistencies.
	// FIXME: We have to make this consistent, since if the tar archive doesn't
	//        have entries for some of these components we won't be able to
	//        verify that we have consistent results during unpacking.
	if err := te.fsEval.MkdirAll(dir, 0777); err != nil {
		return errors.Wrap(err, "mkdir parent")
	}

	isDirlink := false
	// We remove whatever existed at the old path to clobber it so that
	// creating a new path will not break. The only exception is if the path is
	// a directory in both the layer and the current filesystem, in which case
	// we don't delete it for obvious reasons. In all other cases we clobber.
	//
	// Note that this will cause hard-links in the "lower" layer to not be able
	// to point to "upper" layer inodes even if the extracted type is the same
	// as the old one, however it is not clear whether this is something a user
	// would expect anyway. In addition, this will incorrectly deal with a
	// TarLink that is present before the "upper" entry in the layer but the
	// "lower" file still exists (so the hard-link would point to the old
	// inode). It's not clear if such an archive is actually valid though.
	if !fi.IsDir() || hdr.Typeflag != tar.TypeDir {
		// If we are in --keep-dirlinks mode and the existing fs object is a
		// symlink to a directory (with the pending object is a directory), we
		// don't remove the symlink (and instead allow subsequent objects to be
		// just written through the symlink into the directory). This is a very
		// specific usecase where layers that were generated independently from
		// each other (on different base filesystems) end up with weird things
		// like /lib64 being a symlink only sometimes but you never want to
		// delete libraries (not just the ones that were under the "real"
		// directory).
		//
		// TODO: This code should also handle a pending symlink entry where the
		//       existing object is a directory. I'm not sure how we could
		//       disambiguate this from a symlink-to-a-file but I imagine that
		//       this is something that would also be useful in the same vein
		//       as --keep-dirlinks (which currently only prevents clobbering
		//       in the opposite case).
		if te.keepDirlinks &&
			fi.Mode()&os.ModeSymlink == os.ModeSymlink && hdr.Typeflag == tar.TypeDir {
			isDirlink, err = te.isDirlink(root, path)
			if err != nil {
				return errors.Wrap(err, "check is dirlink")
			}
		}
		if !(isDirlink && te.keepDirlinks) {
			if err := te.fsEval.RemoveAll(path); err != nil {
				return errors.Wrap(err, "clobber old path")
			}
		}
	}

	// Now create or otherwise modify the state of the path. Right now, either
	// the type of path matches hdr or the path doesn't exist. Note that we
	// don't care about umasks or the initial mode here, since applyMetadata
	// will fix all of that for us.
	switch hdr.Typeflag {
	// regular file
	case tar.TypeReg, tar.TypeRegA:
		// Create a new file, then just copy the data.
		fh, err := te.fsEval.Create(path)
		if err != nil {
			return errors.Wrap(err, "create regular")
		}
		defer fh.Close()

		// We need to make sure that we copy all of the bytes.
		n, err := io.Copy(fh, r)
		if int64(n) != hdr.Size {
			if err != nil {
				err = errors.Wrapf(err, "short write")
			} else {
				err = io.ErrShortWrite
			}
		}
		if err != nil {
			return errors.Wrap(err, "unpack to regular file")
		}

		// Force close here so that we don't affect the metadata.
		if err := fh.Close(); err != nil {
			return errors.Wrap(err, "close unpacked regular file")
		}

	// directory
	case tar.TypeDir:
		if isDirlink {
			break
		}

		// Attempt to create the directory. We do a MkdirAll here because even
		// though you need to have a tar entry for every component of a new
		// path, applyMetadata will correct any inconsistencies.
		if err := te.fsEval.MkdirAll(path, 0777); err != nil {
			return errors.Wrap(err, "mkdirall")
		}

	// hard link, symbolic link
	case tar.TypeLink, tar.TypeSymlink:
		linkname := hdr.Linkname

		// Hardlinks and symlinks act differently when it comes to the scoping.
		// In both cases, we have to just unlink and then re-link the given
		// path. But the function used and the argument are slightly different.
		var linkFn func(string, string) error
		switch hdr.Typeflag {
		case tar.TypeLink:
			linkFn = te.fsEval.Link
			// Because hardlinks are inode-based we need to scope the link to
			// the rootfs using SecureJoinVFS. As before, we need to be careful
			// that we don't resolve the last part of the link path (in case
			// the user actually wanted to hardlink to a symlink).
			unsafeLinkDir, linkFile := filepath.Split(CleanPath(linkname))
			linkDir, err := securejoin.SecureJoinVFS(root, unsafeLinkDir, te.fsEval)
			if err != nil {
				return errors.Wrap(err, "sanitise hardlink target in root")
			}
			linkname = filepath.Join(linkDir, linkFile)
		case tar.TypeSymlink:
			linkFn = te.fsEval.Symlink
		}

		// Link the new one.
		if err := linkFn(linkname, path); err != nil {
			// FIXME: Currently this can break if tar hardlink entries occur
			//        before we hit the entry those hardlinks link to. I have a
			//        feeling that such archives are invalid, but the correct
			//        way of handling this is to delay link creation until the
			//        very end. Unfortunately this won't work with symlinks
			//        (which can link to directories).
			return errors.Wrap(err, "link")
		}

	// character device node, block device node
	case tar.TypeChar, tar.TypeBlock:
		// In rootless mode we have no choice but to fake this, since mknod(2)
		// doesn't work as an unprivileged user here.
		//
		// TODO: We need to add the concept of a fake block device in
		//       "user.rootlesscontainers", because this workaround suffers
		//       from the obvious issue that if the file is touched (even the
		//       metadata) then it will be incorrectly copied into the layer.
		//       This would break distribution images fairly badly.
		if te.partialRootless {
			log.Warnf("rootless{%s} creating empty file in place of device %d:%d", hdr.Name, hdr.Devmajor, hdr.Devminor)
			fh, err := te.fsEval.Create(path)
			if err != nil {
				return errors.Wrap(err, "create rootless block")
			}
			defer fh.Close()
			if err := fh.Chmod(0); err != nil {
				return errors.Wrap(err, "chmod 0 rootless block")
			}
			goto out
		}

		// Otherwise the handling is the same as a FIFO.
		fallthrough
	// fifo node
	case tar.TypeFifo:
		// We have to remove and then create the device. In the FIFO case we
		// could choose not to do so, but we do it anyway just to be on the
		// safe side.

		mode := system.Tarmode(hdr.Typeflag)
		dev := unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor))

		// Create the node.
		if err := te.fsEval.Mknod(path, os.FileMode(int64(mode)|hdr.Mode), dev); err != nil {
			return errors.Wrap(err, "mknod")
		}

	// We should never hit any other headers (Go abstracts them away from us),
	// and we can't handle any custom Tar extensions. So just error out.
	default:
		return fmt.Errorf("unpack entry: %s: unknown typeflag '\\x%x'", hdr.Name, hdr.Typeflag)
	}

out:
	// Apply the metadata, which will apply any mappings necessary. We don't
	// apply metadata for hardlinks, because hardlinks don't have any separate
	// metadata from their link (and the tar headers might not be filled).
	if hdr.Typeflag != tar.TypeLink {
		if err := te.applyMetadata(path, hdr); err != nil {
			return errors.Wrap(err, "apply hdr metadata")
		}
	}

	// Everything is done -- the path now exists. Add it (and all its
	// ancestors) to the set of upper paths. We first have to figure out the
	// proper path corresponding to hdr.Name though.
	upperPath, err := filepath.Rel(root, path)
	if err != nil {
		// Really shouldn't happen because of the guarantees of SecureJoinVFS.
		return errors.Wrap(err, "find relative-to-root [should never happen]")
	}
	for pth := upperPath; pth != filepath.Dir(pth); pth = filepath.Dir(pth) {
		te.upperPaths[pth] = struct{}{}
	}
	return nil
}

--- a/oci/layer/tar_extract.go
+++ b/oci/layer/tar_extract.go
@@ -17,6 +17,11 @@
 	if filepath.Join("/", hdr.Name) == "/" {
 		// If we got an entry for the root, then unsafeDir is the full path.
 		unsafeDir, file = hdr.Name, "."
+		// If we're being asked to change the root type, bail because they may
+		// change it to a symlink which we could inadvertently follow.
+		if hdr.Typeflag != tar.TypeDir {
+			return errors.New("malicious tar entry -- refusing to change type of root directory")
+		}
 	}
 	dir, err := securejoin.SecureJoinVFS(root, unsafeDir, te.fsEval)
 	if err != nil {


Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') vulnerability in Apache Superset. Specifically, certain engine-specific functions are not checked, which allows attackers to bypass Apache Superset's SQL authorization. This issue is a follow-up to CVE-2024-39887 with additional disallowed PostgreSQL functions now included: query_to_xml_and_xmlschema, table_to_xml, table_to_xml_and_xmlschema.

This issue affects Apache Superset: <4.1.0.

Users are recommended to upgrade to version 4.1.0, which fixes the issue or add these Postgres functions to the config set DISALLOWED_SQL_FUNCTIONS.

The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.

DISALLOWED_SQL_FUNCTIONS: dict[str, set[str]] = {
    "postgresql": {"version", "query_to_xml", "inet_server_addr", "inet_client_addr"},
    "clickhouse": {"url"},
    "mysql": {"version"},
}

DISALLOWED_SQL_FUNCTIONS: dict[str, set[str]] = {
    "postgresql": {
        "database_to_xml",
        "inet_client_addr",
        "inet_server_addr",
        "query_to_xml",
        "query_to_xml_and_xmlschema",
        "table_to_xml",
        "table_to_xml_and_xmlschema",
        "version",
    },
    "clickhouse": {"url"},
    "mysql": {"version"},
}

--- a/superset/config.py
+++ b/superset/config.py
@@ -1,5 +1,14 @@
 DISALLOWED_SQL_FUNCTIONS: dict[str, set[str]] = {
-    "postgresql": {"version", "query_to_xml", "inet_server_addr", "inet_client_addr"},
+    "postgresql": {
+        "database_to_xml",
+        "inet_client_addr",
+        "inet_server_addr",
+        "query_to_xml",
+        "query_to_xml_and_xmlschema",
+        "table_to_xml",
+        "table_to_xml_and_xmlschema",
+        "version",
+    },
     "clickhouse": {"url"},
     "mysql": {"version"},
 }


NATS nats-server before 2.7.2 has Incorrect Access Control. Any authenticated user can obtain the privileges of the System account by misusing the "dynamically provisioned sandbox accounts" feature.

Missing Authorization

The product does not perform an authorization check when an actor attempts to access a resource or perform an action.

func (o *consumer) needFlowControl() bool {
	if o.maxpb == 0 {
		return false
	}
	// Decide whether to send a flow control message which we will need the user to respond.
	// We send when we are over 50% of our current window limit.
	if o.fcid == _EMPTY_ && o.pbytes > o.maxpb/2 {
		return true
	}
	return false
}

func (o *consumer) needFlowControl(sz int) bool {
	if o.maxpb == 0 {
		return false
	}
	// Decide whether to send a flow control message which we will need the user to respond.
	// We send when we are over 50% of our current window limit.
	if o.fcid == _EMPTY_ && o.pbytes > o.maxpb/2 {
		return true
	}
	// If we have an existing outstanding FC, check to see if we need to expand the o.fcsz
	if o.fcid != _EMPTY_ && (o.pbytes-o.fcsz) >= o.maxpb {
		o.fcsz += sz
	}
	return false
}

func (o *consumer) deliverMsg(dsubj, subj string, hdr, msg []byte, seq, dc uint64, ts int64) {
	if o.mset == nil {
		return
	}
	// Update pending on first attempt. This can go upside down for a short bit, that is ok.
	// See adjustedPending().
	if dc == 1 {
		o.sgap--
	}

	dseq := o.dseq
	o.dseq++

	// If headers only do not send msg payload.
	// Add in msg size itself as header.
	if o.cfg.HeadersOnly {
		var bb bytes.Buffer
		if len(hdr) == 0 {
			bb.WriteString(hdrLine)
		} else {
			bb.Write(hdr)
			bb.Truncate(len(hdr) - LEN_CR_LF)
		}
		bb.WriteString(JSMsgSize)
		bb.WriteString(": ")
		bb.WriteString(strconv.FormatInt(int64(len(msg)), 10))
		bb.WriteString(CR_LF)
		bb.WriteString(CR_LF)
		hdr = bb.Bytes()
		// Cancel msg payload
		msg = nil
	}

	pmsg := newJSPubMsg(dsubj, subj, o.ackReply(seq, dseq, dc, ts, o.adjustedPending()), hdr, msg, o, seq)
	psz := pmsg.size()

	if o.maxpb > 0 {
		o.pbytes += psz
	}

	mset := o.mset
	ap := o.cfg.AckPolicy

	// Send message.
	o.outq.send(pmsg)

	if ap == AckExplicit || ap == AckAll {
		o.trackPending(seq, dseq)
	} else if ap == AckNone {
		o.adflr = dseq
		o.asflr = seq
	}

	// Flow control.
	if o.maxpb > 0 && o.needFlowControl(psz) {
		o.sendFlowControl()
	}

	// FIXME(dlc) - Capture errors?
	o.updateDelivered(dseq, seq, dc, ts)

	// If we are ack none and mset is interest only we should make sure stream removes interest.
	if ap == AckNone && mset.cfg.Retention != LimitsPolicy {
		if o.node == nil || o.cfg.Direct {
			mset.ackq.push(seq)
		} else {
			o.updateAcks(dseq, seq)
		}
	}
}

--- a/server/consumer.go
+++ b/server/consumer.go
@@ -1,4 +1,4 @@
-func (o *consumer) needFlowControl() bool {
+func (o *consumer) needFlowControl(sz int) bool {
 	if o.maxpb == 0 {
 		return false
 	}
@@ -7,5 +7,9 @@
 	if o.fcid == _EMPTY_ && o.pbytes > o.maxpb/2 {
 		return true
 	}
+	// If we have an existing outstanding FC, check to see if we need to expand the o.fcsz
+	if o.fcid != _EMPTY_ && (o.pbytes-o.fcsz) >= o.maxpb {
+		o.fcsz += sz
+	}
 	return false
 }

--- /dev/null
+++ b/server/consumer.go
@@ -0,0 +1,70 @@
+func (o *consumer) deliverMsg(dsubj, subj string, hdr, msg []byte, seq, dc uint64, ts int64) {
+	if o.mset == nil {
+		return
+	}
+	// Update pending on first attempt. This can go upside down for a short bit, that is ok.
+	// See adjustedPending().
+	if dc == 1 {
+		o.sgap--
+	}
+
+	dseq := o.dseq
+	o.dseq++
+
+	// If headers only do not send msg payload.
+	// Add in msg size itself as header.
+	if o.cfg.HeadersOnly {
+		var bb bytes.Buffer
+		if len(hdr) == 0 {
+			bb.WriteString(hdrLine)
+		} else {
+			bb.Write(hdr)
+			bb.Truncate(len(hdr) - LEN_CR_LF)
+		}
+		bb.WriteString(JSMsgSize)
+		bb.WriteString(": ")
+		bb.WriteString(strconv.FormatInt(int64(len(msg)), 10))
+		bb.WriteString(CR_LF)
+		bb.WriteString(CR_LF)
+		hdr = bb.Bytes()
+		// Cancel msg payload
+		msg = nil
+	}
+
+	pmsg := newJSPubMsg(dsubj, subj, o.ackReply(seq, dseq, dc, ts, o.adjustedPending()), hdr, msg, o, seq)
+	psz := pmsg.size()
+
+	if o.maxpb > 0 {
+		o.pbytes += psz
+	}
+
+	mset := o.mset
+	ap := o.cfg.AckPolicy
+
+	// Send message.
+	o.outq.send(pmsg)
+
+	if ap == AckExplicit || ap == AckAll {
+		o.trackPending(seq, dseq)
+	} else if ap == AckNone {
+		o.adflr = dseq
+		o.asflr = seq
+	}
+
+	// Flow control.
+	if o.maxpb > 0 && o.needFlowControl(psz) {
+		o.sendFlowControl()
+	}
+
+	// FIXME(dlc) - Capture errors?
+	o.updateDelivered(dseq, seq, dc, ts)
+
+	// If we are ack none and mset is interest only we should make sure stream removes interest.
+	if ap == AckNone && mset.cfg.Retention != LimitsPolicy {
+		if o.node == nil || o.cfg.Direct {
+			mset.ackq.push(seq)
+		} else {
+			o.updateAcks(dseq, seq)
+		}
+	}
+}

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

cd /workspace/nats-server
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -timeout 30s -run ^TestJetStreamFlowControlStall$ github.com/nats-io/nats-server/v2/server

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

cd /workspace/nats-server
git apply --whitespace=nowarn /workspace/fix.patch
go test  -timeout 30s -run ^TestJetStreamFlowControlRequiresHeartbeats$ github.com/nats-io/nats-server/v2/server


GitPython is a python library used to interact with Git repositories. In order to resolve some git references, GitPython reads files from the `.git` directory, in some places the name of the file being read is provided by the user, GitPython doesn't check if this file is located outside the `.git` directory. This allows an attacker to make GitPython read any file from the system. This vulnerability is present in https://github.com/gitpython-developers/GitPython/blob/1c8310d7cae144f74a671cbe17e51f63a830adbf/git/refs/symbolic.py#L174-L175. That code joins the base directory with a user given string without checking if the final path is located outside the base directory. This vulnerability cannot be used to read the contents of files but could in theory be used to trigger a denial of service for the program. This issue has been addressed in version 3.1.37.

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 _get_ref_info_helper(
        cls, repo: "Repo", ref_path: Union[PathLike, None]
    ) -> Union[Tuple[str, None], Tuple[None, str]]:
        """Return: (str(sha), str(target_ref_path)) if available, the sha the file at
        rela_path points to, or None. target_ref_path is the reference we
        point to, or None"""
        if ".." in str(ref_path):
            raise ValueError(f"Invalid reference '{ref_path}'")
        tokens: Union[None, List[str], Tuple[str, str]] = None
        repodir = _git_dir(repo, ref_path)
        try:
            with open(os.path.join(repodir, str(ref_path)), "rt", encoding="UTF-8") as fp:
                value = fp.read().rstrip()
            # Don't only split on spaces, but on whitespace, which allows to parse lines like
            # 60b64ef992065e2600bfef6187a97f92398a9144                branch 'master' of git-server:/path/to/repo
            tokens = value.split()
            assert len(tokens) != 0
        except OSError:
            # Probably we are just packed, find our entry in the packed refs file
            # NOTE: We are not a symbolic ref if we are in a packed file, as these
            # are excluded explicitly
            for sha, path in cls._iter_packed_refs(repo):
                if path != ref_path:
                    continue
                # sha will be used
                tokens = sha, path
                break
            # END for each packed ref
        # END handle packed refs
        if tokens is None:
            raise ValueError("Reference at %r does not exist" % ref_path)

        # is it a reference ?
        if tokens[0] == "ref:":
            return (None, tokens[1])

        # its a commit
        if repo.re_hexsha_only.match(tokens[0]):
            return (tokens[0], None)

        raise ValueError("Failed to parse reference information from %r" % ref_path)

    def _get_ref_info_helper(
        cls, repo: "Repo", ref_path: Union[PathLike, None]
    ) -> Union[Tuple[str, None], Tuple[None, str]]:
        """Return: (str(sha), str(target_ref_path)) if available, the sha the file at
        rela_path points to, or None. target_ref_path is the reference we
        point to, or None"""
        if ref_path:
            cls._check_ref_name_valid(ref_path)

        tokens: Union[None, List[str], Tuple[str, str]] = None
        repodir = _git_dir(repo, ref_path)
        try:
            with open(os.path.join(repodir, str(ref_path)), "rt", encoding="UTF-8") as fp:
                value = fp.read().rstrip()
            # Don't only split on spaces, but on whitespace, which allows to parse lines like
            # 60b64ef992065e2600bfef6187a97f92398a9144                branch 'master' of git-server:/path/to/repo
            tokens = value.split()
            assert len(tokens) != 0
        except OSError:
            # Probably we are just packed, find our entry in the packed refs file
            # NOTE: We are not a symbolic ref if we are in a packed file, as these
            # are excluded explicitly
            for sha, path in cls._iter_packed_refs(repo):
                if path != ref_path:
                    continue
                # sha will be used
                tokens = sha, path
                break
            # END for each packed ref
        # END handle packed refs
        if tokens is None:
            raise ValueError("Reference at %r does not exist" % ref_path)

    @staticmethod
    def _check_ref_name_valid(ref_path: PathLike) -> None:
        # Based on the rules described in https://git-scm.com/docs/git-check-ref-format/#_description
        previous: Union[str, None] = None
        one_before_previous: Union[str, None] = None
        for c in str(ref_path):
            if c in " ~^:?*[\\":
                raise ValueError(
                    f"Invalid reference '{ref_path}': references cannot contain spaces, tildes (~), carets (^),"
                    f" colons (:), question marks (?), asterisks (*), open brackets ([) or backslashes (\\)"
                )
            elif c == ".":
                if previous is None or previous == "/":
                    raise ValueError(
                        f"Invalid reference '{ref_path}': references cannot start with a period (.) or contain '/.'"
                    )
                elif previous == ".":
                    raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '..'")
            elif c == "/":
                if previous == "/":
                    raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '//'")
                elif previous is None:
                    raise ValueError(
                        f"Invalid reference '{ref_path}': references cannot start with forward slashes '/'"
                    )
            elif c == "{" and previous == "@":
                raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '@{{'")
            elif ord(c) < 32 or ord(c) == 127:
                raise ValueError(f"Invalid reference '{ref_path}': references cannot contain ASCII control characters")

            one_before_previous = previous
            previous = c

        if previous == ".":
            raise ValueError(f"Invalid reference '{ref_path}': references cannot end with a period (.)")
        elif previous == "/":
            raise ValueError(f"Invalid reference '{ref_path}': references cannot end with a forward slash (/)")
        elif previous == "@" and one_before_previous is None:
            raise ValueError(f"Invalid reference '{ref_path}': references cannot be '@'")
        elif any([component.endswith(".lock") for component in str(ref_path).split("/")]):
            raise ValueError(
                f"Invalid reference '{ref_path}': references cannot have slash-separated components that end with"
                f" '.lock'"
            )

--- a/git/refs/symbolic.py
+++ b/git/refs/symbolic.py
@@ -4,8 +4,9 @@
         """Return: (str(sha), str(target_ref_path)) if available, the sha the file at
         rela_path points to, or None. target_ref_path is the reference we
         point to, or None"""
-        if ".." in str(ref_path):
-            raise ValueError(f"Invalid reference '{ref_path}'")
+        if ref_path:
+            cls._check_ref_name_valid(ref_path)
+
         tokens: Union[None, List[str], Tuple[str, str]] = None
         repodir = _git_dir(repo, ref_path)
         try:
@@ -29,13 +30,3 @@
         # END handle packed refs
         if tokens is None:
             raise ValueError("Reference at %r does not exist" % ref_path)
-
-        # is it a reference ?
-        if tokens[0] == "ref:":
-            return (None, tokens[1])
-
-        # its a commit
-        if repo.re_hexsha_only.match(tokens[0]):
-            return (tokens[0], None)
-
-        raise ValueError("Failed to parse reference information from %r" % ref_path)

--- /dev/null
+++ b/git/refs/symbolic.py
@@ -0,0 +1,44 @@
+    @staticmethod
+    def _check_ref_name_valid(ref_path: PathLike) -> None:
+        # Based on the rules described in https://git-scm.com/docs/git-check-ref-format/#_description
+        previous: Union[str, None] = None
+        one_before_previous: Union[str, None] = None
+        for c in str(ref_path):
+            if c in " ~^:?*[\\":
+                raise ValueError(
+                    f"Invalid reference '{ref_path}': references cannot contain spaces, tildes (~), carets (^),"
+                    f" colons (:), question marks (?), asterisks (*), open brackets ([) or backslashes (\\)"
+                )
+            elif c == ".":
+                if previous is None or previous == "/":
+                    raise ValueError(
+                        f"Invalid reference '{ref_path}': references cannot start with a period (.) or contain '/.'"
+                    )
+                elif previous == ".":
+                    raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '..'")
+            elif c == "/":
+                if previous == "/":
+                    raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '//'")
+                elif previous is None:
+                    raise ValueError(
+                        f"Invalid reference '{ref_path}': references cannot start with forward slashes '/'"
+                    )
+            elif c == "{" and previous == "@":
+                raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '@{{'")
+            elif ord(c) < 32 or ord(c) == 127:
+                raise ValueError(f"Invalid reference '{ref_path}': references cannot contain ASCII control characters")
+
+            one_before_previous = previous
+            previous = c
+
+        if previous == ".":
+            raise ValueError(f"Invalid reference '{ref_path}': references cannot end with a period (.)")
+        elif previous == "/":
+            raise ValueError(f"Invalid reference '{ref_path}': references cannot end with a forward slash (/)")
+        elif previous == "@" and one_before_previous is None:
+            raise ValueError(f"Invalid reference '{ref_path}': references cannot be '@'")
+        elif any([component.endswith(".lock") for component in str(ref_path).split("/")]):
+            raise ValueError(
+                f"Invalid reference '{ref_path}': references cannot have slash-separated components that end with"
+                f" '.lock'"
+            )

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

cd /workspace/GitPython
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
/workspace/PoC_env/CVE-2023-41040/bin/python -m pytest test/test_refs.py::TestRefs::test_validity_ref_names

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

cd /workspace/GitPython
git apply --whitespace=nowarn /workspace/fix.patch
/workspace/PoC_env/CVE-2023-41040/bin/python -m pytest test/test_refs.py -k "not test_head_checkout_detached_head and not test_is_valid and not test_tag_message and not test_heads and not test_head_reset and not test_tags_author and not test_tag_base" --deselect=test/test_refs.py::TestRefs::test_refs -p no:warning --disable-warnings


gnome-shell 3.22 through 3.24.1 mishandles extensions that fail to reload, which can lead to leaving extensions enabled in the lock screen. With these extensions, a bystander could launch applications (but not interact with them), see information from the extensions (e.g., what applications you have opened or what music you were playing), or even execute arbitrary commands. It all depends on what extensions a user has enabled. The problem is caused by lack of exception handling in js/ui/extensionSystem.js.

function _onVersionValidationChanged() {
    // we want to reload all extensions, but only enable
    // extensions when allowed by the sessionMode, so
    // temporarily disable them all
    enabledExtensions = [];
    for (let uuid in ExtensionUtils.extensions)
        reloadExtension(ExtensionUtils.extensions[uuid]);
    enabledExtensions = getEnabledExtensions();

    if (Main.sessionMode.allowExtensions) {
        enabledExtensions.forEach(function(uuid) {
            enableExtension(uuid);
        });
    }
}

function _onVersionValidationChanged() {
    // we want to reload all extensions, but only enable
    // extensions when allowed by the sessionMode, so
    // temporarily disable them all
    enabledExtensions = [];
    for (let uuid in ExtensionUtils.extensions)
        try {
            reloadExtension(ExtensionUtils.extensions[uuid]);
        } catch(e) {
            logExtensionError(uuid, e);
        }
    enabledExtensions = getEnabledExtensions();

    if (Main.sessionMode.allowExtensions) {
        enabledExtensions.forEach(function(uuid) {
            try {
                enableExtension(uuid);
            } catch(e) {
                logExtensionError(uuid, e);
            }
        });
    }
}

--- a/js/ui/extensionSystem.js
+++ b/js/ui/extensionSystem.js
@@ -4,12 +4,20 @@
     // temporarily disable them all
     enabledExtensions = [];
     for (let uuid in ExtensionUtils.extensions)
-        reloadExtension(ExtensionUtils.extensions[uuid]);
+        try {
+            reloadExtension(ExtensionUtils.extensions[uuid]);
+        } catch(e) {
+            logExtensionError(uuid, e);
+        }
     enabledExtensions = getEnabledExtensions();

     if (Main.sessionMode.allowExtensions) {
         enabledExtensions.forEach(function(uuid) {
-            enableExtension(uuid);
+            try {
+                enableExtension(uuid);
+            } catch(e) {
+                logExtensionError(uuid, e);
+            }
         });
     }
 }


Go before 1.17.10 and 1.18.x before 1.18.2 has Incorrect Privilege Assignment. When called with a non-zero flags parameter, the Faccessat function could incorrectly report that a file is accessible.

Improper Privilege Management

The product does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor.

func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) {
	if flags & ^(_AT_SYMLINK_NOFOLLOW|_AT_EACCESS) != 0 {
		return EINVAL
	}

	// The Linux kernel faccessat system call does not take any flags.
	// The glibc faccessat implements the flags itself; see
	// https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/faccessat.c;hb=HEAD
	// Because people naturally expect syscall.Faccessat to act
	// like C faccessat, we do the same.

	if flags == 0 {
		return faccessat(dirfd, path, mode)
	}

	var st Stat_t
	if err := fstatat(dirfd, path, &st, flags&_AT_SYMLINK_NOFOLLOW); err != nil {
		return err
	}

	mode &= 7
	if mode == 0 {
		return nil
	}

	var uid int
	if flags&_AT_EACCESS != 0 {
		uid = Geteuid()
	} else {
		uid = Getuid()
	}

	if uid == 0 {
		if mode&1 == 0 {
			// Root can read and write any file.
			return nil
		}
		if st.Mode&0111 != 0 {
			// Root can execute any file that anybody can execute.
			return nil
		}
		return EACCES
	}

	var fmode uint32
	if uint32(uid) == st.Uid {
		fmode = (st.Mode >> 6) & 7
	} else {
		var gid int
		if flags&_AT_EACCESS != 0 {
			gid = Getegid()
		} else {
			gid = Getgid()
		}

		if uint32(gid) == st.Gid || isGroupMember(gid) {
			fmode = (st.Mode >> 3) & 7
		} else {
			fmode = st.Mode & 7
		}
	}

	if fmode&mode == mode {
		return nil
	}

	return EACCES
}

func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) {
	if flags & ^(_AT_SYMLINK_NOFOLLOW|_AT_EACCESS) != 0 {
		return EINVAL
	}

	// The Linux kernel faccessat system call does not take any flags.
	// The glibc faccessat implements the flags itself; see
	// https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/faccessat.c;hb=HEAD
	// Because people naturally expect syscall.Faccessat to act
	// like C faccessat, we do the same.

	if flags == 0 {
		return faccessat(dirfd, path, mode)
	}

	var st Stat_t
	if err := fstatat(dirfd, path, &st, flags&_AT_SYMLINK_NOFOLLOW); err != nil {
		return err
	}

	mode &= 7
	if mode == 0 {
		return nil
	}

	var uid int
	if flags&_AT_EACCESS != 0 {
		uid = Geteuid()
	} else {
		uid = Getuid()
	}

	if uid == 0 {
		if mode&1 == 0 {
			// Root can read and write any file.
			return nil
		}
		if st.Mode&0111 != 0 {
			// Root can execute any file that anybody can execute.
			return nil
		}
		return EACCES
	}

	var fmode uint32
	if uint32(uid) == st.Uid {
		fmode = (st.Mode >> 6) & 7
	} else {
		var gid int
		if flags&_AT_EACCESS != 0 {
			gid = Getegid()
		} else {
			gid = Getgid()
		}

		if uint32(gid) == st.Gid || isGroupMember(int(st.Gid)) {
			fmode = (st.Mode >> 3) & 7
		} else {
			fmode = st.Mode & 7
		}
	}

	if fmode&mode == mode {
		return nil
	}

	return EACCES
}

--- a/src/syscall/syscall_linux.go
+++ b/src/syscall/syscall_linux.go
@@ -53,7 +53,7 @@
 			gid = Getgid()
 		}

-		if uint32(gid) == st.Gid || isGroupMember(gid) {
+		if uint32(gid) == st.Gid || isGroupMember(int(st.Gid)) {
 			fmode = (st.Mode >> 3) & 7
 		} else {
 			fmode = st.Mode & 7


With the following crawler configuration:

```python
from bs4 import BeautifulSoup as Soup

url = "https://example.com"
loader = RecursiveUrlLoader(
    url=url, max_depth=2, extractor=lambda x: Soup(x, "html.parser").text
)
docs = loader.load()
```

An attacker in control of the contents of `https://example.com` could place a malicious HTML file in there with links like "https://example.completely.different/my_file.html" and the crawler would proceed to download that file as well even though `prevent_outside=True`.

https://github.com/langchain-ai/langchain/blob/bf0b3cc0b5ade1fb95a5b1b6fa260e99064c2e22/libs/community/langchain_community/document_loaders/recursive_url_loader.py#L51-L51

Resolved in https://github.com/langchain-ai/langchain/pull/15559

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 extract_sub_links(
    raw_html: str,
    url: str,
    *,
    base_url: Optional[str] = None,
    pattern: Union[str, re.Pattern, None] = None,
    prevent_outside: bool = True,
    exclude_prefixes: Sequence[str] = (),
) -> List[str]:
    """Extract all links from a raw html string and convert into absolute paths.

    Args:
        raw_html: original html.
        url: the url of the html.
        base_url: the base url to check for outside links against.
        pattern: Regex to use for extracting links from raw html.
        prevent_outside: If True, ignore external links which are not children
            of the base url.
        exclude_prefixes: Exclude any URLs that start with one of these prefixes.

    Returns:
        List[str]: sub links
    """
    base_url = base_url if base_url is not None else url
    all_links = find_all_links(raw_html, pattern=pattern)
    absolute_paths = set()
    for link in all_links:
        # Some may be absolute links like https://to/path
        if link.startswith("http"):
            absolute_paths.add(link)
        # Some may have omitted the protocol like //to/path
        elif link.startswith("//"):
            absolute_paths.add(f"{urlparse(url).scheme}:{link}")
        else:
            absolute_paths.add(urljoin(url, link))
    res = []
    for path in absolute_paths:
        if any(path.startswith(exclude) for exclude in exclude_prefixes):
            continue
        if prevent_outside and not path.startswith(base_url):
            continue
        res.append(path)
    return res

def extract_sub_links(
    raw_html: str,
    url: str,
    *,
    base_url: Optional[str] = None,
    pattern: Union[str, re.Pattern, None] = None,
    prevent_outside: bool = True,
    exclude_prefixes: Sequence[str] = (),
) -> List[str]:
    """Extract all links from a raw html string and convert into absolute paths.

    Args:
        raw_html: original html.
        url: the url of the html.
        base_url: the base url to check for outside links against.
        pattern: Regex to use for extracting links from raw html.
        prevent_outside: If True, ignore external links which are not children
            of the base url.
        exclude_prefixes: Exclude any URLs that start with one of these prefixes.

    Returns:
        List[str]: sub links
    """
    base_url_to_use = base_url if base_url is not None else url
    parsed_base_url = urlparse(base_url_to_use)
    all_links = find_all_links(raw_html, pattern=pattern)
    absolute_paths = set()
    for link in all_links:
        parsed_link = urlparse(link)
        # Some may be absolute links like https://to/path
        if parsed_link.scheme == "http" or parsed_link.scheme == "https":
            absolute_path = link
        # Some may have omitted the protocol like //to/path
        elif link.startswith("//"):
            absolute_path = f"{urlparse(url).scheme}:{link}"
        else:
            absolute_path = urljoin(url, parsed_link.path)
        absolute_paths.add(absolute_path)

    results = []
    for path in absolute_paths:
        if any(path.startswith(exclude_prefix) for exclude_prefix in exclude_prefixes):
            continue

        if prevent_outside:
            parsed_path = urlparse(path)

            if parsed_base_url.netloc != parsed_path.netloc:
                continue

            # Will take care of verifying rest of path after netloc
            # if it's more specific
            if not path.startswith(base_url_to_use):
                continue

        results.append(path)
    return results

--- a/libs/core/langchain_core/utils/html.py
+++ b/libs/core/langchain_core/utils/html.py
@@ -21,23 +21,37 @@
     Returns:
         List[str]: sub links
     """
-    base_url = base_url if base_url is not None else url
+    base_url_to_use = base_url if base_url is not None else url
+    parsed_base_url = urlparse(base_url_to_use)
     all_links = find_all_links(raw_html, pattern=pattern)
     absolute_paths = set()
     for link in all_links:
+        parsed_link = urlparse(link)
         # Some may be absolute links like https://to/path
-        if link.startswith("http"):
-            absolute_paths.add(link)
+        if parsed_link.scheme == "http" or parsed_link.scheme == "https":
+            absolute_path = link
         # Some may have omitted the protocol like //to/path
         elif link.startswith("//"):
-            absolute_paths.add(f"{urlparse(url).scheme}:{link}")
+            absolute_path = f"{urlparse(url).scheme}:{link}"
         else:
-            absolute_paths.add(urljoin(url, link))
-    res = []
+            absolute_path = urljoin(url, parsed_link.path)
+        absolute_paths.add(absolute_path)
+
+    results = []
     for path in absolute_paths:
-        if any(path.startswith(exclude) for exclude in exclude_prefixes):
+        if any(path.startswith(exclude_prefix) for exclude_prefix in exclude_prefixes):
             continue
-        if prevent_outside and not path.startswith(base_url):
-            continue
-        res.append(path)
-    return res
+
+        if prevent_outside:
+            parsed_path = urlparse(path)
+
+            if parsed_base_url.netloc != parsed_path.netloc:
+                continue
+
+            # Will take care of verifying rest of path after netloc
+            # if it's more specific
+            if not path.startswith(base_url_to_use):
+                continue
+
+        results.append(path)
+    return results

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

cd /workspace/langchain
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
cd libs/core && /workspace/PoC_env/CVE-2024-0243/bin/python -m pytest tests/unit_tests/utils/test_html.py -v -o addopts="" -k "test_prevent_outside"

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

cd /workspace/langchain
git apply --whitespace=nowarn  /workspace/fix.patch
cd libs/core && /workspace/PoC_env/CVE-2024-0243/bin/python -m pytest tests/unit_tests/utils/test_html.py -v -o addopts=""


Langchain before v0.0.225 was discovered to contain a remote code execution (RCE) vulnerability in the component JiraAPIWrapper (aka the JIRA API wrapper). This vulnerability allows attackers to execute arbitrary code via crafted input. As noted in the "releases/tag" reference, a fix is available.

    def other(self, query: str) -> str:
        context = {"self": self}
        exec(f"result = {query}", context)
        result = context["result"]
        return str(result)

    def other(self, query: str) -> str:
        try:
            import json
        except ImportError:
            raise ImportError(
                "json is not installed. Please install it with `pip install json`"
            )
        params = json.loads(query)
        jira_function = getattr(self.jira, params["function"])
        return jira_function(*params.get("args", []), **params.get("kwargs", {}))

--- a/langchain/utilities/jira.py
+++ b/langchain/utilities/jira.py
@@ -1,5 +1,10 @@
     def other(self, query: str) -> str:
-        context = {"self": self}
-        exec(f"result = {query}", context)
-        result = context["result"]
-        return str(result)
+        try:
+            import json
+        except ImportError:
+            raise ImportError(
+                "json is not installed. Please install it with `pip install json`"
+            )
+        params = json.loads(query)
+        jira_function = getattr(self.jira, params["function"])
+        return jira_function(*params.get("args", []), **params.get("kwargs", {}))


Authentication is globally bypassed in github.com/nanobox-io/golang-nanoauth between v0.0.0-20160722212129-ac0cc4484ad4 and v0.0.0-20200131131040-063a3fb69896 if ListenAndServe is called with an empty token.

Improper Authentication

When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.

var (
	// DefaultAuth is the default Auth object
	DefaultAuth = &Auth{}

	// whether or not to check auth tokens
	check = true
)

func (self Auth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	reqPath := req.URL.Path
	skipOnce := false

	for _, path := range self.ExcludedPaths {
		if path == reqPath {
			skipOnce = true
			break
		}
	}

	// open up for the CORS "secure" pre-flight check (browser doesn't allow devs to set headers in OPTIONS request)
	if req.Method == "OPTIONS" {
		// todo: actually check origin header to better implement CORS
		skipOnce = true
	}

	if !skipOnce && check {
		auth := ""
		if auth = req.Header.Get(self.Header); auth == "" {
			// check form value (case sensitive) if header not set
			auth = req.FormValue(self.Header)
		}

		if auth != self.Token {
			rw.WriteHeader(http.StatusUnauthorized)
			return
		}
	}

	self.child.ServeHTTP(rw, req)
}

func (self *Auth) ListenAndServeTLS(addr, token string, h http.Handler, excludedPaths ...string) error {
	config := &tls.Config{
		Certificates: []tls.Certificate{*self.Certificate},
	}
	config.BuildNameToCertificate()
	tlsListener, err := tls.Listen("tcp", addr, config)
	if err != nil {
		return err
	}

	if token == "" {
		check = false
	}
	self.ExcludedPaths = excludedPaths
	self.Token = token

	if h == nil {
		h = http.DefaultServeMux
	}
	self.child = h

	return http.Serve(tlsListener, self)
}

func (self *Auth) ListenAndServe(addr, token string, h http.Handler, excludedPaths ...string) error {
	httpListener, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	if token == "" {
		check = false
	}
	self.ExcludedPaths = excludedPaths
	self.Token = token

	if h == nil {
		h = http.DefaultServeMux
	}
	self.child = h

	return http.Serve(httpListener, self)
}

var (
	// DefaultAuth is the default Auth object
	DefaultAuth = &Auth{}
)

func (self *Auth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	reqPath := req.URL.Path
	skipOnce := false

	for _, path := range self.ExcludedPaths {
		if path == reqPath {
			skipOnce = true
			break
		}
	}

	// open up for the CORS "secure" pre-flight check (browser doesn't allow devs to set headers in OPTIONS request)
	if req.Method == "OPTIONS" {
		// todo: actually check origin header to better implement CORS
		skipOnce = true
	}

	if !skipOnce {
		auth := ""
		if auth = req.Header.Get(self.Header); auth == "" {
			// check form value (case sensitive) if header not set
			auth = req.FormValue(self.Header)
		}

		if subtle.ConstantTimeCompare([]byte(auth), []byte(self.Token)) == 0 {
			rw.WriteHeader(http.StatusUnauthorized)
			return
		}
	}

	self.child.ServeHTTP(rw, req)
}

func (self *Auth) ListenAndServeTLS(addr, token string, h http.Handler, excludedPaths ...string) error {
	if token == "" {
		return errors.New("nanoauth: token missing")
	}
	config := &tls.Config{
		Certificates: []tls.Certificate{*self.Certificate},
	}
	config.BuildNameToCertificate()
	tlsListener, err := tls.Listen("tcp", addr, config)
	if err != nil {
		return err
	}

	self.ExcludedPaths = excludedPaths
	self.Token = token

	if h == nil {
		h = http.DefaultServeMux
	}
	self.child = h

	return http.Serve(tlsListener, self)
}

func (self *Auth) ListenAndServe(addr, token string, h http.Handler, excludedPaths ...string) error {
	if token == "" {
		return errors.New("nanoauth: token missing")
	}
	httpListener, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}

	self.ExcludedPaths = excludedPaths
	self.Token = token

	if h == nil {
		h = http.DefaultServeMux
	}
	self.child = h

	return http.Serve(httpListener, self)
}

--- a/nanoauth.go
+++ b/nanoauth.go
@@ -1,7 +1,4 @@
 var (
 	// DefaultAuth is the default Auth object
 	DefaultAuth = &Auth{}
-
-	// whether or not to check auth tokens
-	check = true
 )

--- a/nanoauth.go
+++ b/nanoauth.go
@@ -1,4 +1,4 @@
-func (self Auth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
+func (self *Auth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 	reqPath := req.URL.Path
 	skipOnce := false

@@ -15,14 +15,14 @@
 		skipOnce = true
 	}

-	if !skipOnce && check {
+	if !skipOnce {
 		auth := ""
 		if auth = req.Header.Get(self.Header); auth == "" {
 			// check form value (case sensitive) if header not set
 			auth = req.FormValue(self.Header)
 		}

-		if auth != self.Token {
+		if subtle.ConstantTimeCompare([]byte(auth), []byte(self.Token)) == 0 {
 			rw.WriteHeader(http.StatusUnauthorized)
 			return
 		}

--- a/nanoauth.go
+++ b/nanoauth.go
@@ -1,4 +1,7 @@
 func (self *Auth) ListenAndServeTLS(addr, token string, h http.Handler, excludedPaths ...string) error {
+	if token == "" {
+		return errors.New("nanoauth: token missing")
+	}
 	config := &tls.Config{
 		Certificates: []tls.Certificate{*self.Certificate},
 	}
@@ -8,9 +11,6 @@
 		return err
 	}

-	if token == "" {
-		check = false
-	}
 	self.ExcludedPaths = excludedPaths
 	self.Token = token

--- a/nanoauth.go
+++ b/nanoauth.go
@@ -1,12 +1,12 @@
 func (self *Auth) ListenAndServe(addr, token string, h http.Handler, excludedPaths ...string) error {
+	if token == "" {
+		return errors.New("nanoauth: token missing")
+	}
 	httpListener, err := net.Listen("tcp", addr)
 	if err != nil {
 		return err
 	}

-	if token == "" {
-		check = false
-	}
 	self.ExcludedPaths = excludedPaths
 	self.Token = token


Session Fixation in GitHub repository ikus060/rdiffweb prior to 2.4.7.

Session Fixation

Authenticating a user, or otherwise establishing a new user session, without invalidating any existing session identifier gives an attacker the opportunity to steal authenticated sessions.

    def index(self, **kwargs):
        form = LoginForm()
        # Validate user's credentials
        if form.validate_on_submit():
            try:
                userobj = self.app.store.login(form.login.data, form.password.data)
            except Exception:
                logger.exception('fail to validate credential')
                flash(_("Fail to validate user credential."))
            else:
                if userobj:
                    cherrypy.session[SESSION_KEY] = userobj.username
                    raise cherrypy.HTTPRedirect(form.redirect.data)
                flash(_("Invalid username or password."))

        params = {'form': form}

        # Add welcome message to params. Try to load translated message.
        if self._welcome_msg:
            params["welcome_msg"] = self._welcome_msg.get('')
            if hasattr(cherrypy.response, 'i18n'):
                locale = cherrypy.response.i18n.locale.language
                params["welcome_msg"] = self._welcome_msg.get(locale, params["welcome_msg"])

        return self._compile_template("login.html", **params).encode("utf-8")

    def default(self):
        cherrypy.session[SESSION_KEY] = None
        raise cherrypy.HTTPRedirect('/')

    def index(self, **kwargs):
        form = LoginForm()
        # Validate user's credentials
        if form.validate_on_submit():
            try:
                userobj = self.app.store.login(form.login.data, form.password.data)
            except Exception:
                logger.exception('fail to validate credential')
                flash(_("Fail to validate user credential."))
            else:
                if userobj:
                    cherrypy.session[SESSION_KEY] = userobj.username
                    cherrypy.session.regenerate()
                    raise cherrypy.HTTPRedirect(form.redirect.data)
                flash(_("Invalid username or password."))

        params = {'form': form}

        # Add welcome message to params. Try to load translated message.
        if self._welcome_msg:
            params["welcome_msg"] = self._welcome_msg.get('')
            if hasattr(cherrypy.response, 'i18n'):
                locale = cherrypy.response.i18n.locale.language
                params["welcome_msg"] = self._welcome_msg.get(locale, params["welcome_msg"])

        return self._compile_template("login.html", **params).encode("utf-8")

    def default(self):
        cherrypy.session[SESSION_KEY] = None
        cherrypy.session.regenerate()
        raise cherrypy.HTTPRedirect('/')

--- a/rdiffweb/controller/page_login.py
+++ b/rdiffweb/controller/page_login.py
@@ -10,6 +10,7 @@
             else:
                 if userobj:
                     cherrypy.session[SESSION_KEY] = userobj.username
+                    cherrypy.session.regenerate()
                     raise cherrypy.HTTPRedirect(form.redirect.data)
                 flash(_("Invalid username or password."))

--- a/rdiffweb/controller/page_login.py
+++ b/rdiffweb/controller/page_login.py
@@ -1,3 +1,4 @@
     def default(self):
         cherrypy.session[SESSION_KEY] = None
+        cherrypy.session.regenerate()
         raise cherrypy.HTTPRedirect('/')