Mattermost fails to delete the attachments when deleting a message in a thread allowing a simple user to still be able to access and download the attachment of a deleted message

func (s *SqlPostStore) deleteThread(transaction *sqlxTxWrapper, postId string, deleteAtTime int64) error {
	queryString, args, err := s.getQueryBuilder().
		Update("Threads").
		Set("ThreadDeleteAt", deleteAtTime).
		Where(sq.Eq{"PostId": postId}).
		ToSql()
	if err != nil {
		return errors.Wrapf(err, "failed to create SQL query to mark thread for root post %s as deleted", postId)
	}

	_, err = transaction.Exec(queryString, args...)
	if err != nil {
		return errors.Wrapf(err, "failed to mark thread for root post %s as deleted", postId)
	}

	return nil
}

func (s *SqlPostStore) deleteThread(transaction *sqlxTxWrapper, postId string, deleteAtTime int64) error {
	queryString, args, err := s.getQueryBuilder().
		Update("Threads").
		Set("ThreadDeleteAt", deleteAtTime).
		Where(sq.Eq{"PostId": postId}).
		ToSql()
	if err != nil {
		return errors.Wrapf(err, "failed to create SQL query to mark thread for root post %s as deleted", postId)
	}

	_, err = transaction.Exec(queryString, args...)
	if err != nil {
		return errors.Wrapf(err, "failed to mark thread for root post %s as deleted", postId)
	}

	return s.deleteThreadFiles(transaction, postId, deleteAtTime)
}

func (s *SqlPostStore) deleteThreadFiles(transaction *sqlxTxWrapper, postID string, deleteAtTime int64) error {
	var query sq.UpdateBuilder
	if s.DriverName() == model.DatabaseDriverPostgres {
		query = s.getQueryBuilder().Update("FileInfo").
			Set("DeleteAt", deleteAtTime).
			From("Posts")
	} else {
		query = s.getQueryBuilder().Update("FileInfo", "Posts").
			Set("FileInfo.DeleteAt", deleteAtTime)
	}

	query = query.Where(sq.And{
		sq.Expr("FileInfo.PostId = Posts.Id"),
		sq.Eq{"Posts.RootId": postID},
	})

	_, err := transaction.ExecBuilder(query)
	if err != nil {
		return errors.Wrapf(err, "failed to mark files of thread post %s as deleted", postID)
	}

	return nil
}

--- a/store/sqlstore/post_store.go
+++ b/store/sqlstore/post_store.go
@@ -13,5 +13,5 @@
 		return errors.Wrapf(err, "failed to mark thread for root post %s as deleted", postId)
 	}

-	return nil
+	return s.deleteThreadFiles(transaction, postId, deleteAtTime)
 }

--- /dev/null
+++ b/store/sqlstore/post_store.go
@@ -0,0 +1,23 @@
+func (s *SqlPostStore) deleteThreadFiles(transaction *sqlxTxWrapper, postID string, deleteAtTime int64) error {
+	var query sq.UpdateBuilder
+	if s.DriverName() == model.DatabaseDriverPostgres {
+		query = s.getQueryBuilder().Update("FileInfo").
+			Set("DeleteAt", deleteAtTime).
+			From("Posts")
+	} else {
+		query = s.getQueryBuilder().Update("FileInfo", "Posts").
+			Set("FileInfo.DeleteAt", deleteAtTime)
+	}
+
+	query = query.Where(sq.And{
+		sq.Expr("FileInfo.PostId = Posts.Id"),
+		sq.Eq{"Posts.RootId": postID},
+	})
+
+	_, err := transaction.ExecBuilder(query)
+	if err != nil {
+		return errors.Wrapf(err, "failed to mark files of thread post %s as deleted", postID)
+	}
+
+	return nil
+}


An archive traversal flaw was found in all ansible-engine versions 2.9.x prior to 2.9.7, when running ansible-galaxy collection install. When extracting a collection .tar.gz file, the directory is created without sanitizing the filename. An attacker could take advantage to overwrite any file within the system.

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 install(self, path, b_temp_path):
        if self.skip:
            display.display("Skipping '%s' as it is already installed" % to_text(self))
            return

        # Install if it is not
        collection_path = os.path.join(path, self.namespace, self.name)
        b_collection_path = to_bytes(collection_path, errors='surrogate_or_strict')
        display.display("Installing '%s:%s' to '%s'" % (to_text(self), self.latest_version, collection_path))

        if self.b_path is None:
            download_url = self._metadata.download_url
            artifact_hash = self._metadata.artifact_sha256
            headers = {}
            self.api._add_auth_token(headers, download_url, required=False)

            self.b_path = _download_file(download_url, b_temp_path, artifact_hash, self.api.validate_certs,
                                         headers=headers)

        if os.path.exists(b_collection_path):
            shutil.rmtree(b_collection_path)
        os.makedirs(b_collection_path)

        with tarfile.open(self.b_path, mode='r') as collection_tar:
            files_member_obj = collection_tar.getmember('FILES.json')
            with _tarfile_extract(collection_tar, files_member_obj) as files_obj:
                files = json.loads(to_text(files_obj.read(), errors='surrogate_or_strict'))

            _extract_tar_file(collection_tar, 'MANIFEST.json', b_collection_path, b_temp_path)
            _extract_tar_file(collection_tar, 'FILES.json', b_collection_path, b_temp_path)

            for file_info in files['files']:
                file_name = file_info['name']
                if file_name == '.':
                    continue

                if file_info['ftype'] == 'file':
                    _extract_tar_file(collection_tar, file_name, b_collection_path, b_temp_path,
                                      expected_hash=file_info['chksum_sha256'])
                else:
                    os.makedirs(os.path.join(b_collection_path, to_bytes(file_name, errors='surrogate_or_strict')))

def _extract_tar_file(tar, filename, b_dest, b_temp_path, expected_hash=None):
    n_filename = to_native(filename, errors='surrogate_or_strict')
    try:
        member = tar.getmember(n_filename)
    except KeyError:
        raise AnsibleError("Collection tar at '%s' does not contain the expected file '%s'." % (to_native(tar.name),
                                                                                n_filename))

    with tempfile.NamedTemporaryFile(dir=b_temp_path, delete=False) as tmpfile_obj:
        bufsize = 65536
        sha256_digest = sha256()
        with _tarfile_extract(tar, member) as tar_obj:
            data = tar_obj.read(bufsize)
            while data:
                tmpfile_obj.write(data)
                tmpfile_obj.flush()
                sha256_digest.update(data)
                data = tar_obj.read(bufsize)

        actual_hash = sha256_digest.hexdigest()

        if expected_hash and actual_hash != expected_hash:
            raise AnsibleError("Checksum mismatch for '%s' inside collection at '%s'"
                               % (n_filename, to_native(tar.name)))

        b_dest_filepath = os.path.join(b_dest, to_bytes(filename, errors='surrogate_or_strict'))
        b_parent_dir = os.path.split(b_dest_filepath)[0]
        if not os.path.exists(b_parent_dir):
            # Seems like Galaxy does not validate if all file entries have a corresponding dir ftype entry. This check
            # makes sure we create the parent directory even if it wasn't set in the metadata.
            os.makedirs(b_parent_dir, mode=0o0755)

        shutil.move(to_bytes(tmpfile_obj.name, errors='surrogate_or_strict'), b_dest_filepath)

        # Default to rw-r--r-- and only add execute if the tar file has execute.
        new_mode = 0o644
        if stat.S_IMODE(member.mode) & stat.S_IXUSR:
            new_mode |= 0o0111

        os.chmod(b_dest_filepath, new_mode)

    def install(self, path, b_temp_path):
        if self.skip:
            display.display("Skipping '%s' as it is already installed" % to_text(self))
            return

        # Install if it is not
        collection_path = os.path.join(path, self.namespace, self.name)
        b_collection_path = to_bytes(collection_path, errors='surrogate_or_strict')
        display.display("Installing '%s:%s' to '%s'" % (to_text(self), self.latest_version, collection_path))

        if self.b_path is None:
            download_url = self._metadata.download_url
            artifact_hash = self._metadata.artifact_sha256
            headers = {}
            self.api._add_auth_token(headers, download_url, required=False)

            self.b_path = _download_file(download_url, b_temp_path, artifact_hash, self.api.validate_certs,
                                         headers=headers)

        if os.path.exists(b_collection_path):
            shutil.rmtree(b_collection_path)
        os.makedirs(b_collection_path)

        try:
            with tarfile.open(self.b_path, mode='r') as collection_tar:
                files_member_obj = collection_tar.getmember('FILES.json')
                with _tarfile_extract(collection_tar, files_member_obj) as files_obj:
                    files = json.loads(to_text(files_obj.read(), errors='surrogate_or_strict'))

                _extract_tar_file(collection_tar, 'MANIFEST.json', b_collection_path, b_temp_path)
                _extract_tar_file(collection_tar, 'FILES.json', b_collection_path, b_temp_path)

                for file_info in files['files']:
                    file_name = file_info['name']
                    if file_name == '.':
                        continue

                    if file_info['ftype'] == 'file':
                        _extract_tar_file(collection_tar, file_name, b_collection_path, b_temp_path,
                                          expected_hash=file_info['chksum_sha256'])
                    else:
                        os.makedirs(os.path.join(b_collection_path, to_bytes(file_name, errors='surrogate_or_strict')))
        except Exception:
            # Ensure we don't leave the dir behind in case of a failure.
            shutil.rmtree(b_collection_path)

            b_namespace_path = os.path.dirname(b_collection_path)
            if not os.listdir(b_namespace_path):
                os.rmdir(b_namespace_path)

            raise

def _extract_tar_file(tar, filename, b_dest, b_temp_path, expected_hash=None):
    n_filename = to_native(filename, errors='surrogate_or_strict')
    try:
        member = tar.getmember(n_filename)
    except KeyError:
        raise AnsibleError("Collection tar at '%s' does not contain the expected file '%s'." % (to_native(tar.name),
                                                                                n_filename))

    with tempfile.NamedTemporaryFile(dir=b_temp_path, delete=False) as tmpfile_obj:
        bufsize = 65536
        sha256_digest = sha256()
        with _tarfile_extract(tar, member) as tar_obj:
            data = tar_obj.read(bufsize)
            while data:
                tmpfile_obj.write(data)
                tmpfile_obj.flush()
                sha256_digest.update(data)
                data = tar_obj.read(bufsize)

        actual_hash = sha256_digest.hexdigest()

        if expected_hash and actual_hash != expected_hash:
            raise AnsibleError("Checksum mismatch for '%s' inside collection at '%s'"
                               % (n_filename, to_native(tar.name)))

        b_dest_filepath = os.path.abspath(os.path.join(b_dest, to_bytes(filename, errors='surrogate_or_strict')))
        b_parent_dir = os.path.dirname(b_dest_filepath)
        if b_parent_dir != b_dest and not b_parent_dir.startswith(b_dest + to_bytes(os.path.sep)):
            raise AnsibleError("Cannot extract tar entry '%s' as it will be placed outside the collection directory"
                               % to_native(filename, errors='surrogate_or_strict'))

        if not os.path.exists(b_parent_dir):
            # Seems like Galaxy does not validate if all file entries have a corresponding dir ftype entry. This check
            # makes sure we create the parent directory even if it wasn't set in the metadata.
            os.makedirs(b_parent_dir, mode=0o0755)

        shutil.move(to_bytes(tmpfile_obj.name, errors='surrogate_or_strict'), b_dest_filepath)

        # Default to rw-r--r-- and only add execute if the tar file has execute.
        new_mode = 0o644
        if stat.S_IMODE(member.mode) & stat.S_IXUSR:
            new_mode |= 0o0111

        os.chmod(b_dest_filepath, new_mode)

--- a/lib/ansible/galaxy/collection.py
+++ b/lib/ansible/galaxy/collection.py
@@ -21,21 +21,31 @@
             shutil.rmtree(b_collection_path)
         os.makedirs(b_collection_path)

-        with tarfile.open(self.b_path, mode='r') as collection_tar:
-            files_member_obj = collection_tar.getmember('FILES.json')
-            with _tarfile_extract(collection_tar, files_member_obj) as files_obj:
-                files = json.loads(to_text(files_obj.read(), errors='surrogate_or_strict'))
+        try:
+            with tarfile.open(self.b_path, mode='r') as collection_tar:
+                files_member_obj = collection_tar.getmember('FILES.json')
+                with _tarfile_extract(collection_tar, files_member_obj) as files_obj:
+                    files = json.loads(to_text(files_obj.read(), errors='surrogate_or_strict'))

-            _extract_tar_file(collection_tar, 'MANIFEST.json', b_collection_path, b_temp_path)
-            _extract_tar_file(collection_tar, 'FILES.json', b_collection_path, b_temp_path)
+                _extract_tar_file(collection_tar, 'MANIFEST.json', b_collection_path, b_temp_path)
+                _extract_tar_file(collection_tar, 'FILES.json', b_collection_path, b_temp_path)

-            for file_info in files['files']:
-                file_name = file_info['name']
-                if file_name == '.':
-                    continue
+                for file_info in files['files']:
+                    file_name = file_info['name']
+                    if file_name == '.':
+                        continue

-                if file_info['ftype'] == 'file':
-                    _extract_tar_file(collection_tar, file_name, b_collection_path, b_temp_path,
-                                      expected_hash=file_info['chksum_sha256'])
-                else:
-                    os.makedirs(os.path.join(b_collection_path, to_bytes(file_name, errors='surrogate_or_strict')))
+                    if file_info['ftype'] == 'file':
+                        _extract_tar_file(collection_tar, file_name, b_collection_path, b_temp_path,
+                                          expected_hash=file_info['chksum_sha256'])
+                    else:
+                        os.makedirs(os.path.join(b_collection_path, to_bytes(file_name, errors='surrogate_or_strict')))
+        except Exception:
+            # Ensure we don't leave the dir behind in case of a failure.
+            shutil.rmtree(b_collection_path)
+
+            b_namespace_path = os.path.dirname(b_collection_path)
+            if not os.listdir(b_namespace_path):
+                os.rmdir(b_namespace_path)
+
+            raise

--- a/lib/ansible/galaxy/collection.py
+++ b/lib/ansible/galaxy/collection.py
@@ -23,8 +23,12 @@
             raise AnsibleError("Checksum mismatch for '%s' inside collection at '%s'"
                                % (n_filename, to_native(tar.name)))

-        b_dest_filepath = os.path.join(b_dest, to_bytes(filename, errors='surrogate_or_strict'))
-        b_parent_dir = os.path.split(b_dest_filepath)[0]
+        b_dest_filepath = os.path.abspath(os.path.join(b_dest, to_bytes(filename, errors='surrogate_or_strict')))
+        b_parent_dir = os.path.dirname(b_dest_filepath)
+        if b_parent_dir != b_dest and not b_parent_dir.startswith(b_dest + to_bytes(os.path.sep)):
+            raise AnsibleError("Cannot extract tar entry '%s' as it will be placed outside the collection directory"
+                               % to_native(filename, errors='surrogate_or_strict'))
+
         if not os.path.exists(b_parent_dir):
             # Seems like Galaxy does not validate if all file entries have a corresponding dir ftype entry. This check
             # makes sure we create the parent directory even if it wasn't set in the metadata.

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

cd /workspace/ansible
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
/workspace/PoC_env/CVE-2020-10691/bin/ansible-test integration ansible-galaxy-collection --python 3.8 --tags "bad_tarball" --allow-disabled && /workspace/PoC_env/CVE-2020-10691/bin/pytest test/units/galaxy/test_collection.py::test_extract_tar_file_outside_dir

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

cd /workspace/ansible
git apply --whitespace=nowarn /workspace/fix.patch
/workspace/PoC_env/CVE-2020-10691/bin/ansible-test integration ansible-galaxy-collection --python 3.8 --tags "bad_tarball" --allow-disabled && /workspace/PoC_env/CVE-2020-10691/bin/pytest test/units/galaxy/test_collection.py


jwx is a Go module implementing various JWx (JWA/JWE/JWK/JWS/JWT, otherwise known as JOSE) technologies. Calling `jws.Parse` with a JSON serialized payload where the `signature` field is present while `protected` is absent can lead to a nil pointer dereference. The vulnerability can be used to crash/DOS a system doing JWS verification. This vulnerability has been patched in versions 2.0.19 and 1.2.28.

NULL Pointer Dereference

The product dereferences a pointer that it expects to be valid but is NULL.

func (m *Message) UnmarshalJSON(buf []byte) error {
	m.payload = nil
	m.signatures = nil
	m.b64 = true

	var mup messageUnmarshalProbe
	mup.Header = NewHeaders()
	if err := json.Unmarshal(buf, &mup); err != nil {
		return fmt.Errorf(`failed to unmarshal into temporary structure: %w`, err)
	}

	b64 := true
	if mup.Signature == nil { // flattened signature is NOT present
		if len(mup.Signatures) == 0 {
			return fmt.Errorf(`required field "signatures" not present`)
		}

		m.signatures = make([]*Signature, 0, len(mup.Signatures))
		for i, rawsig := range mup.Signatures {
			var sig Signature
			sig.SetDecodeCtx(m.DecodeCtx())
			if err := json.Unmarshal(rawsig, &sig); err != nil {
				return fmt.Errorf(`failed to unmarshal signature #%d: %w`, i+1, err)
			}
			sig.SetDecodeCtx(nil)

			if i == 0 {
				if !getB64Value(sig.protected) {
					b64 = false
				}
			} else {
				if b64 != getB64Value(sig.protected) {
					return fmt.Errorf(`b64 value must be the same for all signatures`)
				}
			}

			m.signatures = append(m.signatures, &sig)
		}
	} else { // .signature is present, it's a flattened structure
		if len(mup.Signatures) != 0 {
			return fmt.Errorf(`invalid format ("signatures" and "signature" keys cannot both be present)`)
		}

		var sig Signature
		sig.headers = mup.Header
		if src := mup.Protected; src != nil {
			decoded, err := base64.DecodeString(*src)
			if err != nil {
				return fmt.Errorf(`failed to base64 decode flattened protected headers: %w`, err)
			}
			prt := NewHeaders()
			//nolint:forcetypeassert
			prt.(*stdHeaders).SetDecodeCtx(m.DecodeCtx())
			if err := json.Unmarshal(decoded, prt); err != nil {
				return fmt.Errorf(`failed to unmarshal flattened protected headers: %w`, err)
			}
			//nolint:forcetypeassert
			prt.(*stdHeaders).SetDecodeCtx(nil)
			sig.protected = prt
		}

		decoded, err := base64.DecodeString(*mup.Signature)
		if err != nil {
			return fmt.Errorf(`failed to base64 decode flattened signature: %w`, err)
		}
		sig.signature = decoded

		m.signatures = []*Signature{&sig}
		b64 = getB64Value(sig.protected)
	}

	if mup.Payload != nil {
		if !b64 { // NOT base64 encoded
			m.payload = []byte(*mup.Payload)
		} else {
			decoded, err := base64.DecodeString(*mup.Payload)
			if err != nil {
				return fmt.Errorf(`failed to base64 decode payload: %w`, err)
			}
			m.payload = decoded
		}
	}
	m.b64 = b64
	return nil
}

func (m *Message) UnmarshalJSON(buf []byte) error {
	m.payload = nil
	m.signatures = nil
	m.b64 = true

	var mup messageUnmarshalProbe
	mup.Header = NewHeaders()
	if err := json.Unmarshal(buf, &mup); err != nil {
		return fmt.Errorf(`failed to unmarshal into temporary structure: %w`, err)
	}

	b64 := true
	if mup.Signature == nil { // flattened signature is NOT present
		if len(mup.Signatures) == 0 {
			return fmt.Errorf(`required field "signatures" not present`)
		}

		m.signatures = make([]*Signature, 0, len(mup.Signatures))
		for i, rawsig := range mup.Signatures {
			var sig Signature
			sig.SetDecodeCtx(m.DecodeCtx())
			if err := json.Unmarshal(rawsig, &sig); err != nil {
				return fmt.Errorf(`failed to unmarshal signature #%d: %w`, i+1, err)
			}
			sig.SetDecodeCtx(nil)

			if sig.protected == nil {
				// Instead of barfing on a nil protected header, use an empty header
				sig.protected = NewHeaders()
			}

			if i == 0 {
				if !getB64Value(sig.protected) {
					b64 = false
				}
			} else {
				if b64 != getB64Value(sig.protected) {
					return fmt.Errorf(`b64 value must be the same for all signatures`)
				}
			}

			m.signatures = append(m.signatures, &sig)
		}
	} else { // .signature is present, it's a flattened structure
		if len(mup.Signatures) != 0 {
			return fmt.Errorf(`invalid format ("signatures" and "signature" keys cannot both be present)`)
		}

		var sig Signature
		sig.headers = mup.Header
		if src := mup.Protected; src != nil {
			decoded, err := base64.DecodeString(*src)
			if err != nil {
				return fmt.Errorf(`failed to base64 decode flattened protected headers: %w`, err)
			}
			prt := NewHeaders()
			//nolint:forcetypeassert
			prt.(*stdHeaders).SetDecodeCtx(m.DecodeCtx())
			if err := json.Unmarshal(decoded, prt); err != nil {
				return fmt.Errorf(`failed to unmarshal flattened protected headers: %w`, err)
			}
			//nolint:forcetypeassert
			prt.(*stdHeaders).SetDecodeCtx(nil)
			sig.protected = prt
		}

		if sig.protected == nil {
			// Instead of barfing on a nil protected header, use an empty header
			sig.protected = NewHeaders()
		}

		decoded, err := base64.DecodeString(*mup.Signature)
		if err != nil {
			return fmt.Errorf(`failed to base64 decode flattened signature: %w`, err)
		}
		sig.signature = decoded

		m.signatures = []*Signature{&sig}
		b64 = getB64Value(sig.protected)
	}

	if mup.Payload != nil {
		if !b64 { // NOT base64 encoded
			m.payload = []byte(*mup.Payload)
		} else {
			decoded, err := base64.DecodeString(*mup.Payload)
			if err != nil {
				return fmt.Errorf(`failed to base64 decode payload: %w`, err)
			}
			m.payload = decoded
		}
	}
	m.b64 = b64
	return nil
}

--- a/jws/message.go
+++ b/jws/message.go
@@ -23,6 +23,11 @@
 				return fmt.Errorf(`failed to unmarshal signature #%d: %w`, i+1, err)
 			}
 			sig.SetDecodeCtx(nil)
+
+			if sig.protected == nil {
+				// Instead of barfing on a nil protected header, use an empty header
+				sig.protected = NewHeaders()
+			}

 			if i == 0 {
 				if !getB64Value(sig.protected) {
@@ -59,6 +64,11 @@
 			sig.protected = prt
 		}

+		if sig.protected == nil {
+			// Instead of barfing on a nil protected header, use an empty header
+			sig.protected = NewHeaders()
+		}
+
 		decoded, err := base64.DecodeString(*mup.Signature)
 		if err != nil {
 			return fmt.Errorf(`failed to base64 decode flattened signature: %w`, err)


An issue was discovered in Bleach 2.1.x before 2.1.3. Attributes that have URI values weren't properly sanitized if the values contained character entities. Using character entities, it was possible to construct a URI value with a scheme that was not allowed that would slide through unsanitized.

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.

    def sanitize_characters(self, token):
        """Handles Characters tokens

        Our overridden tokenizer doesn't do anything with entities. However,
        that means that the serializer will convert all ``&`` in Characters
        tokens to ``&``.

        Since we don't want that, we extract entities here and convert them to
        Entity tokens so the serializer will let them be.

        :arg token: the Characters token to work on

        :returns: a list of tokens

        """
        data = token.get('data', '')

        if not data:
            return token

        data = INVISIBLE_CHARACTERS_RE.sub(INVISIBLE_REPLACEMENT_CHAR, data)
        token['data'] = data

        # If there isn't a & in the data, we can return now
        if '&' not in data:
            return token

        new_tokens = []

        # For each possible entity that starts with a "&", we try to extract an
        # actual entity and re-tokenize accordingly
        for part in next_possible_entity(data):
            if not part:
                continue

            if part.startswith('&'):
                entity = match_entity(part)
                if entity is not None:
                    new_tokens.append({'type': 'Entity', 'name': entity})
                    # Length of the entity plus 2--one for & at the beginning
                    # and and one for ; at the end
                    part = part[len(entity) + 2:]
                    if part:
                        new_tokens.append({'type': 'Characters', 'data': part})
                    continue

            new_tokens.append({'type': 'Characters', 'data': part})

        return new_tokens

    def allow_token(self, token):
        """Handles the case where we're allowing the tag"""
        if 'data' in token:
            # Loop through all the attributes and drop the ones that are not
            # allowed, are unsafe or break other rules. Additionally, fix
            # attribute values that need fixing.
            #
            # At the end of this loop, we have the final set of attributes
            # we're keeping.
            attrs = {}
            for namespaced_name, val in token['data'].items():
                namespace, name = namespaced_name

                # Drop attributes that are not explicitly allowed
                #
                # NOTE(willkg): We pass in the attribute name--not a namespaced
                # name.
                if not self.attr_filter(token['name'], name, val):
                    continue

                # Look at attributes that have uri values
                if namespaced_name in self.attr_val_is_uri:
                    val_unescaped = re.sub(
                        "[`\000-\040\177-\240\s]+",
                        '',
                        unescape(val)).lower()

                    # Remove replacement characters from unescaped characters.
                    val_unescaped = val_unescaped.replace("\ufffd", "")

                    # Drop attributes with uri values that have protocols that
                    # aren't allowed
                    if (re.match(r'^[a-z0-9][-+.a-z0-9]*:', val_unescaped) and
                            (val_unescaped.split(':')[0] not in self.allowed_protocols)):
                        continue

                # Drop values in svg attrs with non-local IRIs
                if namespaced_name in self.svg_attr_val_allows_ref:
                    new_val = re.sub(r'url\s*\(\s*[^#\s][^)]+?\)',
                                     ' ',
                                     unescape(val))
                    new_val = new_val.strip()
                    if not new_val:
                        continue

                    else:
                        # Replace the val with the unescaped version because
                        # it's a iri
                        val = new_val

                # Drop href and xlink:href attr for svg elements with non-local IRIs
                if (None, token['name']) in self.svg_allow_local_href:
                    if namespaced_name in [(None, 'href'), (namespaces['xlink'], 'href')]:
                        if re.search(r'^\s*[^#\s]', val):
                            continue

                # If it's a style attribute, sanitize it
                if namespaced_name == (None, u'style'):
                    val = self.sanitize_css(val)

                # At this point, we want to keep the attribute, so add it in
                attrs[namespaced_name] = val

            token['data'] = alphabetize_attributes(attrs)

        return token

    def sanitize_characters(self, token):
        """Handles Characters tokens

        Our overridden tokenizer doesn't do anything with entities. However,
        that means that the serializer will convert all ``&`` in Characters
        tokens to ``&``.

        Since we don't want that, we extract entities here and convert them to
        Entity tokens so the serializer will let them be.

        :arg token: the Characters token to work on

        :returns: a list of tokens

        """
        data = token.get('data', '')

        if not data:
            return token

        data = INVISIBLE_CHARACTERS_RE.sub(INVISIBLE_REPLACEMENT_CHAR, data)
        token['data'] = data

        # If there isn't a & in the data, we can return now
        if '&' not in data:
            return token

        new_tokens = []

        # For each possible entity that starts with a "&", we try to extract an
        # actual entity and re-tokenize accordingly
        for part in next_possible_entity(data):
            if not part:
                continue

            if part.startswith('&'):
                entity = match_entity(part)
                if entity is not None:
                    new_tokens.append({'type': 'Entity', 'name': entity})
                    # Length of the entity plus 2--one for & at the beginning
                    # and and one for ; at the end
                    remainder = part[len(entity) + 2:]
                    if remainder:
                        new_tokens.append({'type': 'Characters', 'data': remainder})
                    continue

            new_tokens.append({'type': 'Characters', 'data': part})

        return new_tokens

    def allow_token(self, token):
        """Handles the case where we're allowing the tag"""
        if 'data' in token:
            # Loop through all the attributes and drop the ones that are not
            # allowed, are unsafe or break other rules. Additionally, fix
            # attribute values that need fixing.
            #
            # At the end of this loop, we have the final set of attributes
            # we're keeping.
            attrs = {}
            for namespaced_name, val in token['data'].items():
                namespace, name = namespaced_name

                # Drop attributes that are not explicitly allowed
                #
                # NOTE(willkg): We pass in the attribute name--not a namespaced
                # name.
                if not self.attr_filter(token['name'], name, val):
                    continue

                # Drop attributes with uri values that use a disallowed protocol
                # Sanitize attributes with uri values
                if namespaced_name in self.attr_val_is_uri:
                    new_value = self.sanitize_uri_value(val, self.allowed_protocols)
                    if new_value is None:
                        continue
                    val = new_value

                # Drop values in svg attrs with non-local IRIs
                if namespaced_name in self.svg_attr_val_allows_ref:
                    new_val = re.sub(r'url\s*\(\s*[^#\s][^)]+?\)',
                                     ' ',
                                     unescape(val))
                    new_val = new_val.strip()
                    if not new_val:
                        continue

                    else:
                        # Replace the val with the unescaped version because
                        # it's a iri
                        val = new_val

                # Drop href and xlink:href attr for svg elements with non-local IRIs
                if (None, token['name']) in self.svg_allow_local_href:
                    if namespaced_name in [(None, 'href'), (namespaces['xlink'], 'href')]:
                        if re.search(r'^\s*[^#\s]', val):
                            continue

                # If it's a style attribute, sanitize it
                if namespaced_name == (None, u'style'):
                    val = self.sanitize_css(val)

                # At this point, we want to keep the attribute, so add it in
                attrs[namespaced_name] = val

            token['data'] = alphabetize_attributes(attrs)

        return token

def convert_entity(value):
    """Convert an entity (minus the & and ; part) into what it represents

    This handles numeric, hex, and text entities.

    :arg value: the string (minus the ``&`` and ``;`` part) to convert

    :returns: unicode character

    """
    if value[0] == '#':
        if value[1] in ('x', 'X'):
            return six.unichr(int(value[2:], 16))
        return six.unichr(int(value[1:], 10))

    return ENTITIES[value]

def convert_entities(text):
    """Converts all found entities in the text

    :arg text: the text to convert entities in

    :returns: unicode text with converted entities

    """
    if '&' not in text:
        return text

    new_text = []
    for part in next_possible_entity(text):
        if not part:
            continue

        if part.startswith('&'):
            entity = match_entity(part)
            if entity is not None:
                new_text.append(convert_entity(entity))
                remainder = part[len(entity) + 2:]
                if part:
                    new_text.append(remainder)
                continue

        new_text.append(part)

    return u''.join(new_text)

    def sanitize_uri_value(self, value, allowed_protocols):
        """Checks a uri value to see if it's allowed

        :arg value: the uri value to sanitize
        :arg allowed_protocols: list of allowed protocols

        :returns: allowed value or None

        """
        # NOTE(willkg): This transforms the value into one that's easier to
        # match and verify, but shouldn't get returned since it's vastly
        # different than the original value.

        # Convert all character entities in the value
        new_value = convert_entities(value)

        # Nix backtick, space characters, and control characters
        new_value = re.sub(
            "[`\000-\040\177-\240\s]+",
            '',
            new_value
        )

        # Remove REPLACEMENT characters
        new_value = new_value.replace('\ufffd', '')

        # Lowercase it--this breaks the value, but makes it easier to match
        # against
        new_value = new_value.lower()

        # Drop attributes with uri values that have protocols that aren't
        # allowed
        parsed = urlparse(new_value)
        if parsed.scheme:
            # If urlparse found a scheme, check that
            if parsed.scheme in allowed_protocols:
                return value

        else:
            # Allow uris that are just an anchor
            if new_value.startswith('#'):
                return value

            # Handle protocols that urlparse doesn't recognize like "myprotocol"
            if ':' in new_value and new_value.split(':')[0] in allowed_protocols:
                return value

            # If there's no protocol/scheme specified, then assume it's "http"
            # and see if that's allowed
            if 'http' in allowed_protocols:
                return value

        return None

--- a/bleach/sanitizer.py
+++ b/bleach/sanitizer.py
@@ -39,9 +39,9 @@
                     new_tokens.append({'type': 'Entity', 'name': entity})
                     # Length of the entity plus 2--one for & at the beginning
                     # and and one for ; at the end
-                    part = part[len(entity) + 2:]
-                    if part:
-                        new_tokens.append({'type': 'Characters', 'data': part})
+                    remainder = part[len(entity) + 2:]
+                    if remainder:
+                        new_tokens.append({'type': 'Characters', 'data': remainder})
                     continue

             new_tokens.append({'type': 'Characters', 'data': part})

--- a/bleach/sanitizer.py
+++ b/bleach/sanitizer.py
@@ -18,21 +18,13 @@
                 if not self.attr_filter(token['name'], name, val):
                     continue

-                # Look at attributes that have uri values
+                # Drop attributes with uri values that use a disallowed protocol
+                # Sanitize attributes with uri values
                 if namespaced_name in self.attr_val_is_uri:
-                    val_unescaped = re.sub(
-                        "[`\000-\040\177-\240\s]+",
-                        '',
-                        unescape(val)).lower()
-
-                    # Remove replacement characters from unescaped characters.
-                    val_unescaped = val_unescaped.replace("\ufffd", "")
-
-                    # Drop attributes with uri values that have protocols that
-                    # aren't allowed
-                    if (re.match(r'^[a-z0-9][-+.a-z0-9]*:', val_unescaped) and
-                            (val_unescaped.split(':')[0] not in self.allowed_protocols)):
+                    new_value = self.sanitize_uri_value(val, self.allowed_protocols)
+                    if new_value is None:
                         continue
+                    val = new_value

                 # Drop values in svg attrs with non-local IRIs
                 if namespaced_name in self.svg_attr_val_allows_ref:

--- /dev/null
+++ b/bleach/sanitizer.py
@@ -0,0 +1,16 @@
+def convert_entity(value):
+    """Convert an entity (minus the & and ; part) into what it represents
+
+    This handles numeric, hex, and text entities.
+
+    :arg value: the string (minus the ``&`` and ``;`` part) to convert
+
+    :returns: unicode character
+
+    """
+    if value[0] == '#':
+        if value[1] in ('x', 'X'):
+            return six.unichr(int(value[2:], 16))
+        return six.unichr(int(value[1:], 10))
+
+    return ENTITIES[value]

--- /dev/null
+++ b/bleach/sanitizer.py
@@ -0,0 +1,28 @@
+def convert_entities(text):
+    """Converts all found entities in the text
+
+    :arg text: the text to convert entities in
+
+    :returns: unicode text with converted entities
+
+    """
+    if '&' not in text:
+        return text
+
+    new_text = []
+    for part in next_possible_entity(text):
+        if not part:
+            continue
+
+        if part.startswith('&'):
+            entity = match_entity(part)
+            if entity is not None:
+                new_text.append(convert_entity(entity))
+                remainder = part[len(entity) + 2:]
+                if part:
+                    new_text.append(remainder)
+                continue
+
+        new_text.append(part)
+
+    return u''.join(new_text)

--- /dev/null
+++ b/bleach/sanitizer.py
@@ -0,0 +1,53 @@
+    def sanitize_uri_value(self, value, allowed_protocols):
+        """Checks a uri value to see if it's allowed
+
+        :arg value: the uri value to sanitize
+        :arg allowed_protocols: list of allowed protocols
+
+        :returns: allowed value or None
+
+        """
+        # NOTE(willkg): This transforms the value into one that's easier to
+        # match and verify, but shouldn't get returned since it's vastly
+        # different than the original value.
+
+        # Convert all character entities in the value
+        new_value = convert_entities(value)
+
+        # Nix backtick, space characters, and control characters
+        new_value = re.sub(
+            "[`\000-\040\177-\240\s]+",
+            '',
+            new_value
+        )
+
+        # Remove REPLACEMENT characters
+        new_value = new_value.replace('\ufffd', '')
+
+        # Lowercase it--this breaks the value, but makes it easier to match
+        # against
+        new_value = new_value.lower()
+
+        # Drop attributes with uri values that have protocols that aren't
+        # allowed
+        parsed = urlparse(new_value)
+        if parsed.scheme:
+            # If urlparse found a scheme, check that
+            if parsed.scheme in allowed_protocols:
+                return value
+
+        else:
+            # Allow uris that are just an anchor
+            if new_value.startswith('#'):
+                return value
+
+            # Handle protocols that urlparse doesn't recognize like "myprotocol"
+            if ':' in new_value and new_value.split(':')[0] in allowed_protocols:
+                return value
+
+            # If there's no protocol/scheme specified, then assume it's "http"
+            # and see if that's allowed
+            if 'http' in allowed_protocols:
+                return value
+
+        return None

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

cd /workspace/bleach
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
/workspace/PoC_env/CVE-2018-7753/bin/python -m pytest tests/test_clean.py::test_uri_value_allowed_protocols tests/test_clean.py::test_character_entities_handling -v

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

cd /workspace/bleach
git apply --whitespace=nowarn /workspace/fix.patch
/workspace/PoC_env/CVE-2018-7753/bin/python -m pytest tests/test_clean.py -v


Pivotal Concourse version 5.0.0, contains an API that is vulnerable to SQL injection. An Concourse resource can craft a version identifier that can carry a SQL injection payload to the Concourse server, allowing the attacker to read privileged data.

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

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.

func (b *build) SaveOutput(
	logger lager.Logger,
	resourceType string,
	source atc.Source,
	resourceTypes creds.VersionedResourceTypes,
	version atc.Version,
	metadata ResourceConfigMetadataFields,
	outputName string,
	resourceName string,
) error {
	// We should never save outputs for builds without a Pipeline ID because
	// One-off Builds will never have Put steps. This shouldn't happen, but
	// its best to return an error just in case
	if b.pipelineID == 0 {
		return ErrBuildHasNoPipeline
	}

	pipeline, found, err := b.Pipeline()
	if err != nil {
		return err
	}

	if !found {
		return ErrBuildHasNoPipeline
	}

	resource, found, err := pipeline.Resource(resourceName)
	if err != nil {
		return err
	}

	if !found {
		return ResourceNotFoundInPipeline{resource.Name(), b.pipelineName}
	}

	tx, err := b.conn.Begin()
	if err != nil {
		return err
	}

	defer Rollback(tx)

	resourceConfigDescriptor, err := constructResourceConfigDescriptor(resourceType, source, resourceTypes)
	if err != nil {
		return err
	}

	resourceConfig, err := resourceConfigDescriptor.findOrCreate(logger, tx, b.lockFactory, b.conn)
	if err != nil {
		return err
	}

	resourceConfigScope, err := findOrCreateResourceConfigScope(tx, b.conn, b.lockFactory, resourceConfig, resource, resourceTypes)
	if err != nil {
		return err
	}

	newVersion, err := saveResourceVersion(tx, resourceConfigScope, version, metadata)
	if err != nil {
		return err
	}

	versionBytes, err := json.Marshal(version)
	if err != nil {
		return err
	}

	versionJSON := string(versionBytes)

	if newVersion {
		err = incrementCheckOrder(tx, resourceConfigScope, versionJSON)
		if err != nil {
			return err
		}
	}

	_, err = psql.Insert("build_resource_config_version_outputs").
		Columns("resource_id", "build_id", "version_md5", "name").
		Values(resource.ID(), strconv.Itoa(b.id), sq.Expr(fmt.Sprintf("md5('%s')", versionJSON)), sq.Expr(fmt.Sprintf("'%s'", outputName))).
		Suffix("ON CONFLICT DO NOTHING").
		RunWith(tx).
		Exec()

	if err != nil {
		return err
	}

	err = bumpCacheIndex(tx, b.pipelineID)
	if err != nil {
		return err
	}

	err = tx.Commit()
	if err != nil {
		return err
	}

	err = bumpCacheIndexForPipelinesUsingResourceConfigScope(b.conn, resourceConfigScope.ID())
	if err != nil {
		return err
	}

	return nil
}

func (p *build) saveInputTx(tx Tx, buildID int, input BuildInput) error {
	versionJSON, err := json.Marshal(input.Version)
	if err != nil {
		return err
	}

	_, err = psql.Insert("build_resource_config_version_inputs").
		Columns("build_id", "resource_id", "version_md5", "name").
		Values(buildID, input.ResourceID, sq.Expr(fmt.Sprintf("md5('%s')", versionJSON)), input.Name).
		Suffix("ON CONFLICT DO NOTHING").
		RunWith(tx).
		Exec()

	return err
}

func (r *resourceConfigScope) FindVersion(v atc.Version) (ResourceConfigVersion, bool, error) {
	rcv := &resourceConfigVersion{
		resourceConfigScope: r,
		conn:                r.conn,
	}

	versionByte, err := json.Marshal(v)
	if err != nil {
		return nil, false, err
	}

	row := resourceConfigVersionQuery.
		Where(sq.Eq{
			"v.resource_config_scope_id": r.id,
		}).
		Where(sq.Expr(fmt.Sprintf("v.version_md5 = md5('%s')", versionByte))).
		RunWith(r.conn).
		QueryRow()

	err = scanResourceConfigVersion(rcv, row)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, false, nil
		}
		return nil, false, err
	}

	return rcv, true, nil
}

func (b *build) SaveOutput(
	logger lager.Logger,
	resourceType string,
	source atc.Source,
	resourceTypes creds.VersionedResourceTypes,
	version atc.Version,
	metadata ResourceConfigMetadataFields,
	outputName string,
	resourceName string,
) error {
	// We should never save outputs for builds without a Pipeline ID because
	// One-off Builds will never have Put steps. This shouldn't happen, but
	// its best to return an error just in case
	if b.pipelineID == 0 {
		return ErrBuildHasNoPipeline
	}

	pipeline, found, err := b.Pipeline()
	if err != nil {
		return err
	}

	if !found {
		return ErrBuildHasNoPipeline
	}

	resource, found, err := pipeline.Resource(resourceName)
	if err != nil {
		return err
	}

	if !found {
		return ResourceNotFoundInPipeline{resource.Name(), b.pipelineName}
	}

	tx, err := b.conn.Begin()
	if err != nil {
		return err
	}

	defer Rollback(tx)

	resourceConfigDescriptor, err := constructResourceConfigDescriptor(resourceType, source, resourceTypes)
	if err != nil {
		return err
	}

	resourceConfig, err := resourceConfigDescriptor.findOrCreate(logger, tx, b.lockFactory, b.conn)
	if err != nil {
		return err
	}

	resourceConfigScope, err := findOrCreateResourceConfigScope(tx, b.conn, b.lockFactory, resourceConfig, resource, resourceTypes)
	if err != nil {
		return err
	}

	newVersion, err := saveResourceVersion(tx, resourceConfigScope, version, metadata)
	if err != nil {
		return err
	}

	versionBytes, err := json.Marshal(version)
	if err != nil {
		return err
	}

	versionJSON := string(versionBytes)

	if newVersion {
		err = incrementCheckOrder(tx, resourceConfigScope, versionJSON)
		if err != nil {
			return err
		}
	}

	_, err = psql.Insert("build_resource_config_version_outputs").
		Columns("resource_id", "build_id", "version_md5", "name").
		Values(resource.ID(), strconv.Itoa(b.id), sq.Expr("md5(?)", versionJSON), outputName).
		Suffix("ON CONFLICT DO NOTHING").
		RunWith(tx).
		Exec()

	if err != nil {
		return err
	}

	err = bumpCacheIndex(tx, b.pipelineID)
	if err != nil {
		return err
	}

	err = tx.Commit()
	if err != nil {
		return err
	}

	err = bumpCacheIndexForPipelinesUsingResourceConfigScope(b.conn, resourceConfigScope.ID())
	if err != nil {
		return err
	}

	return nil
}

func (p *build) saveInputTx(tx Tx, buildID int, input BuildInput) error {
	versionJSON, err := json.Marshal(input.Version)
	if err != nil {
		return err
	}

	_, err = psql.Insert("build_resource_config_version_inputs").
		Columns("build_id", "resource_id", "version_md5", "name").
		Values(buildID, input.ResourceID, sq.Expr("md5(?)", versionJSON), input.Name).
		Suffix("ON CONFLICT DO NOTHING").
		RunWith(tx).
		Exec()

	return err
}

func (r *resourceConfigScope) FindVersion(v atc.Version) (ResourceConfigVersion, bool, error) {
	rcv := &resourceConfigVersion{
		resourceConfigScope: r,
		conn:                r.conn,
	}

	versionByte, err := json.Marshal(v)
	if err != nil {
		return nil, false, err
	}

	row := resourceConfigVersionQuery.
		Where(sq.Eq{
			"v.resource_config_scope_id": r.id,
		}).
		Where(sq.Expr("v.version_md5 = md5(?)", versionByte)).
		RunWith(r.conn).
		QueryRow()

	err = scanResourceConfigVersion(rcv, row)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, false, nil
		}
		return nil, false, err
	}

	return rcv, true, nil
}

--- a/atc/db/build.go
+++ b/atc/db/build.go
@@ -76,7 +76,7 @@

 	_, err = psql.Insert("build_resource_config_version_outputs").
 		Columns("resource_id", "build_id", "version_md5", "name").
-		Values(resource.ID(), strconv.Itoa(b.id), sq.Expr(fmt.Sprintf("md5('%s')", versionJSON)), sq.Expr(fmt.Sprintf("'%s'", outputName))).
+		Values(resource.ID(), strconv.Itoa(b.id), sq.Expr("md5(?)", versionJSON), outputName).
 		Suffix("ON CONFLICT DO NOTHING").
 		RunWith(tx).
 		Exec()

--- a/atc/db/build.go
+++ b/atc/db/build.go
@@ -6,7 +6,7 @@

 	_, err = psql.Insert("build_resource_config_version_inputs").
 		Columns("build_id", "resource_id", "version_md5", "name").
-		Values(buildID, input.ResourceID, sq.Expr(fmt.Sprintf("md5('%s')", versionJSON)), input.Name).
+		Values(buildID, input.ResourceID, sq.Expr("md5(?)", versionJSON), input.Name).
 		Suffix("ON CONFLICT DO NOTHING").
 		RunWith(tx).
 		Exec()

--- a/atc/db/resource_config_scope.go
+++ b/atc/db/resource_config_scope.go
@@ -13,7 +13,7 @@
 		Where(sq.Eq{
 			"v.resource_config_scope_id": r.id,
 		}).
-		Where(sq.Expr(fmt.Sprintf("v.version_md5 = md5('%s')", versionByte))).
+		Where(sq.Expr("v.version_md5 = md5(?)", versionByte)).
 		RunWith(r.conn).
 		QueryRow()


Total.js framework (npm package total.js) is a framework for Node.js platfrom written in pure JavaScript similar to PHP's Laravel or Python's Django or ASP.NET MVC. In total.js framework before version 3.4.9, calling the utils.set function with user-controlled values leads to code-injection. This can cause a variety of impacts that include arbitrary code execution. This is fixed in version 3.4.9.

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.

exports.set = function(obj, path, value) {
	var cachekey = 'S+' + path;

	if (F.temporary.other[cachekey])
		return F.temporary.other[cachekey](obj, value);

	var arr = parsepath(path);
	var builder = [];

	for (var i = 0; i < arr.length - 1; i++) {
		var type = arr[i + 1] ? (REGISARR.test(arr[i + 1]) ? '[]' : '{}') : '{}';
		var p = 'w' + (arr[i][0] === '[' ? '' : '.') + arr[i];
		builder.push('if(typeof(' + p + ')!==\'object\'||' + p + '==null)' + p + '=' + type + ';');
	}

	var v = arr[arr.length - 1];
	var ispush = v.lastIndexOf('[]') !== -1;
	var a = builder.join(';') + ';var v=typeof(a)===\'function\'?a(U.get(b)):a;w' + (v[0] === '[' ? '' : '.') + (ispush ? v.replace(REGREPLACEARR, '.push(v)') : (v + '=v')) + ';return v';

	if ((/__proto__|constructor|prototype|eval/).test(a))
		throw new Error('Potential vulnerability');

	var fn = new Function('w', 'a', 'b', a);
	F.temporary.other[cachekey] = fn;
	fn(obj, value, path);
};

exports.get = function(obj, path) {

	var cachekey = 'G=' + path;

	if (F.temporary.other[cachekey])
		return F.temporary.other[cachekey](obj);

	var arr = parsepath(path);
	var builder = [];

	for (var i = 0, length = arr.length - 1; i < length; i++)
		builder.push('if(!w' + (!arr[i] || arr[i][0] === '[' ? '' : '.') + arr[i] + ')return');

	var v = arr[arr.length - 1];
	var fn = (new Function('w', builder.join(';') + ';return w' + (v[0] === '[' ? '' : '.') + v));
	F.temporary.other[cachekey] = fn;
	return fn(obj);
};

exports.set = function(obj, path, value) {
	var cachekey = 'S+' + path;

	if (F.temporary.other[cachekey])
		return F.temporary.other[cachekey](obj, value);

	if ((/__proto__|constructor|prototype|eval|function|\*|\+|;|\s|\(|\)|!/).test(path))
		return value;

	var arr = parsepath(path);
	var builder = [];

	for (var i = 0; i < arr.length - 1; i++) {
		var type = arr[i + 1] ? (REGISARR.test(arr[i + 1]) ? '[]' : '{}') : '{}';
		var p = 'w' + (arr[i][0] === '[' ? '' : '.') + arr[i];
		builder.push('if(typeof(' + p + ')!==\'object\'||' + p + '==null)' + p + '=' + type + ';');
	}

	var v = arr[arr.length - 1];
	var ispush = v.lastIndexOf('[]') !== -1;
	var a = builder.join(';') + ';var v=typeof(a)===\'function\'?a(U.get(b)):a;w' + (v[0] === '[' ? '' : '.') + (ispush ? v.replace(REGREPLACEARR, '.push(v)') : (v + '=v')) + ';return v';

	var fn = new Function('w', 'a', 'b', a);
	F.temporary.other[cachekey] = fn;
	return fn(obj, value, path);
};

exports.get = function(obj, path) {

	var cachekey = 'G=' + path;

	if (F.temporary.other[cachekey])
		return F.temporary.other[cachekey](obj);

	if ((/__proto__|constructor|prototype|eval|function|\*|\+|;|\s|\(|\)|!/).test(path))
		return;

	var arr = parsepath(path);
	var builder = [];

	for (var i = 0, length = arr.length - 1; i < length; i++)
		builder.push('if(!w' + (!arr[i] || arr[i][0] === '[' ? '' : '.') + arr[i] + ')return');

	var v = arr[arr.length - 1];
	var fn = (new Function('w', builder.join(';') + ';return w' + (v[0] === '[' ? '' : '.') + v));
	F.temporary.other[cachekey] = fn;
	return fn(obj);
};

--- a/utils.js
+++ b/utils.js
@@ -3,6 +3,9 @@

 	if (F.temporary.other[cachekey])
 		return F.temporary.other[cachekey](obj, value);
+
+	if ((/__proto__|constructor|prototype|eval|function|\*|\+|;|\s|\(|\)|!/).test(path))
+		return value;

 	var arr = parsepath(path);
 	var builder = [];
@@ -17,10 +20,7 @@
 	var ispush = v.lastIndexOf('[]') !== -1;
 	var a = builder.join(';') + ';var v=typeof(a)===\'function\'?a(U.get(b)):a;w' + (v[0] === '[' ? '' : '.') + (ispush ? v.replace(REGREPLACEARR, '.push(v)') : (v + '=v')) + ';return v';

-	if ((/__proto__|constructor|prototype|eval/).test(a))
-		throw new Error('Potential vulnerability');
-
 	var fn = new Function('w', 'a', 'b', a);
 	F.temporary.other[cachekey] = fn;
-	fn(obj, value, path);
+	return fn(obj, value, path);
 };

--- a/utils.js
+++ b/utils.js
@@ -4,6 +4,9 @@

 	if (F.temporary.other[cachekey])
 		return F.temporary.other[cachekey](obj);
+
+	if ((/__proto__|constructor|prototype|eval|function|\*|\+|;|\s|\(|\)|!/).test(path))
+		return;

 	var arr = parsepath(path);
 	var builder = [];


Meshery is an open source, cloud native manager that enables the design and management of Kubernetes-based infrastructure and applications. A SQL injection vulnerability in Meshery prior to version 0.7.17 allows a remote attacker to obtain sensitive information via the `order` parameter of `GetMeshSyncResources`. Version 0.7.17 contains a patch for this issue.

func (h *Handler) GetMeshSyncResources(rw http.ResponseWriter, r *http.Request, _ *models.Preference, _ *models.User, provider models.Provider) {
	rw.Header().Set("Content-Type", "application/json")
	enc := json.NewEncoder(rw)

	var resources []model.KubernetesResource
	var totalCount int64
	limitstr := r.URL.Query().Get("pagesize")
	var limit int
	if limitstr != "all" {
		limit, _ = strconv.Atoi(limitstr)
		if limit <= 0 {
			limit = defaultPageSize
		}
	}

	pagestr := r.URL.Query().Get("page")
	page, _ := strconv.Atoi(pagestr)

	if page <= 0 {
		page = 1
	}

	offset := (page - 1) * limit
	order := r.URL.Query().Get("order")
	sort := r.URL.Query().Get("sort")
	search := r.URL.Query().Get("search")
	apiVersion := r.URL.Query().Get("apiVersion")
	spec, _ := strconv.ParseBool(r.URL.Query().Get("spec"))
	status, _ := strconv.ParseBool(r.URL.Query().Get("status"))
	isAnnotaion, _ := strconv.ParseBool(r.URL.Query().Get("annotations"))
	isLabels, _ := strconv.ParseBool(r.URL.Query().Get("labels"))
	kind := r.URL.Query().Get("kind")

	filter := struct {
		ClusterIds []string `json:"clusterIds"`
	}{}

	clusterIds := r.URL.Query().Get("clusterIds")
	if clusterIds != "" {
		err := json.Unmarshal([]byte(clusterIds), &filter.ClusterIds)
		if err != nil {
			h.log.Error(ErrFetchMeshSyncResources(err))
			http.Error(rw, ErrFetchMeshSyncResources(err).Error(), http.StatusInternalServerError)
			return
		}
	} else {
		filter.ClusterIds = []string{}
	}

	result := provider.GetGenericPersister().Model(&model.KubernetesResource{}).
		Preload("KubernetesResourceMeta").
		Where("kubernetes_resources.cluster_id IN (?)", filter.ClusterIds)

	if kind != "" {
		result = result.Where(&model.KubernetesResource{Kind: kind})
	}

	if apiVersion != "" {
		result = result.Where(&model.KubernetesResource{APIVersion: apiVersion})
	}

	if isLabels {
		result = result.Preload("KubernetesResourceMeta.Labels", "kind = ?", model.KindLabel)
	}
	if isAnnotaion {
		result = result.Preload("KubernetesResourceMeta.Annotations", "kind = ?", model.KindAnnotation)
	}

	if spec {
		result = result.Preload("Spec")
	}

	if status {
		result = result.Preload("Status")
	}

	if search != "" {

		result = result.
			Joins("JOIN kubernetes_resource_object_meta ON kubernetes_resource_object_meta.id = kubernetes_resources.id").
			Where("kubernetes_resource_object_meta.name LIKE ?", "%"+search+"%")
	}

	result.Count(&totalCount)

	if limit != 0 {
		result = result.Limit(limit)
	}

	if offset != 0 {
		result = result.Offset(offset)
	}

	if order != "" {
		if sort == "desc" {
			result = result.Order(clause.OrderByColumn{Column: clause.Column{Name: order}, Desc: true})
		} else {
			result = result.Order(order)
		}
	}

	err := result.Find(&resources).Error
	if err != nil {
		h.log.Error(ErrFetchMeshSyncResources(err))
		http.Error(rw, ErrFetchMeshSyncResources(err).Error(), http.StatusInternalServerError)
		return
	}

	var pgSize int
	if limitstr == "all" {
		pgSize = len(resources)
	} else {
		pgSize = limit
	}

	response := &models.MeshSyncResourcesAPIResponse{
		Page:       page,
		PageSize:   pgSize,
		TotalCount: totalCount,
		Resources:  resources,
	}

	if err := enc.Encode(response); err != nil {
		h.log.Error(ErrFetchMeshSyncResources(err))
		http.Error(rw, ErrFetchMeshSyncResources(err).Error(), http.StatusInternalServerError)
	}
}

func (h *Handler) GetMeshSyncResources(rw http.ResponseWriter, r *http.Request, _ *models.Preference, _ *models.User, provider models.Provider) {
	rw.Header().Set("Content-Type", "application/json")
	enc := json.NewEncoder(rw)

	var resources []model.KubernetesResource
	var totalCount int64
	limitstr := r.URL.Query().Get("pagesize")
	var limit int
	if limitstr != "all" {
		limit, _ = strconv.Atoi(limitstr)
		if limit <= 0 {
			limit = defaultPageSize
		}
	}

	pagestr := r.URL.Query().Get("page")
	page, _ := strconv.Atoi(pagestr)

	if page <= 0 {
		page = 1
	}

	offset := (page - 1) * limit
	order := r.URL.Query().Get("order")
	sort := r.URL.Query().Get("sort")
	search := r.URL.Query().Get("search")
	apiVersion := r.URL.Query().Get("apiVersion")
	spec, _ := strconv.ParseBool(r.URL.Query().Get("spec"))
	status, _ := strconv.ParseBool(r.URL.Query().Get("status"))
	isAnnotaion, _ := strconv.ParseBool(r.URL.Query().Get("annotations"))
	isLabels, _ := strconv.ParseBool(r.URL.Query().Get("labels"))
	kind := r.URL.Query().Get("kind")

	filter := struct {
		ClusterIds []string `json:"clusterIds"`
	}{}

	clusterIds := r.URL.Query().Get("clusterIds")
	if clusterIds != "" {
		err := json.Unmarshal([]byte(clusterIds), &filter.ClusterIds)
		if err != nil {
			h.log.Error(ErrFetchMeshSyncResources(err))
			http.Error(rw, ErrFetchMeshSyncResources(err).Error(), http.StatusInternalServerError)
			return
		}
	} else {
		filter.ClusterIds = []string{}
	}

	result := provider.GetGenericPersister().Model(&model.KubernetesResource{}).
		Preload("KubernetesResourceMeta").
		Where("kubernetes_resources.cluster_id IN (?)", filter.ClusterIds)

	if kind != "" {
		result = result.Where(&model.KubernetesResource{Kind: kind})
	}

	if apiVersion != "" {
		result = result.Where(&model.KubernetesResource{APIVersion: apiVersion})
	}

	if isLabels {
		result = result.Preload("KubernetesResourceMeta.Labels", "kind = ?", model.KindLabel)
	}
	if isAnnotaion {
		result = result.Preload("KubernetesResourceMeta.Annotations", "kind = ?", model.KindAnnotation)
	}

	if spec {
		result = result.Preload("Spec")
	}

	if status {
		result = result.Preload("Status")
	}

	if search != "" {

		result = result.
			Joins("JOIN kubernetes_resource_object_meta ON kubernetes_resource_object_meta.id = kubernetes_resources.id").
			Where("kubernetes_resource_object_meta.name LIKE ?", "%"+search+"%")
	}

	result.Count(&totalCount)

	if limit != 0 {
		result = result.Limit(limit)
	}

	if offset != 0 {
		result = result.Offset(offset)
	}

	order = models.SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
	if order != "" {
		if sort == "desc" {
			result = result.Order(clause.OrderByColumn{Column: clause.Column{Name: order}, Desc: true})
		} else {
			result = result.Order(order)
		}
	}

	err := result.Find(&resources).Error
	if err != nil {
		h.log.Error(ErrFetchMeshSyncResources(err))
		http.Error(rw, ErrFetchMeshSyncResources(err).Error(), http.StatusInternalServerError)
		return
	}

	var pgSize int
	if limitstr == "all" {
		pgSize = len(resources)
	} else {
		pgSize = limit
	}

	response := &models.MeshSyncResourcesAPIResponse{
		Page:       page,
		PageSize:   pgSize,
		TotalCount: totalCount,
		Resources:  resources,
	}

	if err := enc.Encode(response); err != nil {
		h.log.Error(ErrFetchMeshSyncResources(err))
		http.Error(rw, ErrFetchMeshSyncResources(err).Error(), http.StatusInternalServerError)
	}
}

--- a/server/handlers/meshsync_handler.go
+++ b/server/handlers/meshsync_handler.go
@@ -91,6 +91,7 @@
 		result = result.Offset(offset)
 	}

+	order = models.SanitizeOrderInput(order, []string{"created_at", "updated_at", "name"})
 	if order != "" {
 		if sort == "desc" {
 			result = result.Order(clause.OrderByColumn{Column: clause.Column{Name: order}, Desc: true})


HashiCorp Vault and Vault Enterprise versions 0.9.0 through 1.3.3 may, under certain circumstances, have an Entity's Group membership inadvertently include Groups the Entity no longer has permissions to. Fixed in 1.3.4.

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.

Execution with Unnecessary Privileges

The product performs an operation at a privilege level that is higher than the minimum level required, which creates new weaknesses or amplifies the consequences of other weaknesses.

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 (m *ExpirationManager) RenewToken(ctx context.Context, req *logical.Request, te *logical.TokenEntry,
	increment time.Duration) (*logical.Response, error) {
	defer metrics.MeasureSince([]string{"expire", "renew-token"}, time.Now())

	tokenNS, err := NamespaceByID(ctx, te.NamespaceID, m.core)
	if err != nil {
		return nil, err
	}
	if tokenNS == nil {
		return nil, namespace.ErrNoNamespace
	}

	ns, err := namespace.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	if ns.ID != tokenNS.ID {
		return nil, errors.New("cannot renew a token across namespaces")
	}

	// Compute the Lease ID
	saltedID, err := m.tokenStore.SaltID(ctx, te.ID)
	if err != nil {
		return nil, err
	}

	leaseID := path.Join(te.Path, saltedID)

	if ns.ID != namespace.RootNamespaceID {
		leaseID = fmt.Sprintf("%s.%s", leaseID, ns.ID)
	}

	// Load the entry
	le, err := m.loadEntry(ctx, leaseID)
	if err != nil {
		return nil, err
	}
	if le == nil {
		return logical.ErrorResponse("invalid lease ID"), logical.ErrInvalidRequest
	}

	// Check if the lease is renewable. Note that this also checks for a nil
	// lease and errors in that case as well.
	if _, err := le.renewable(); err != nil {
		return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
	}

	// Attempt to renew the auth entry
	resp, err := m.renewAuthEntry(ctx, req, le, increment)
	if err != nil {
		return nil, err
	}
	if resp == nil {
		return nil, nil
	}
	if resp.IsError() {
		return &logical.Response{
			Data: resp.Data,
		}, nil
	}
	if resp.Auth == nil {
		return nil, nil
	}

	sysViewCtx := namespace.ContextWithNamespace(ctx, le.namespace)
	sysView := m.router.MatchingSystemView(sysViewCtx, le.Path)
	if sysView == nil {
		return nil, fmt.Errorf("unable to retrieve system view from router")
	}

	ttl, warnings, err := framework.CalculateTTL(sysView, increment, resp.Auth.TTL, resp.Auth.Period, resp.Auth.MaxTTL, resp.Auth.ExplicitMaxTTL, le.IssueTime)
	if err != nil {
		return nil, err
	}
	retResp := &logical.Response{}
	for _, warning := range warnings {
		retResp.AddWarning(warning)
	}
	resp.Auth.TTL = ttl

	// Attach the ClientToken
	resp.Auth.ClientToken = te.ID

	// Refresh groups
	if resp.Auth.EntityID != "" &&
		resp.Auth.GroupAliases != nil &&
		m.core.identityStore != nil {
		validAliases, err := m.core.identityStore.refreshExternalGroupMembershipsByEntityID(ctx, resp.Auth.EntityID, resp.Auth.GroupAliases)
		if err != nil {
			return nil, err
		}
		resp.Auth.GroupAliases = validAliases
	}

	// Update the lease entry
	le.Auth = resp.Auth
	le.ExpireTime = resp.Auth.ExpirationTime()
	le.LastRenewalTime = time.Now()

	{
		m.pendingLock.Lock()
		if err := m.persistEntry(ctx, le); err != nil {
			m.pendingLock.Unlock()
			return nil, err
		}

		// Update the expiration time
		m.updatePendingInternal(le, resp.Auth.LeaseTotal())
		m.pendingLock.Unlock()
	}

	retResp.Auth = resp.Auth
	return retResp, nil
}

func (m *ExpirationManager) RenewToken(ctx context.Context, req *logical.Request, te *logical.TokenEntry,
	increment time.Duration) (*logical.Response, error) {
	defer metrics.MeasureSince([]string{"expire", "renew-token"}, time.Now())

	tokenNS, err := NamespaceByID(ctx, te.NamespaceID, m.core)
	if err != nil {
		return nil, err
	}
	if tokenNS == nil {
		return nil, namespace.ErrNoNamespace
	}

	ns, err := namespace.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	if ns.ID != tokenNS.ID {
		return nil, errors.New("cannot renew a token across namespaces")
	}

	// Compute the Lease ID
	saltedID, err := m.tokenStore.SaltID(ctx, te.ID)
	if err != nil {
		return nil, err
	}

	leaseID := path.Join(te.Path, saltedID)

	if ns.ID != namespace.RootNamespaceID {
		leaseID = fmt.Sprintf("%s.%s", leaseID, ns.ID)
	}

	// Load the entry
	le, err := m.loadEntry(ctx, leaseID)
	if err != nil {
		return nil, err
	}
	if le == nil {
		return logical.ErrorResponse("invalid lease ID"), logical.ErrInvalidRequest
	}

	// Check if the lease is renewable. Note that this also checks for a nil
	// lease and errors in that case as well.
	if _, err := le.renewable(); err != nil {
		return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
	}

	// Attempt to renew the auth entry
	resp, err := m.renewAuthEntry(ctx, req, le, increment)
	if err != nil {
		return nil, err
	}
	if resp == nil {
		return nil, nil
	}
	if resp.IsError() {
		return &logical.Response{
			Data: resp.Data,
		}, nil
	}
	if resp.Auth == nil {
		return nil, nil
	}

	sysViewCtx := namespace.ContextWithNamespace(ctx, le.namespace)
	sysView := m.router.MatchingSystemView(sysViewCtx, le.Path)
	if sysView == nil {
		return nil, fmt.Errorf("unable to retrieve system view from router")
	}

	ttl, warnings, err := framework.CalculateTTL(sysView, increment, resp.Auth.TTL, resp.Auth.Period, resp.Auth.MaxTTL, resp.Auth.ExplicitMaxTTL, le.IssueTime)
	if err != nil {
		return nil, err
	}
	retResp := &logical.Response{}
	for _, warning := range warnings {
		retResp.AddWarning(warning)
	}
	resp.Auth.TTL = ttl

	// Attach the ClientToken
	resp.Auth.ClientToken = te.ID

	// Refresh groups
	if resp.Auth.EntityID != "" && m.core.identityStore != nil {
		validAliases, err := m.core.identityStore.refreshExternalGroupMembershipsByEntityID(ctx, resp.Auth.EntityID, resp.Auth.GroupAliases)
		if err != nil {
			return nil, err
		}
		resp.Auth.GroupAliases = validAliases
	}

	// Update the lease entry
	le.Auth = resp.Auth
	le.ExpireTime = resp.Auth.ExpirationTime()
	le.LastRenewalTime = time.Now()

	{
		m.pendingLock.Lock()
		if err := m.persistEntry(ctx, le); err != nil {
			m.pendingLock.Unlock()
			return nil, err
		}

		// Update the expiration time
		m.updatePendingInternal(le, resp.Auth.LeaseTotal())
		m.pendingLock.Unlock()
	}

	retResp.Auth = resp.Auth
	return retResp, nil
}

--- a/vault/expiration.go
+++ b/vault/expiration.go
@@ -82,9 +82,7 @@
 	resp.Auth.ClientToken = te.ID

 	// Refresh groups
-	if resp.Auth.EntityID != "" &&
-		resp.Auth.GroupAliases != nil &&
-		m.core.identityStore != nil {
+	if resp.Auth.EntityID != "" && m.core.identityStore != nil {
 		validAliases, err := m.core.identityStore.refreshExternalGroupMembershipsByEntityID(ctx, resp.Auth.EntityID, resp.Auth.GroupAliases)
 		if err != nil {
 			return nil, err


Pritunl Client v1.2.2550.20 contains a local privilege escalation vulnerability in the pritunl-service component. The attack vector is: malicious openvpn config. A local attacker could leverage the log and log-append along with log injection to create or append to privileged script files and execute code as root/SYSTEM.

func (p *Profile) write() (pth string, err error) {
	rootDir, err := utils.GetTempDir()
	if err != nil {
		return
	}

	pth = filepath.Join(rootDir, p.Id)

	_ = os.Remove(pth)
	err = ioutil.WriteFile(pth, []byte(p.Data), os.FileMode(0600))
	if err != nil {
		err = &WriteError{
			errors.Wrap(err, "profile: Failed to write profile"),
		}
		return
	}

	return
}

func (p *Profile) write() (pth string, err error) {
	rootDir, err := utils.GetTempDir()
	if err != nil {
		return
	}

	pth = filepath.Join(rootDir, p.Id)

	data := ""
	for _, line := range strings.Split(p.Data, "\n") {
		trimLine := strings.TrimSpace(line)
		trimLine = strings.Trim(trimLine, "#")
		trimLine = strings.Trim(trimLine, "-")
		trimLine = strings.Trim(trimLine, "_")
		trimLine = strings.Trim(trimLine, ":")
		trimLine = strings.Trim(trimLine, ";")
		trimLine = strings.Trim(trimLine, "*")
		trimLine = strings.Trim(trimLine, "%")
		trimLine = strings.Trim(trimLine, "$")
		trimLine = strings.Trim(trimLine, "+")
		trimLine = strings.Trim(trimLine, "=")
		trimLine = strings.Trim(trimLine, "~")
		trimLine = strings.Trim(trimLine, "(")
		trimLine = strings.Trim(trimLine, ")")
		trimLine = strings.Trim(trimLine, "[")
		trimLine = strings.Trim(trimLine, "]")
		trimLine = strings.Trim(trimLine, "{")
		trimLine = strings.Trim(trimLine, "}")

		if strings.Contains(trimLine, "script-security") ||
			strings.HasPrefix(trimLine, "log ") ||
			strings.HasPrefix(trimLine, "up ") ||
			strings.HasPrefix(trimLine, "down ") ||
			strings.HasPrefix(trimLine, "route-pre-down ") ||
			strings.HasPrefix(trimLine, "tls-verify ") ||
			strings.HasPrefix(trimLine, "ipchange ") ||
			strings.HasPrefix(trimLine, "route-up ") {

			continue
		}
		data += line + "\n"
	}

	_ = os.Remove(pth)
	err = ioutil.WriteFile(pth, []byte(data), os.FileMode(0600))
	if err != nil {
		err = &WriteError{
			errors.Wrap(err, "profile: Failed to write profile"),
		}
		return
	}

	return
}

--- a/service/profile/profile.go
+++ b/service/profile/profile.go
@@ -6,8 +6,43 @@

 	pth = filepath.Join(rootDir, p.Id)

+	data := ""
+	for _, line := range strings.Split(p.Data, "\n") {
+		trimLine := strings.TrimSpace(line)
+		trimLine = strings.Trim(trimLine, "#")
+		trimLine = strings.Trim(trimLine, "-")
+		trimLine = strings.Trim(trimLine, "_")
+		trimLine = strings.Trim(trimLine, ":")
+		trimLine = strings.Trim(trimLine, ";")
+		trimLine = strings.Trim(trimLine, "*")
+		trimLine = strings.Trim(trimLine, "%")
+		trimLine = strings.Trim(trimLine, "$")
+		trimLine = strings.Trim(trimLine, "+")
+		trimLine = strings.Trim(trimLine, "=")
+		trimLine = strings.Trim(trimLine, "~")
+		trimLine = strings.Trim(trimLine, "(")
+		trimLine = strings.Trim(trimLine, ")")
+		trimLine = strings.Trim(trimLine, "[")
+		trimLine = strings.Trim(trimLine, "]")
+		trimLine = strings.Trim(trimLine, "{")
+		trimLine = strings.Trim(trimLine, "}")
+
+		if strings.Contains(trimLine, "script-security") ||
+			strings.HasPrefix(trimLine, "log ") ||
+			strings.HasPrefix(trimLine, "up ") ||
+			strings.HasPrefix(trimLine, "down ") ||
+			strings.HasPrefix(trimLine, "route-pre-down ") ||
+			strings.HasPrefix(trimLine, "tls-verify ") ||
+			strings.HasPrefix(trimLine, "ipchange ") ||
+			strings.HasPrefix(trimLine, "route-up ") {
+
+			continue
+		}
+		data += line + "\n"
+	}
+
 	_ = os.Remove(pth)
-	err = ioutil.WriteFile(pth, []byte(p.Data), os.FileMode(0600))
+	err = ioutil.WriteFile(pth, []byte(data), os.FileMode(0600))
 	if err != nil {
 		err = &WriteError{
 			errors.Wrap(err, "profile: Failed to write profile"),


Computer Vision Annotation Tool (CVAT) is an interactive video and image annotation tool for computer vision. CVAT allows users to supply custom endpoint URLs for cloud storages based on Amazon S3 and Azure Blob Storage. Starting in version 2.1.0 and prior to version 2.14.3, an attacker with a CVAT account can exploit this feature by specifying URLs whose host part is an intranet IP address or an internal domain name. By doing this, the attacker may be able to probe the network that the CVAT backend runs in for HTTP(S) servers. In addition, if there is a web server on this network that is sufficiently API-compatible with an Amazon S3 or Azure Blob Storage endpoint, and either allows anonymous access, or allows authentication with credentials that are known by the attacker, then the attacker may be able to create a cloud storage linked to this server. They may then be able to list files on the server; extract files from the server, if these files are of a type that CVAT supports reading from cloud storage (media data (such as images/videos/archives), importable annotations or datasets, task/project backups); and/or overwrite files on this server with exported annotations/datasets/backups. The exact capabilities of the attacker will depend on how the internal server is configured. Users should upgrade to CVAT 2.14.3 to receive a patch. In this release, the existing SSRF mitigation measures are applied to requests to cloud providers, with access to intranet IP addresses prohibited by default. Some workarounds are also available. One may use network security solutions such as virtual networks or firewalls to prohibit network access from the CVAT backend to unrelated servers on your internal network and/or require authentication for access to internal servers.

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 __init__(self,
                bucket: str,
                region: Optional[str] = None,
                access_key_id: Optional[str] = None,
                secret_key: Optional[str] = None,
                session_token: Optional[str] = None,
                endpoint_url: Optional[str] = None,
                prefix: Optional[str] = None,
    ):
        super().__init__(prefix=prefix)
        if (
            sum(
                1
                for credential in (access_key_id, secret_key, session_token)
                if credential
            )
            == 1
        ):
            raise Exception("Insufficient data for authentication")

        kwargs = dict()
        for key, arg_v in zip(
            (
                "aws_access_key_id",
                "aws_secret_access_key",
                "aws_session_token",
                "region_name",
            ),
            (access_key_id, secret_key, session_token, region),
        ):
            if arg_v:
                kwargs[key] = arg_v

        session = boto3.Session(**kwargs)
        self._s3 = session.resource("s3", endpoint_url=endpoint_url)

        # anonymous access
        if not any([access_key_id, secret_key, session_token]):
            self._s3.meta.client.meta.events.register(
                "choose-signer.s3.*", disable_signing
            )

        self._client = self._s3.meta.client
        self._bucket = self._s3.Bucket(bucket)
        self.region = region

    def __init__(
        self,
        container: str,
        account_name: Optional[str] = None,
        sas_token: Optional[str] = None,
        connection_string: Optional[str] = None,
        prefix: Optional[str] = None,
    ):
        super().__init__(prefix=prefix)
        self._account_name = account_name
        if connection_string:
            self._blob_service_client = BlobServiceClient.from_connection_string(connection_string)
        elif sas_token:
            self._blob_service_client = BlobServiceClient(account_url=self.account_url, credential=sas_token)
        else:
            self._blob_service_client = BlobServiceClient(account_url=self.account_url)
        self._client = self._blob_service_client.get_container_client(container)

    def __init__(self,
                bucket: str,
                region: Optional[str] = None,
                access_key_id: Optional[str] = None,
                secret_key: Optional[str] = None,
                session_token: Optional[str] = None,
                endpoint_url: Optional[str] = None,
                prefix: Optional[str] = None,
    ):
        super().__init__(prefix=prefix)
        if (
            sum(
                1
                for credential in (access_key_id, secret_key, session_token)
                if credential
            )
            == 1
        ):
            raise Exception("Insufficient data for authentication")

        kwargs = dict()
        for key, arg_v in zip(
            (
                "aws_access_key_id",
                "aws_secret_access_key",
                "aws_session_token",
                "region_name",
            ),
            (access_key_id, secret_key, session_token, region),
        ):
            if arg_v:
                kwargs[key] = arg_v

        session = boto3.Session(**kwargs)
        self._s3 = session.resource("s3", endpoint_url=endpoint_url,
            config=Config(proxies=PROXIES_FOR_UNTRUSTED_URLS or {}),
        )

        # anonymous access
        if not any([access_key_id, secret_key, session_token]):
            self._s3.meta.client.meta.events.register(
                "choose-signer.s3.*", disable_signing
            )

        self._client = self._s3.meta.client
        self._bucket = self._s3.Bucket(bucket)
        self.region = region

    def __init__(
        self,
        container: str,
        account_name: Optional[str] = None,
        sas_token: Optional[str] = None,
        connection_string: Optional[str] = None,
        prefix: Optional[str] = None,
    ):
        super().__init__(prefix=prefix)
        self._account_name = account_name
        if connection_string:
            self._blob_service_client = BlobServiceClient.from_connection_string(
                connection_string, proxies=PROXIES_FOR_UNTRUSTED_URLS)
        elif sas_token:
            self._blob_service_client = BlobServiceClient(
                account_url=self.account_url, credential=sas_token, proxies=PROXIES_FOR_UNTRUSTED_URLS)
        else:
            self._blob_service_client = BlobServiceClient(
                account_url=self.account_url, proxies=PROXIES_FOR_UNTRUSTED_URLS)
        self._client = self._blob_service_client.get_container_client(container)

--- a/cvat/apps/engine/cloud_provider.py
+++ b/cvat/apps/engine/cloud_provider.py
@@ -32,7 +32,9 @@
                 kwargs[key] = arg_v

         session = boto3.Session(**kwargs)
-        self._s3 = session.resource("s3", endpoint_url=endpoint_url)
+        self._s3 = session.resource("s3", endpoint_url=endpoint_url,
+            config=Config(proxies=PROXIES_FOR_UNTRUSTED_URLS or {}),
+        )

         # anonymous access
         if not any([access_key_id, secret_key, session_token]):

--- a/cvat/apps/engine/cloud_provider.py
+++ b/cvat/apps/engine/cloud_provider.py
@@ -9,9 +9,12 @@
         super().__init__(prefix=prefix)
         self._account_name = account_name
         if connection_string:
-            self._blob_service_client = BlobServiceClient.from_connection_string(connection_string)
+            self._blob_service_client = BlobServiceClient.from_connection_string(
+                connection_string, proxies=PROXIES_FOR_UNTRUSTED_URLS)
         elif sas_token:
-            self._blob_service_client = BlobServiceClient(account_url=self.account_url, credential=sas_token)
+            self._blob_service_client = BlobServiceClient(
+                account_url=self.account_url, credential=sas_token, proxies=PROXIES_FOR_UNTRUSTED_URLS)
         else:
-            self._blob_service_client = BlobServiceClient(account_url=self.account_url)
+            self._blob_service_client = BlobServiceClient(
+                account_url=self.account_url, proxies=PROXIES_FOR_UNTRUSTED_URLS)
         self._client = self._blob_service_client.get_container_client(container)


HashiCorp Consul and Consul Enterprise 1.13.0 up to 1.13.3 do not filter cluster filtering's imported nodes and services for HTTP or RPC endpoints used by the UI. Fixed in 1.14.0.

Missing Authorization

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

Authorization Bypass Through User-Controlled Key

The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data.

func (f *Filter) Filter(subject any) {
	switch v := subject.(type) {
	case *structs.CheckServiceNodes:
		f.filterCheckServiceNodes(v)

	case *structs.IndexedCheckServiceNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterCheckServiceNodes(&v.Nodes)

	case *structs.PreparedQueryExecuteResponse:
		v.QueryMeta.ResultsFilteredByACLs = f.filterCheckServiceNodes(&v.Nodes)

	case *structs.IndexedServiceTopology:
		filtered := f.filterServiceTopology(v.ServiceTopology)
		if filtered {
			v.FilteredByACLs = true
			v.QueryMeta.ResultsFilteredByACLs = true
		}

	case *structs.DatacenterIndexedCheckServiceNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterDatacenterCheckServiceNodes(&v.DatacenterNodes)

	case *structs.IndexedCoordinates:
		v.QueryMeta.ResultsFilteredByACLs = f.filterCoordinates(&v.Coordinates)

	case *structs.IndexedHealthChecks:
		v.QueryMeta.ResultsFilteredByACLs = f.filterHealthChecks(&v.HealthChecks)

	case *structs.IndexedIntentions:
		v.QueryMeta.ResultsFilteredByACLs = f.filterIntentions(&v.Intentions)

	case *structs.IndexedNodeDump:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodeDump(&v.Dump)

	case *structs.IndexedServiceDump:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServiceDump(&v.Dump)

	case *structs.IndexedNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodes(&v.Nodes)

	case *structs.IndexedNodeServices:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodeServices(&v.NodeServices)

	case *structs.IndexedNodeServiceList:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodeServiceList(&v.NodeServices)

	case *structs.IndexedServiceNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServiceNodes(&v.ServiceNodes)

	case *structs.IndexedServices:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServices(v.Services, &v.EnterpriseMeta)

	case *structs.IndexedSessions:
		v.QueryMeta.ResultsFilteredByACLs = f.filterSessions(&v.Sessions)

	case *structs.IndexedPreparedQueries:
		v.QueryMeta.ResultsFilteredByACLs = f.filterPreparedQueries(&v.Queries)

	case **structs.PreparedQuery:
		f.redactPreparedQueryTokens(v)

	case *structs.ACLTokens:
		f.filterTokens(v)
	case **structs.ACLToken:
		f.filterToken(v)
	case *[]*structs.ACLTokenListStub:
		f.filterTokenStubs(v)
	case **structs.ACLTokenListStub:
		f.filterTokenStub(v)

	case *structs.ACLPolicies:
		f.filterPolicies(v)
	case **structs.ACLPolicy:
		f.filterPolicy(v)

	case *structs.ACLRoles:
		f.filterRoles(v)
	case **structs.ACLRole:
		f.filterRole(v)

	case *structs.ACLBindingRules:
		f.filterBindingRules(v)
	case **structs.ACLBindingRule:
		f.filterBindingRule(v)

	case *structs.ACLAuthMethods:
		f.filterAuthMethods(v)
	case **structs.ACLAuthMethod:
		f.filterAuthMethod(v)

	case *structs.IndexedServiceList:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServiceList(&v.Services)

	case *structs.IndexedExportedServiceList:
		for peer, peerServices := range v.Services {
			v.QueryMeta.ResultsFilteredByACLs = f.filterServiceList(&peerServices)
			if len(peerServices) == 0 {
				delete(v.Services, peer)
			} else {
				v.Services[peer] = peerServices
			}
		}

	case *structs.IndexedGatewayServices:
		v.QueryMeta.ResultsFilteredByACLs = f.filterGatewayServices(&v.Services)

	case *structs.IndexedNodesWithGateways:
		if f.filterCheckServiceNodes(&v.Nodes) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}
		if f.filterGatewayServices(&v.Gateways) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}
		if f.filterCheckServiceNodes(&v.ImportedNodes) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}

	default:
		panic(fmt.Errorf("Unhandled type passed to ACL filter: %T %#v", subject, subject))
	}
}

func (f *Filter) Filter(subject any) {
	switch v := subject.(type) {
	case *structs.CheckServiceNodes:
		f.filterCheckServiceNodes(v)

	case *structs.IndexedCheckServiceNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterCheckServiceNodes(&v.Nodes)

	case *structs.PreparedQueryExecuteResponse:
		v.QueryMeta.ResultsFilteredByACLs = f.filterCheckServiceNodes(&v.Nodes)

	case *structs.IndexedServiceTopology:
		filtered := f.filterServiceTopology(v.ServiceTopology)
		if filtered {
			v.FilteredByACLs = true
			v.QueryMeta.ResultsFilteredByACLs = true
		}

	case *structs.DatacenterIndexedCheckServiceNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterDatacenterCheckServiceNodes(&v.DatacenterNodes)

	case *structs.IndexedCoordinates:
		v.QueryMeta.ResultsFilteredByACLs = f.filterCoordinates(&v.Coordinates)

	case *structs.IndexedHealthChecks:
		v.QueryMeta.ResultsFilteredByACLs = f.filterHealthChecks(&v.HealthChecks)

	case *structs.IndexedIntentions:
		v.QueryMeta.ResultsFilteredByACLs = f.filterIntentions(&v.Intentions)

	case *structs.IndexedNodeDump:
		if f.filterNodeDump(&v.Dump) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}
		if f.filterNodeDump(&v.ImportedDump) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}

	case *structs.IndexedServiceDump:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServiceDump(&v.Dump)

	case *structs.IndexedNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodes(&v.Nodes)

	case *structs.IndexedNodeServices:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodeServices(&v.NodeServices)

	case *structs.IndexedNodeServiceList:
		v.QueryMeta.ResultsFilteredByACLs = f.filterNodeServiceList(&v.NodeServices)

	case *structs.IndexedServiceNodes:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServiceNodes(&v.ServiceNodes)

	case *structs.IndexedServices:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServices(v.Services, &v.EnterpriseMeta)

	case *structs.IndexedSessions:
		v.QueryMeta.ResultsFilteredByACLs = f.filterSessions(&v.Sessions)

	case *structs.IndexedPreparedQueries:
		v.QueryMeta.ResultsFilteredByACLs = f.filterPreparedQueries(&v.Queries)

	case **structs.PreparedQuery:
		f.redactPreparedQueryTokens(v)

	case *structs.ACLTokens:
		f.filterTokens(v)
	case **structs.ACLToken:
		f.filterToken(v)
	case *[]*structs.ACLTokenListStub:
		f.filterTokenStubs(v)
	case **structs.ACLTokenListStub:
		f.filterTokenStub(v)

	case *structs.ACLPolicies:
		f.filterPolicies(v)
	case **structs.ACLPolicy:
		f.filterPolicy(v)

	case *structs.ACLRoles:
		f.filterRoles(v)
	case **structs.ACLRole:
		f.filterRole(v)

	case *structs.ACLBindingRules:
		f.filterBindingRules(v)
	case **structs.ACLBindingRule:
		f.filterBindingRule(v)

	case *structs.ACLAuthMethods:
		f.filterAuthMethods(v)
	case **structs.ACLAuthMethod:
		f.filterAuthMethod(v)

	case *structs.IndexedServiceList:
		v.QueryMeta.ResultsFilteredByACLs = f.filterServiceList(&v.Services)

	case *structs.IndexedExportedServiceList:
		for peer, peerServices := range v.Services {
			v.QueryMeta.ResultsFilteredByACLs = f.filterServiceList(&peerServices)
			if len(peerServices) == 0 {
				delete(v.Services, peer)
			} else {
				v.Services[peer] = peerServices
			}
		}

	case *structs.IndexedGatewayServices:
		v.QueryMeta.ResultsFilteredByACLs = f.filterGatewayServices(&v.Services)

	case *structs.IndexedNodesWithGateways:
		if f.filterCheckServiceNodes(&v.Nodes) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}
		if f.filterGatewayServices(&v.Gateways) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}
		if f.filterCheckServiceNodes(&v.ImportedNodes) {
			v.QueryMeta.ResultsFilteredByACLs = true
		}

	default:
		panic(fmt.Errorf("Unhandled type passed to ACL filter: %T %#v", subject, subject))
	}
}

--- a/agent/structs/aclfilter/filter.go
+++ b/agent/structs/aclfilter/filter.go
@@ -29,7 +29,12 @@
 		v.QueryMeta.ResultsFilteredByACLs = f.filterIntentions(&v.Intentions)

 	case *structs.IndexedNodeDump:
-		v.QueryMeta.ResultsFilteredByACLs = f.filterNodeDump(&v.Dump)
+		if f.filterNodeDump(&v.Dump) {
+			v.QueryMeta.ResultsFilteredByACLs = true
+		}
+		if f.filterNodeDump(&v.ImportedDump) {
+			v.QueryMeta.ResultsFilteredByACLs = true
+		}

 	case *structs.IndexedServiceDump:
 		v.QueryMeta.ResultsFilteredByACLs = f.filterServiceDump(&v.Dump)

--- a/agent/structs/structs_oss.go
+++ /dev/null
@@ -1 +0,0 @@
-func (_ *NodeInfo) FillAuthzContext(_ *acl.AuthorizerContext) {}

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

cd /workspace/consul
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -timeout 30s -run ^TestACL_filterNodeDump$ github.com/hashicorp/consul/agent/structs/aclfilter