Uptime Kuma is an open source, self-hosted monitoring tool. An **Improper URL Handling Vulnerability** allows an attacker to access sensitive local files on the server by exploiting the `file:///` protocol. This vulnerability is triggered via the **"real-browser"** request type, which takes a screenshot of the URL provided by the attacker. By supplying local file paths, such as `file:///etc/passwd`, an attacker can read sensitive data from the server. This vulnerability arises because the system does not properly validate or sanitize the user input for the URL field. Specifically: 1. The URL input (`<input data-v-5f5c86d7="" id="url" type="url" class="form-control" pattern="https?://.+" required="">`) allows users to input arbitrary file paths, including those using the `file:///` protocol, without server-side validation. 2. The server then uses the user-provided URL to make a request, passing it to a browser instance that performs the "real-browser" request, which takes a screenshot of the content at the given URL. If a local file path is entered (e.g., `file:///etc/passwd`), the browser fetches and captures the file’s content. Since the user input is not validated, an attacker can manipulate the URL to request local files (e.g., `file:///etc/passwd`), and the system will capture a screenshot of the file's content, potentially exposing sensitive data. Any **authenticated user** who can submit a URL in "real-browser" mode is at risk of exposing sensitive data through screenshots of these files. This issue has been addressed in version 1.23.16 and all users are advised to upgrade. There are no known workarounds for this vulnerability.

class RealBrowserMonitorType extends MonitorType {

    name = "real-browser";

    async check(monitor, heartbeat, server) {
        const browser = await getBrowser();
        const context = await browser.newContext();
        const page = await context.newPage();

        const res = await page.goto(monitor.url, {
            waitUntil: "networkidle",
            timeout: monitor.interval * 1000 * 0.8,
        });

        let filename = jwt.sign(monitor.id, server.jwtSecret) + ".png";

        await page.screenshot({
            path: path.join(Database.screenshotDir, filename),
        });

        await context.close();

        if (res.status() >= 200 && res.status() < 400) {
            heartbeat.status = UP;
            heartbeat.msg = res.status();

            const timing = res.request().timing();
            heartbeat.ping = timing.responseEnd;
        } else {
            throw new Error(res.status() + "");
        }
    }
}

class RealBrowserMonitorType extends MonitorType {

    name = "real-browser";

    async check(monitor, heartbeat, server) {
        const browser = await getBrowser();
        const context = await browser.newContext();
        const page = await context.newPage();

        // Prevent Local File Inclusion
        // Accept only http:// and https://
        // https://github.com/louislam/uptime-kuma/security/advisories/GHSA-2qgm-m29m-cj2h
        let url = new URL(monitor.url);
        if (url.protocol !== "http:" && url.protocol !== "https:") {
            throw new Error("Invalid url protocol, only http and https are allowed.");
        }

        const res = await page.goto(monitor.url, {
            waitUntil: "networkidle",
            timeout: monitor.interval * 1000 * 0.8,
        });

        let filename = jwt.sign(monitor.id, server.jwtSecret) + ".png";

        await page.screenshot({
            path: path.join(Database.screenshotDir, filename),
        });

        await context.close();

        if (res.status() >= 200 && res.status() < 400) {
            heartbeat.status = UP;
            heartbeat.msg = res.status();

            const timing = res.request().timing();
            heartbeat.ping = timing.responseEnd;
        } else {
            throw new Error(res.status() + "");
        }
    }
}

--- a/server/monitor-types/real-browser-monitor-type.js
+++ b/server/monitor-types/real-browser-monitor-type.js
@@ -6,6 +6,14 @@
         const browser = await getBrowser();
         const context = await browser.newContext();
         const page = await context.newPage();
+
+        // Prevent Local File Inclusion
+        // Accept only http:// and https://
+        // https://github.com/louislam/uptime-kuma/security/advisories/GHSA-2qgm-m29m-cj2h
+        let url = new URL(monitor.url);
+        if (url.protocol !== "http:" && url.protocol !== "https:") {
+            throw new Error("Invalid url protocol, only http and https are allowed.");
+        }

         const res = await page.goto(monitor.url, {
             waitUntil: "networkidle",


A path traversal vulnerability exists in mlflow/mlflow version 2.11.0, identified as a bypass for the previously addressed CVE-2023-6909. The vulnerability arises from the application's handling of artifact URLs, where a '#' character can be used to insert a path into the fragment, effectively skipping validation. This allows an attacker to construct a URL that, when processed, ignores the protocol scheme and uses the provided path for filesystem access. As a result, an attacker can read arbitrary files, including sensitive information such as SSH and cloud keys, by exploiting the way the application converts the URL into a filesystem path. The issue stems from insufficient validation of the fragment portion of the URL, leading to arbitrary file read through path traversal.

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 _create_experiment():
    request_message = _get_request_message(
        CreateExperiment(),
        schema={
            "name": [_assert_required, _assert_string],
            "artifact_location": [_assert_string],
            "tags": [_assert_array],
        },
    )

    tags = [ExperimentTag(tag.key, tag.value) for tag in request_message.tags]

    # Validate query string in artifact location to prevent attacks
    parsed_artifact_locaion = urllib.parse.urlparse(request_message.artifact_location)
    validate_query_string(parsed_artifact_locaion.query)

    experiment_id = _get_tracking_store().create_experiment(
        request_message.name, request_message.artifact_location, tags
    )
    response_message = CreateExperiment.Response()
    response_message.experiment_id = experiment_id
    response = Response(mimetype="application/json")
    response.set_data(message_to_json(response_message))
    return response

def _validate_source(source: str, run_id: str) -> None:
    if is_local_uri(source):
        if run_id:
            store = _get_tracking_store()
            run = store.get_run(run_id)
            source = pathlib.Path(local_file_uri_to_path(source)).resolve()
            run_artifact_dir = pathlib.Path(local_file_uri_to_path(run.info.artifact_uri)).resolve()
            if run_artifact_dir in [source, *source.parents]:
                return

        raise MlflowException(
            f"Invalid model version source: '{source}'. To use a local path as a model version "
            "source, the run_id request parameter has to be specified and the local path has to be "
            "contained within the artifact directory of the run specified by the run_id.",
            INVALID_PARAMETER_VALUE,
        )

    # Checks if relative paths are present in the source (a security threat). If any are present,
    # raises an Exception.
    _validate_non_local_source_contains_relative_paths(source)

def _create_experiment():
    request_message = _get_request_message(
        CreateExperiment(),
        schema={
            "name": [_assert_required, _assert_string],
            "artifact_location": [_assert_string],
            "tags": [_assert_array],
        },
    )

    tags = [ExperimentTag(tag.key, tag.value) for tag in request_message.tags]

    # Validate query string in artifact location to prevent attacks
    parsed_artifact_locaion = urllib.parse.urlparse(request_message.artifact_location)
    if parsed_artifact_locaion.fragment:
        raise MlflowException(
            "'artifact_location' URL can't include fragment part.",
            error_code=INVALID_PARAMETER_VALUE,
        )
    validate_query_string(parsed_artifact_locaion.query)
    experiment_id = _get_tracking_store().create_experiment(
        request_message.name, request_message.artifact_location, tags
    )
    response_message = CreateExperiment.Response()
    response_message.experiment_id = experiment_id
    response = Response(mimetype="application/json")
    response.set_data(message_to_json(response_message))
    return response

def _validate_source(source: str, run_id: str) -> None:
    if is_local_uri(source):
        if run_id:
            store = _get_tracking_store()
            run = store.get_run(run_id)
            source = pathlib.Path(local_file_uri_to_path(source)).resolve()
            if is_local_uri(run.info.artifact_uri):
                run_artifact_dir = pathlib.Path(
                    local_file_uri_to_path(run.info.artifact_uri)
                ).resolve()
                if run_artifact_dir in [source, *source.parents]:
                    return

        raise MlflowException(
            f"Invalid model version source: '{source}'. To use a local path as a model version "
            "source, the run_id request parameter has to be specified and the local path has to be "
            "contained within the artifact directory of the run specified by the run_id.",
            INVALID_PARAMETER_VALUE,
        )

    # Checks if relative paths are present in the source (a security threat). If any are present,
    # raises an Exception.
    _validate_non_local_source_contains_relative_paths(source)

--- a/mlflow/server/handlers.py
+++ b/mlflow/server/handlers.py
@@ -12,8 +12,12 @@

     # Validate query string in artifact location to prevent attacks
     parsed_artifact_locaion = urllib.parse.urlparse(request_message.artifact_location)
+    if parsed_artifact_locaion.fragment:
+        raise MlflowException(
+            "'artifact_location' URL can't include fragment part.",
+            error_code=INVALID_PARAMETER_VALUE,
+        )
     validate_query_string(parsed_artifact_locaion.query)
-
     experiment_id = _get_tracking_store().create_experiment(
         request_message.name, request_message.artifact_location, tags
     )

--- a/mlflow/server/handlers.py
+++ b/mlflow/server/handlers.py
@@ -4,9 +4,12 @@
             store = _get_tracking_store()
             run = store.get_run(run_id)
             source = pathlib.Path(local_file_uri_to_path(source)).resolve()
-            run_artifact_dir = pathlib.Path(local_file_uri_to_path(run.info.artifact_uri)).resolve()
-            if run_artifact_dir in [source, *source.parents]:
-                return
+            if is_local_uri(run.info.artifact_uri):
+                run_artifact_dir = pathlib.Path(
+                    local_file_uri_to_path(run.info.artifact_uri)
+                ).resolve()
+                if run_artifact_dir in [source, *source.parents]:
+                    return

         raise MlflowException(
             f"Invalid model version source: '{source}'. To use a local path as a model version "

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

cd /workspace/mlflow
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
/workspace/PoC_env/CVE-2024-3848/bin/python -m pytest tests/server/test_handlers.py::test_local_file_read_write_by_pass_vulnerability -p no:warning --disable-warnings

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

cd /workspace/mlflow
git apply --whitespace=nowarn /workspace/fix.patch
/workspace/PoC_env/CVE-2024-3848/bin/python -m pytest tests/server/test_handlers.py -k "not test_mlflow_server_with_installed_plugin" -p no:warning --disable-warnings


Hertz v0.3.0 ws discovered to contain a path traversal vulnerability via the normalizePath function.

func normalizePath(dst, src []byte) []byte {
	dst = dst[:0]
	dst = addLeadingSlash(dst, src)
	dst = decodeArgAppendNoPlus(dst, src)

	// remove duplicate slashes
	b := dst
	bSize := len(b)
	for {
		n := bytes.Index(b, bytestr.StrSlashSlash)
		if n < 0 {
			break
		}
		b = b[n:]
		copy(b, b[1:])
		b = b[:len(b)-1]
		bSize--
	}
	dst = dst[:bSize]

	// remove /./ parts
	b = dst
	for {
		n := bytes.Index(b, bytestr.StrSlashDotSlash)
		if n < 0 {
			break
		}
		nn := n + len(bytestr.StrSlashDotSlash) - 1
		copy(b[n:], b[nn:])
		b = b[:len(b)-nn+n]
	}

	// remove /foo/../ parts
	for {
		n := bytes.Index(b, bytestr.StrSlashDotDotSlash)
		if n < 0 {
			break
		}
		nn := bytes.LastIndexByte(b[:n], '/')
		if nn < 0 {
			nn = 0
		}
		n += len(bytestr.StrSlashDotDotSlash) - 1
		copy(b[nn:], b[n:])
		b = b[:len(b)-n+nn]
	}

	// remove trailing /foo/..
	n := bytes.LastIndex(b, bytestr.StrSlashDotDot)
	if n >= 0 && n+len(bytestr.StrSlashDotDot) == len(b) {
		nn := bytes.LastIndexByte(b[:n], '/')
		if nn < 0 {
			return bytestr.StrSlash
		}
		b = b[:nn+1]
	}

	return b
}

func normalizePath(dst, src []byte) []byte {
	dst = dst[:0]
	dst = addLeadingSlash(dst, src)
	dst = decodeArgAppendNoPlus(dst, src)

	// Windows server need to replace all backslashes with
	// forward slashes to avoid path traversal attacks.
	if filepath.Separator == '\\' {
		for {
			n := bytes.IndexByte(dst, '\\')
			if n < 0 {
				break
			}
			dst[n] = '/'
		}
	}

	// remove duplicate slashes
	b := dst
	bSize := len(b)
	for {
		n := bytes.Index(b, bytestr.StrSlashSlash)
		if n < 0 {
			break
		}
		b = b[n:]
		copy(b, b[1:])
		b = b[:len(b)-1]
		bSize--
	}
	dst = dst[:bSize]

	// remove /./ parts
	b = dst
	for {
		n := bytes.Index(b, bytestr.StrSlashDotSlash)
		if n < 0 {
			break
		}
		nn := n + len(bytestr.StrSlashDotSlash) - 1
		copy(b[n:], b[nn:])
		b = b[:len(b)-nn+n]
	}

	// remove /foo/../ parts
	for {
		n := bytes.Index(b, bytestr.StrSlashDotDotSlash)
		if n < 0 {
			break
		}
		nn := bytes.LastIndexByte(b[:n], '/')
		if nn < 0 {
			nn = 0
		}
		n += len(bytestr.StrSlashDotDotSlash) - 1
		copy(b[nn:], b[n:])
		b = b[:len(b)-n+nn]
	}

	// remove trailing /foo/..
	n := bytes.LastIndex(b, bytestr.StrSlashDotDot)
	if n >= 0 && n+len(bytestr.StrSlashDotDot) == len(b) {
		nn := bytes.LastIndexByte(b[:n], '/')
		if nn < 0 {
			return bytestr.StrSlash
		}
		b = b[:nn+1]
	}

	return b
}

--- a/pkg/protocol/uri.go
+++ b/pkg/protocol/uri.go
@@ -2,6 +2,18 @@
 	dst = dst[:0]
 	dst = addLeadingSlash(dst, src)
 	dst = decodeArgAppendNoPlus(dst, src)
+
+	// Windows server need to replace all backslashes with
+	// forward slashes to avoid path traversal attacks.
+	if filepath.Separator == '\\' {
+		for {
+			n := bytes.IndexByte(dst, '\\')
+			if n < 0 {
+				break
+			}
+			dst[n] = '/'
+		}
+	}

 	// remove duplicate slashes
 	b := dst


Gradio is an open-source Python package that allows you to quickly build a demo or web application for your machine learning model, API, or any arbitary Python function. Versions of `gradio` prior to 4.11.0 contained a vulnerability in the `/file` route which made them susceptible to file traversal attacks in which an attacker could access arbitrary files on a machine running a Gradio app with a public URL (e.g. if the demo was created with `share=True`, or on Hugging Face Spaces) if they knew the path of files to look for. This issue has been patched in version 4.11.0.

        async def file(path_or_url: str, request: fastapi.Request):
            blocks = app.get_blocks()
            if utils.validate_url(path_or_url):
                return RedirectResponse(
                    url=path_or_url, status_code=status.HTTP_302_FOUND
                )
            abs_path = utils.abspath(path_or_url)

            in_blocklist = any(
                utils.is_in_or_equal(abs_path, blocked_path)
                for blocked_path in blocks.blocked_paths
            )
            is_dir = abs_path.is_dir()

            if in_blocklist or is_dir:
                raise HTTPException(403, f"File not allowed: {path_or_url}.")

            created_by_app = str(abs_path) in set().union(*blocks.temp_file_sets)
            in_allowlist = any(
                utils.is_in_or_equal(abs_path, allowed_path)
                for allowed_path in blocks.allowed_paths
            )
            was_uploaded = utils.is_in_or_equal(abs_path, app.uploaded_file_dir)
            is_cached_example = utils.is_in_or_equal(
                abs_path, utils.abspath(CACHED_FOLDER)
            )

            if not (
                created_by_app or in_allowlist or was_uploaded or is_cached_example
            ):
                raise HTTPException(403, f"File not allowed: {path_or_url}.")

            if not abs_path.exists():
                raise HTTPException(404, f"File not found: {path_or_url}.")

            range_val = request.headers.get("Range", "").strip()
            if range_val.startswith("bytes=") and "-" in range_val:
                range_val = range_val[6:]
                start, end = range_val.split("-")
                if start.isnumeric() and end.isnumeric():
                    start = int(start)
                    end = int(end)
                    response = ranged_response.RangedFileResponse(
                        abs_path,
                        ranged_response.OpenRange(start, end),
                        dict(request.headers),
                        stat_result=os.stat(abs_path),
                    )
                    return response

            return FileResponse(abs_path, headers={"Accept-Ranges": "bytes"})

        async def file(path_or_url: str, request: fastapi.Request):
            blocks = app.get_blocks()
            if client_utils.is_http_url_like(path_or_url):
                return RedirectResponse(
                    url=path_or_url, status_code=status.HTTP_302_FOUND
                )
            abs_path = utils.abspath(path_or_url)

            in_blocklist = any(
                utils.is_in_or_equal(abs_path, blocked_path)
                for blocked_path in blocks.blocked_paths
            )
            is_dir = abs_path.is_dir()

            if in_blocklist or is_dir:
                raise HTTPException(403, f"File not allowed: {path_or_url}.")

            created_by_app = str(abs_path) in set().union(*blocks.temp_file_sets)
            in_allowlist = any(
                utils.is_in_or_equal(abs_path, allowed_path)
                for allowed_path in blocks.allowed_paths
            )
            was_uploaded = utils.is_in_or_equal(abs_path, app.uploaded_file_dir)
            is_cached_example = utils.is_in_or_equal(
                abs_path, utils.abspath(CACHED_FOLDER)
            )

            if not (
                created_by_app or in_allowlist or was_uploaded or is_cached_example
            ):
                raise HTTPException(403, f"File not allowed: {path_or_url}.")

            if not abs_path.exists():
                raise HTTPException(404, f"File not found: {path_or_url}.")

            range_val = request.headers.get("Range", "").strip()
            if range_val.startswith("bytes=") and "-" in range_val:
                range_val = range_val[6:]
                start, end = range_val.split("-")
                if start.isnumeric() and end.isnumeric():
                    start = int(start)
                    end = int(end)
                    response = ranged_response.RangedFileResponse(
                        abs_path,
                        ranged_response.OpenRange(start, end),
                        dict(request.headers),
                        stat_result=os.stat(abs_path),
                    )
                    return response

            return FileResponse(abs_path, headers={"Accept-Ranges": "bytes"})

--- a/gradio/routes.py
+++ b/gradio/routes.py
@@ -1,6 +1,6 @@
         async def file(path_or_url: str, request: fastapi.Request):
             blocks = app.get_blocks()
-            if utils.validate_url(path_or_url):
+            if client_utils.is_http_url_like(path_or_url):
                 return RedirectResponse(
                     url=path_or_url, status_code=status.HTTP_302_FOUND
                 )


Sentry is an error tracking and performance monitoring platform. Starting in version 23.6.0 and prior to version 23.6.2, the Sentry API incorrectly returns the `access-control-allow-credentials: true` HTTP header if the `Origin` request header ends with the `system.base-hostname` option of Sentry installation. This only affects installations that have `system.base-hostname` option explicitly set, as it is empty by default. Impact is limited since recent versions of major browsers have cross-site cookie blocking enabled by default. However, this flaw could allow other multi-step attacks. The patch has been released in Sentry 23.6.2.

Incorrect Comparison

The product compares two entities in a security-relevant context, but the comparison is incorrect, which may lead to resultant weaknesses.

    def allow_cors_options_wrapper(self, request: Request, *args, **kwargs):
        if request.method == "OPTIONS":
            response = HttpResponse(status=200)
            response["Access-Control-Max-Age"] = "3600"  # don't ask for options again for 1 hour
        else:
            response = func(self, request, *args, **kwargs)

        allow = ", ".join(self._allowed_methods())
        response["Allow"] = allow
        response["Access-Control-Allow-Methods"] = allow
        response["Access-Control-Allow-Headers"] = (
            "X-Sentry-Auth, X-Requested-With, Origin, Accept, "
            "Content-Type, Authentication, Authorization, Content-Encoding, "
            "sentry-trace, baggage, X-CSRFToken"
        )
        response["Access-Control-Expose-Headers"] = "X-Sentry-Error, Retry-After"

        if request.META.get("HTTP_ORIGIN") == "null":
            origin = "null"  # if ORIGIN header is explicitly specified as 'null' leave it alone
        else:
            origin = origin_from_request(request)

        if origin is None or origin == "null":
            response["Access-Control-Allow-Origin"] = "*"
        else:
            response["Access-Control-Allow-Origin"] = origin

        # If the requesting origin is a subdomain of
        # the application's base-hostname we should allow cookies
        # to be sent.
        basehost = options.get("system.base-hostname")
        if basehost and origin:
            if origin.endswith(basehost):
                response["Access-Control-Allow-Credentials"] = "true"

        return response

    def allow_cors_options_wrapper(self, request: Request, *args, **kwargs):
        if request.method == "OPTIONS":
            response = HttpResponse(status=200)
            response["Access-Control-Max-Age"] = "3600"  # don't ask for options again for 1 hour
        else:
            response = func(self, request, *args, **kwargs)

        allow = ", ".join(self._allowed_methods())
        response["Allow"] = allow
        response["Access-Control-Allow-Methods"] = allow
        response["Access-Control-Allow-Headers"] = (
            "X-Sentry-Auth, X-Requested-With, Origin, Accept, "
            "Content-Type, Authentication, Authorization, Content-Encoding, "
            "sentry-trace, baggage, X-CSRFToken"
        )
        response["Access-Control-Expose-Headers"] = "X-Sentry-Error, Retry-After"

        if request.META.get("HTTP_ORIGIN") == "null":
            origin = "null"  # if ORIGIN header is explicitly specified as 'null' leave it alone
        else:
            origin = origin_from_request(request)

        if origin is None or origin == "null":
            response["Access-Control-Allow-Origin"] = "*"
        else:
            response["Access-Control-Allow-Origin"] = origin

        # If the requesting origin is a subdomain of
        # the application's base-hostname we should allow cookies
        # to be sent.
        basehost = options.get("system.base-hostname")
        if basehost and origin:
            if origin.endswith(("://" + basehost, "." + basehost)):
                response["Access-Control-Allow-Credentials"] = "true"

        return response

--- a/src/sentry/api/base.py
+++ b/src/sentry/api/base.py
@@ -30,7 +30,7 @@
         # to be sent.
         basehost = options.get("system.base-hostname")
         if basehost and origin:
-            if origin.endswith(basehost):
+            if origin.endswith(("://" + basehost, "." + basehost)):
                 response["Access-Control-Allow-Credentials"] = "true"

         return response


Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes. All Argo CD versions starting with 2.3.0-rc1 and prior to 2.3.17, 2.4.23  2.5.11, and 2.6.2 are vulnerable to an improper authorization bug which allows users who have the ability to update at least one cluster secret to update any cluster secret. The attacker could use this access to escalate privileges (potentially controlling Kubernetes resources) or to break Argo CD functionality (by preventing connections to external clusters). A patch for this vulnerability has been released in Argo CD versions 2.6.2, 2.5.11, 2.4.23, and 2.3.17. Two workarounds are available. Either modify the RBAC configuration to completely revoke all `clusters, update` access, or use the `destinations` and `clusterResourceWhitelist` fields to apply similar restrictions as the `namespaces` and `clusterResources` fields.

Incorrect Authorization

The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.

func (s *Server) Update(ctx context.Context, q *cluster.ClusterUpdateRequest) (*appv1.Cluster, error) {
	c, err := s.getClusterWith403IfNotExist(ctx, &cluster.ClusterQuery{
		Server: q.Cluster.Server,
		Name:   q.Cluster.Name,
		Id:     q.Id,
	})
	if err != nil {
		return nil, err
	}

	// verify that user can do update inside project where cluster is located
	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(c.Project, q.Cluster.Server)); err != nil {
		return nil, err
	}

	if len(q.UpdatedFields) == 0 || sets.NewString(q.UpdatedFields...).Has("project") {
		// verify that user can do update inside project where cluster will be located
		if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(q.Cluster.Project, q.Cluster.Server)); err != nil {
			return nil, err
		}
	}

	if len(q.UpdatedFields) != 0 {
		for _, path := range q.UpdatedFields {
			if updater, ok := clusterFieldsByPath[path]; ok {
				updater(c, q.Cluster)
			}
		}
		q.Cluster = c
	}

	// Test the token we just created before persisting it
	serverVersion, err := s.kubectl.GetServerVersion(q.Cluster.RESTConfig())
	if err != nil {
		return nil, err
	}

	clust, err := s.db.UpdateCluster(ctx, q.Cluster)
	if err != nil {
		return nil, err
	}
	err = s.cache.SetClusterInfo(clust.Server, &appv1.ClusterInfo{
		ServerVersion: serverVersion,
		ConnectionState: appv1.ConnectionState{
			Status:     appv1.ConnectionStatusSuccessful,
			ModifiedAt: &v1.Time{Time: time.Now()},
		},
	})
	if err != nil {
		return nil, err
	}
	return s.toAPIResponse(clust), nil
}

func (s *Server) Update(ctx context.Context, q *cluster.ClusterUpdateRequest) (*appv1.Cluster, error) {
	c, err := s.getClusterWith403IfNotExist(ctx, &cluster.ClusterQuery{
		Server: q.Cluster.Server,
		Name:   q.Cluster.Name,
		Id:     q.Id,
	})
	if err != nil {
		return nil, err
	}

	// verify that user can do update inside project where cluster is located
	if !s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(c.Project, c.Server)) {
		return nil, common.PermissionDeniedAPIError
	}

	if len(q.UpdatedFields) == 0 || sets.NewString(q.UpdatedFields...).Has("project") {
		// verify that user can do update inside project where cluster will be located
		if !s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(q.Cluster.Project, c.Server)) {
			return nil, common.PermissionDeniedAPIError
		}
	}

	if len(q.UpdatedFields) != 0 {
		for _, path := range q.UpdatedFields {
			if updater, ok := clusterFieldsByPath[path]; ok {
				updater(c, q.Cluster)
			}
		}
		q.Cluster = c
	}

	// Test the token we just created before persisting it
	serverVersion, err := s.kubectl.GetServerVersion(q.Cluster.RESTConfig())
	if err != nil {
		return nil, err
	}

	clust, err := s.db.UpdateCluster(ctx, q.Cluster)
	if err != nil {
		return nil, err
	}
	err = s.cache.SetClusterInfo(clust.Server, &appv1.ClusterInfo{
		ServerVersion: serverVersion,
		ConnectionState: appv1.ConnectionState{
			Status:     appv1.ConnectionStatusSuccessful,
			ModifiedAt: &v1.Time{Time: time.Now()},
		},
	})
	if err != nil {
		return nil, err
	}
	return s.toAPIResponse(clust), nil
}

--- a/server/cluster/cluster.go
+++ b/server/cluster/cluster.go
@@ -9,14 +9,14 @@
 	}

 	// verify that user can do update inside project where cluster is located
-	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(c.Project, q.Cluster.Server)); err != nil {
-		return nil, err
+	if !s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(c.Project, c.Server)) {
+		return nil, common.PermissionDeniedAPIError
 	}

 	if len(q.UpdatedFields) == 0 || sets.NewString(q.UpdatedFields...).Has("project") {
 		// verify that user can do update inside project where cluster will be located
-		if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(q.Cluster.Project, q.Cluster.Server)); err != nil {
-			return nil, err
+		if !s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(q.Cluster.Project, c.Server)) {
+			return nil, common.PermissionDeniedAPIError
 		}
 	}

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

cd /workspace/argo-cd
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -timeout 30s -run ^TestUpdateCluster_RejectInvalidParams$ github.com/argoproj/argo-cd/v2/server/cluster

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

cd /workspace/argo-cd
git apply --whitespace=nowarn /workspace/fix.patch
go test  -timeout 30s -run ^TestUpdateCluster_RejectInvalidParams$ github.com/argoproj/argo-cd/v2/server/cluster && go test -timeout 30s -run '^(TestDeleteClusterByName|TestRotateAuth|TestGetCluster_UrlEncodedName|TestGetCluster_NameWithUrlEncodingButShouldNotBeUnescaped|TestUpdateCluster_FieldsPathSet|TestUpdateCluster_NoFieldsPaths)$' github.com/argoproj/argo-cd/v2/server/cluster


Warpinator through 1.2.14 allows access outside of an intended directory, as demonstrated by symbolic directory links.

Improper Link Resolution Before File Access ('Link Following')

The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.

    def __init__(self, op):
        super(FileReceiver, self).__init__()
        self.save_path = prefs.get_save_path()
        self.op = op
        self.preserve_perms = prefs.preserve_permissions() and util.save_folder_is_native_fs()
        self.preserve_timestamp = prefs.preserve_timestamp() and util.save_folder_is_native_fs()

        self.current_path = None
        self.current_gfile = None
        self.current_type = None
        self.current_stream = None
        self.current_mode = 0
        self.current_mtime = 0
        self.current_mtime_usec = 0

        if op.existing:
            for name in op.top_dir_basenames:
                try:
                    path = os.path.join(self.save_path, name)
                    if os.path.isdir(path): # file not found is ok
                        shutil.rmtree(path)
                    else:
                        os.remove(path)
                except FileNotFoundError:
                    pass
                except Exception as e:
                    logging.warning("Problem removing existing files.  Transfer may not succeed: %s" % e)

        # We write files top-down.  If we're preserving permissions and we receive
        # a folder in some hierarchy that is not writable, we won't be able to create
        # anything inside it.
        self.folder_permission_change_list = []

    def receive_data(self, s):
        save_path = prefs.get_save_path()

        path = os.path.join(save_path, s.relative_path)
        if path != self.current_path:
            self.close_current_file()
            self.current_path = path
            self.current_mode = s.file_mode
            self.current_type = s.file_type
            self.current_mtime = s.time.mtime
            self.current_mtime_usec = s.time.mtime_usec

        if not self.current_gfile:
            self.current_gfile = Gio.File.new_for_path(path)

        if s.file_type == FileType.DIRECTORY:
            os.makedirs(path, exist_ok=True)
        elif s.file_type == FileType.SYMBOLIC_LINK:
            make_symbolic_link(self.op, path, s.symlink_target)
        else:
            if self.current_stream == None:
                flags = Gio.FileCreateFlags.REPLACE_DESTINATION
                self.current_stream = self.current_gfile.replace(None, False, flags, None)

            if not s.chunk:
                return

            self.current_stream.write_bytes(GLib.Bytes(s.chunk), None)
            self.op.progress_tracker.update_progress(len(s.chunk))

    def __init__(self, op):
        super(FileReceiver, self).__init__()
        self.save_path = prefs.get_save_path()
        self.save_path_file = Gio.File.new_for_path(self.save_path)
        self.op = op
        self.preserve_perms = prefs.preserve_permissions() and util.save_folder_is_native_fs()
        self.preserve_timestamp = prefs.preserve_timestamp() and util.save_folder_is_native_fs()

        self.current_path = None
        self.current_gfile = None
        self.current_type = None
        self.current_stream = None
        self.current_mode = 0
        self.current_mtime = 0
        self.current_mtime_usec = 0

        if op.existing:
            for name in op.top_dir_basenames:
                try:
                    path = os.path.join(self.save_path, name)
                    if os.path.isdir(path): # file not found is ok
                        shutil.rmtree(path)
                    else:
                        os.remove(path)
                except FileNotFoundError:
                    pass
                except Exception as e:
                    logging.warning("Problem removing existing files.  Transfer may not succeed: %s" % e)

        # We write files top-down.  If we're preserving permissions and we receive
        # a folder in some hierarchy that is not writable, we won't be able to create
        # anything inside it.
        self.folder_permission_change_list = []

    def receive_data(self, s):
        save_path = prefs.get_save_path()

        path = os.path.join(save_path, s.relative_path)
        if path != self.current_path:
            self.close_current_file()
            self.current_path = path
            self.current_mode = s.file_mode
            self.current_type = s.file_type
            self.current_mtime = s.time.mtime
            self.current_mtime_usec = s.time.mtime_usec

        if not self.current_gfile:
            self.current_gfile = Gio.File.new_for_path(path)
            # Check for valid path (GFile resolves paths upon creation).
            if self.save_path_file.get_relative_path(self.current_gfile) is None:
                raise Exception(_("Resolved path is not valid: %s -> %s") % (path, self.current_gfile.get_path()))

        if s.file_type == FileType.DIRECTORY:
            os.makedirs(path, exist_ok=True)
        elif s.file_type == FileType.SYMBOLIC_LINK:
            make_symbolic_link(self.op, path, s.symlink_target)
        else:
            if self.current_stream == None:
                flags = Gio.FileCreateFlags.REPLACE_DESTINATION
                self.current_stream = self.current_gfile.replace(None, False, flags, None)

            if not s.chunk:
                return

            self.current_stream.write_bytes(GLib.Bytes(s.chunk), None)
            self.op.progress_tracker.update_progress(len(s.chunk))

--- a/src/transfers.py
+++ b/src/transfers.py
@@ -1,6 +1,7 @@
     def __init__(self, op):
         super(FileReceiver, self).__init__()
         self.save_path = prefs.get_save_path()
+        self.save_path_file = Gio.File.new_for_path(self.save_path)
         self.op = op
         self.preserve_perms = prefs.preserve_permissions() and util.save_folder_is_native_fs()
         self.preserve_timestamp = prefs.preserve_timestamp() and util.save_folder_is_native_fs()

--- a/src/transfers.py
+++ b/src/transfers.py
@@ -12,6 +12,9 @@

         if not self.current_gfile:
             self.current_gfile = Gio.File.new_for_path(path)
+            # Check for valid path (GFile resolves paths upon creation).
+            if self.save_path_file.get_relative_path(self.current_gfile) is None:
+                raise Exception(_("Resolved path is not valid: %s -> %s") % (path, self.current_gfile.get_path()))

         if s.file_type == FileType.DIRECTORY:
             os.makedirs(path, exist_ok=True)


omr-admin.py in openmptcprouter-vps-admin 0.57.3 and earlier compares the user provided password with the original password in a length dependent manner, which allows remote attackers to guess the password via a timing attack.

Improper Authentication

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

def verify_password(plain_password, user_password):
    if plain_password == user_password:
        LOG.debug("password true")
        return True
    return False

def verify_password(plain_password, user_password):
    if secrets.compare_digest(plain_password,user_password):
        LOG.debug("password true")
        return True
    return False

--- a/omr-admin.py
+++ b/omr-admin.py
@@ -1,5 +1,5 @@
 def verify_password(plain_password, user_password):
-    if plain_password == user_password:
+    if secrets.compare_digest(plain_password,user_password):
         LOG.debug("password true")
         return True
     return False


The woduq1414/munhak-moa repository before 2022-05-03 on GitHub allows absolute path traversal because the Flask send_file function is used unsafely.

@app.route('/images/<path:path>')
def get_image(path):
    def get_absolute_path(path):
        import os
        script_dir = os.path.dirname(__file__)  # <-- absolute dir the script is in
        rel_path = path
        abs_file_path = os.path.join(script_dir, rel_path)
        return abs_file_path

    return send_file(
        get_absolute_path(f"./images/{path}"),
        mimetype='image/png',
        attachment_filename='snapshot.png',
        cache_timeout=0
    )

@app.route('/images/<path:path>')
def get_image(path):
    def get_absolute_path(path):
        import os
        script_dir = os.path.dirname(__file__)  # <-- absolute dir the script is in
        rel_path = path
        abs_file_path = safe_join(script_dir, rel_path)
        return abs_file_path

    return send_file(
        get_absolute_path(f"./images/{path}"),
        mimetype='image/png',
        attachment_filename='snapshot.png',
        cache_timeout=0
    )

--- a/app.py
+++ b/app.py
@@ -4,7 +4,7 @@
         import os
         script_dir = os.path.dirname(__file__)  # <-- absolute dir the script is in
         rel_path = path
-        abs_file_path = os.path.join(script_dir, rel_path)
+        abs_file_path = safe_join(script_dir, rel_path)
         return abs_file_path

     return send_file(

--- /dev/null
+++ b/app.py
@@ -0,0 +1 @@
+from werkzeug.utils import safe_join


ubireader_extract_files is vulnerable to path traversal when run against specifically crafted UBIFS files, allowing the attacker to overwrite files outside of the extraction directory (provided the process has write access to that file or directory).

This is due to the fact that a node name (dent_node.name) is considered trusted and joined to the extraction directory path during processing, then the node content is written to that joined path. By crafting a malicious UBIFS file with node names holding path traversal payloads (e.g. ../../tmp/outside.txt), it's possible to force ubi_reader to write outside of the extraction directory.

This issue affects ubi-reader before 0.8.5.

def extract_dents(ubifs, inodes, dent_node, path='', perms=False):
    if dent_node.inum not in inodes:
        error(extract_dents, 'Error', 'inum: %s not found in inodes' % (dent_node.inum))
        return

    inode = inodes[dent_node.inum]
    dent_path = os.path.join(path, dent_node.name)

    if dent_node.type == UBIFS_ITYPE_DIR:
        try:
            if not os.path.exists(dent_path):
                os.mkdir(dent_path)
                log(extract_dents, 'Make Dir: %s' % (dent_path))

                if perms:
                    _set_file_perms(dent_path, inode)
        except Exception as e:
            error(extract_dents, 'Warn', 'DIR Fail: %s' % e)

        if 'dent' in inode:
            for dnode in inode['dent']:
                extract_dents(ubifs, inodes, dnode, dent_path, perms)

        _set_file_timestamps(dent_path, inode)

    elif dent_node.type == UBIFS_ITYPE_REG:
        try:
            if inode['ino'].nlink > 1:
                if 'hlink' not in inode:
                    inode['hlink'] = dent_path
                    buf = _process_reg_file(ubifs, inode, dent_path)
                    _write_reg_file(dent_path, buf)
                else:
                    os.link(inode['hlink'], dent_path)
                    log(extract_dents, 'Make Link: %s > %s' % (dent_path, inode['hlink']))
            else:
                buf = _process_reg_file(ubifs, inode, dent_path)
                _write_reg_file(dent_path, buf)

            _set_file_timestamps(dent_path, inode)

            if perms:
                _set_file_perms(dent_path, inode)

        except Exception as e:
            error(extract_dents, 'Warn', 'FILE Fail: %s' % e)

    elif dent_node.type == UBIFS_ITYPE_LNK:
        try:
            # probably will need to decompress ino data if > UBIFS_MIN_COMPR_LEN
            os.symlink('%s' % inode['ino'].data.decode('utf-8'), dent_path)
            log(extract_dents, 'Make Symlink: %s > %s' % (dent_path, inode['ino'].data))

        except Exception as e:
            error(extract_dents, 'Warn', 'SYMLINK Fail: %s' % e)

    elif dent_node.type in [UBIFS_ITYPE_BLK, UBIFS_ITYPE_CHR]:
        try:
            dev = struct.unpack('<II', inode['ino'].data)[0]
            if not settings.use_dummy_devices:
                os.mknod(dent_path, inode['ino'].mode, dev)
                log(extract_dents, 'Make Device Node: %s' % (dent_path))

                if perms:
                    _set_file_perms(dent_path, inode)
            else:
                log(extract_dents, 'Create dummy device.')
                _write_reg_file(dent_path, str(dev))

                if perms:
                    _set_file_perms(dent_path, inode)

        except Exception as e:
            error(extract_dents, 'Warn', 'DEV Fail: %s' % e)

    elif dent_node.type == UBIFS_ITYPE_FIFO:
        try:
            os.mkfifo(dent_path, inode['ino'].mode)
            log(extract_dents, 'Make FIFO: %s' % (path))

            if perms:
                _set_file_perms(dent_path, inode)
        except Exception as e:
            error(extract_dents, 'Warn', 'FIFO Fail: %s : %s' % (dent_path, e))

    elif dent_node.type == UBIFS_ITYPE_SOCK:
        try:
            if settings.use_dummy_socket_file:
                _write_reg_file(dent_path, '')
                if perms:
                    _set_file_perms(dent_path, inode)
        except Exception as e:
            error(extract_dents, 'Warn', 'SOCK Fail: %s : %s' % (dent_path, e))

def extract_dents(ubifs, inodes, dent_node, path='', perms=False):
    if dent_node.inum not in inodes:
        error(extract_dents, 'Error', 'inum: %s not found in inodes' % (dent_node.inum))
        return

    inode = inodes[dent_node.inum]

    if not is_safe_path(path, dent_node.name):
        error(extract_dents, 'Warning', 'Path traversal attempt: %s, discarding' % (dent_node.name))
        return
    dent_path = os.path.realpath(os.path.join(path, dent_node.name))

    if dent_node.type == UBIFS_ITYPE_DIR:
        try:
            if not os.path.exists(dent_path):
                os.mkdir(dent_path)
                log(extract_dents, 'Make Dir: %s' % (dent_path))

                if perms:
                    _set_file_perms(dent_path, inode)
        except Exception as e:
            error(extract_dents, 'Warn', 'DIR Fail: %s' % e)

        if 'dent' in inode:
            for dnode in inode['dent']:
                extract_dents(ubifs, inodes, dnode, dent_path, perms)

        _set_file_timestamps(dent_path, inode)

    elif dent_node.type == UBIFS_ITYPE_REG:
        try:
            if inode['ino'].nlink > 1:
                if 'hlink' not in inode:
                    inode['hlink'] = dent_path
                    buf = _process_reg_file(ubifs, inode, dent_path)
                    _write_reg_file(dent_path, buf)
                else:
                    os.link(inode['hlink'], dent_path)
                    log(extract_dents, 'Make Link: %s > %s' % (dent_path, inode['hlink']))
            else:
                buf = _process_reg_file(ubifs, inode, dent_path)
                _write_reg_file(dent_path, buf)

            _set_file_timestamps(dent_path, inode)

            if perms:
                _set_file_perms(dent_path, inode)

        except Exception as e:
            error(extract_dents, 'Warn', 'FILE Fail: %s' % e)

    elif dent_node.type == UBIFS_ITYPE_LNK:
        try:
            # probably will need to decompress ino data if > UBIFS_MIN_COMPR_LEN
            os.symlink('%s' % inode['ino'].data.decode('utf-8'), dent_path)
            log(extract_dents, 'Make Symlink: %s > %s' % (dent_path, inode['ino'].data))

        except Exception as e:
            error(extract_dents, 'Warn', 'SYMLINK Fail: %s' % e)

    elif dent_node.type in [UBIFS_ITYPE_BLK, UBIFS_ITYPE_CHR]:
        try:
            dev = struct.unpack('<II', inode['ino'].data)[0]
            if not settings.use_dummy_devices:
                os.mknod(dent_path, inode['ino'].mode, dev)
                log(extract_dents, 'Make Device Node: %s' % (dent_path))

                if perms:
                    _set_file_perms(dent_path, inode)
            else:
                log(extract_dents, 'Create dummy device.')
                _write_reg_file(dent_path, str(dev))

                if perms:
                    _set_file_perms(dent_path, inode)

        except Exception as e:
            error(extract_dents, 'Warn', 'DEV Fail: %s' % e)

    elif dent_node.type == UBIFS_ITYPE_FIFO:
        try:
            os.mkfifo(dent_path, inode['ino'].mode)
            log(extract_dents, 'Make FIFO: %s' % (path))

            if perms:
                _set_file_perms(dent_path, inode)
        except Exception as e:
            error(extract_dents, 'Warn', 'FIFO Fail: %s : %s' % (dent_path, e))

    elif dent_node.type == UBIFS_ITYPE_SOCK:
        try:
            if settings.use_dummy_socket_file:
                _write_reg_file(dent_path, '')
                if perms:
                    _set_file_perms(dent_path, inode)
        except Exception as e:
            error(extract_dents, 'Warn', 'SOCK Fail: %s : %s' % (dent_path, e))

--- a/ubireader/ubifs/output.py
+++ b/ubireader/ubifs/output.py
@@ -4,8 +4,12 @@
         return

     inode = inodes[dent_node.inum]
-    dent_path = os.path.join(path, dent_node.name)
-
+
+    if not is_safe_path(path, dent_node.name):
+        error(extract_dents, 'Warning', 'Path traversal attempt: %s, discarding' % (dent_node.name))
+        return
+    dent_path = os.path.realpath(os.path.join(path, dent_node.name))
+
     if dent_node.type == UBIFS_ITYPE_DIR:
         try:
             if not os.path.exists(dent_path):


furlongm openvpn-monitor through 1.1.3 allows Authorization Bypass to disconnect arbitrary clients.

    def __init__(self, cfg, **kwargs):
        self.vpns = cfg.vpns

        if kwargs.get('vpn_id'):
            vpn = self.vpns[kwargs['vpn_id']]
            self._socket_connect(vpn)
            if vpn['socket_connected']:
                release = self.send_command('version\n')
                version = semver(self.parse_version(release).split(' ')[1])
                if version.major == 2 and \
                        version.minor >= 4 and \
                        kwargs.get('client_id'):
                    command = 'client-kill {0!s}\n'.format(kwargs['client_id'])
                else:
                    command = 'kill {0!s}:{1!s}\n'.format(kwargs['ip'], kwargs['port'])
                self.send_command(command)
                self._socket_disconnect()

        geoip_data = cfg.settings['geoip_data']
        self.geoip_version = None
        self.gi = None
        try:
            if geoip_data.endswith('.mmdb') and geoip2_available:
                self.gi = database.Reader(geoip_data)
                self.geoip_version = 2
            elif geoip_data.endswith('.dat') and geoip1_available:
                self.gi = geoip1.open(geoip_data, geoip1.GEOIP_STANDARD)
                self.geoip_version = 1
            else:
                warning('No compatible geoip1 or geoip2 data/libraries found.')
        except IOError:
            warning('No compatible geoip1 or geoip2 data/libraries found.')

        for _, vpn in list(self.vpns.items()):
            self._socket_connect(vpn)
            if vpn['socket_connected']:
                self.collect_data(vpn)
                self._socket_disconnect()

    def __init__(self, cfg, **kwargs):
        self.vpns = cfg.vpns

        if kwargs.get('vpn_id'):
            vpn = self.vpns[kwargs['vpn_id']]
            disconnection_allowed = vpn['show_disconnect']
            if disconnection_allowed:
                self._socket_connect(vpn)
                if vpn['socket_connected']:
                    release = self.send_command('version\n')
                    version = semver(self.parse_version(release).split(' ')[1])
                    command = False
                    client_id = int(kwargs.get('client_id'))
                    if version.major == 2 and \
                            version.minor >= 4 and \
                            client_id:
                        command = 'client-kill {0!s}\n'.format(client_id)
                    else:
                        ip = ip_address(kwargs['ip'])
                        port = int(kwargs['port'])
                        if ip and port:
                            command = 'kill {0!s}:{1!s}\n'.format(ip, port)
                    if command:
                        self.send_command(command)
                    self._socket_disconnect()

        geoip_data = cfg.settings['geoip_data']
        self.geoip_version = None
        self.gi = None
        try:
            if geoip_data.endswith('.mmdb') and geoip2_available:
                self.gi = database.Reader(geoip_data)
                self.geoip_version = 2
            elif geoip_data.endswith('.dat') and geoip1_available:
                self.gi = geoip1.open(geoip_data, geoip1.GEOIP_STANDARD)
                self.geoip_version = 1
            else:
                warning('No compatible geoip1 or geoip2 data/libraries found.')
        except IOError:
            warning('No compatible geoip1 or geoip2 data/libraries found.')

        for _, vpn in list(self.vpns.items()):
            self._socket_connect(vpn)
            if vpn['socket_connected']:
                self.collect_data(vpn)
                self._socket_disconnect()

--- a/openvpn-monitor.py
+++ b/openvpn-monitor.py
@@ -3,18 +3,26 @@

         if kwargs.get('vpn_id'):
             vpn = self.vpns[kwargs['vpn_id']]
-            self._socket_connect(vpn)
-            if vpn['socket_connected']:
-                release = self.send_command('version\n')
-                version = semver(self.parse_version(release).split(' ')[1])
-                if version.major == 2 and \
-                        version.minor >= 4 and \
-                        kwargs.get('client_id'):
-                    command = 'client-kill {0!s}\n'.format(kwargs['client_id'])
-                else:
-                    command = 'kill {0!s}:{1!s}\n'.format(kwargs['ip'], kwargs['port'])
-                self.send_command(command)
-                self._socket_disconnect()
+            disconnection_allowed = vpn['show_disconnect']
+            if disconnection_allowed:
+                self._socket_connect(vpn)
+                if vpn['socket_connected']:
+                    release = self.send_command('version\n')
+                    version = semver(self.parse_version(release).split(' ')[1])
+                    command = False
+                    client_id = int(kwargs.get('client_id'))
+                    if version.major == 2 and \
+                            version.minor >= 4 and \
+                            client_id:
+                        command = 'client-kill {0!s}\n'.format(client_id)
+                    else:
+                        ip = ip_address(kwargs['ip'])
+                        port = int(kwargs['port'])
+                        if ip and port:
+                            command = 'kill {0!s}:{1!s}\n'.format(ip, port)
+                    if command:
+                        self.send_command(command)
+                    self._socket_disconnect()

         geoip_data = cfg.settings['geoip_data']
         self.geoip_version = None


A vulnerability classified as critical has been found in Fumon trello-octometric. This affects the function main of the file metrics-ui/server/srv.go. The manipulation of the argument num leads to sql injection. The patch is named a1f1754933fbf21e2221fbc671c81a47de6a04ef. It is recommended to apply a patch to fix this issue. The associated identifier of this vulnerability is VDB-217611.

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 main() {
	log.Println("[i] Server started")

	// Connect to DB
	db, err := sql.Open("postgres", "user=appread dbname='quantifiedSelf' sslmode=disable")
	failOnError(err, "Error connecting to database")
	defer db.Close()

	http.HandleFunc("/data/all/", func(w http.ResponseWriter, r *http.Request) {
		// TODO: Allow filtering via URL
		// Get rows from DB
		var output string
		err := db.QueryRow(`SELECT json_agg(r) FROM (SELECT * FROM trello.cards) r;`).Scan(&output)
		if err != nil {
			log.Println("Error retriving from DB, ", err)
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintln(w, "Error retriving from DB, ", err)
			return
		}

		// Print out returned
		fmt.Fprint(w, output)
	})

	// Restful handler
	r := mux.NewRouter()
	r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "dla;jfkdlsajflkdsa;jfk;ldsajfklds;a")
	})

	r.HandleFunc("/api/totals/last/{num}", func(w http.ResponseWriter, r *http.Request) {
		// Grab vars
		vars := mux.Vars(r)

		var output string
		// This is bad... don't do this.... omg
		query := fmt.Sprintf(`SELECT json_agg(r) FROM (select EXTRACT(epoch FROM day) as day, end_of_day_total from trello.dailytallies order by day DESC limit %s) r;`, vars["num"])
		err := db.QueryRow(query).Scan(&output)

		if err != nil {
			log.Println("Error retriving from DB, ", err)
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintln(w, "Error retriving from DB, ", err)
			return
		}

		// Print out returned
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, output)
	})

	r.HandleFunc("/api/diffs/last/{num}", func(w http.ResponseWriter, r *http.Request) {
		// Grab vars
		vars := mux.Vars(r)

		var output string
		// This is bad... don't do this.... omg
		query := fmt.Sprintf(`SELECT json_agg(r) FROM (select EXTRACT(epoch FROM day) as day, up_count, down_count, finished_count from trello.dailytallies order by day DESC limit %s) r;`, vars["num"])
		err := db.QueryRow(query).Scan(&output)

		if err != nil {
			log.Println("Error retriving from DB, ", err)
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintln(w, "Error retriving from DB, ", err)
			return
		}

		// Print out returned
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, output)
	})
	r.PathPrefix("/").Handler(http.FileServer(http.Dir("../ui")))
	http.Handle("/", r)

	// Die gracefully
	// killchan := make(chan os.Signal)
	// signal.Notify(killchan, os.Interrupt, os.Kill)

	log.Println("[i] Serving on ", servport, "\n\tWaiting...")

	log.Fatal(http.ListenAndServe(servport, nil))
	// <-killchan

	log.Println("[i] Shutting down...")
}

	log.Println("[i] Server started")

	// Connect to DB
	db, err := sql.Open("postgres", "user=appread dbname='quantifiedSelf' sslmode=disable")
	failOnError(err, "Error connecting to database")
	defer db.Close()

	http.HandleFunc("/data/all/", func(w http.ResponseWriter, r *http.Request) {
		// TODO: Allow filtering via URL
		// Get rows from DB
		var output string
		err := db.QueryRow(`SELECT json_agg(r) FROM (SELECT * FROM trello.cards) r;`).Scan(&output)
		if err != nil {
			log.Println("Error retriving from DB, ", err)
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintln(w, "Error retriving from DB, ", err)
			return
		}

		// Print out returned
		fmt.Fprint(w, output)
	})

	// Restful handler
	r := mux.NewRouter()
	r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "dla;jfkdlsajflkdsa;jfk;ldsajfklds;a")
	})

	r.HandleFunc("/api/totals/last/{num}", func(w http.ResponseWriter, r *http.Request) {
		// Grab vars
		vars := mux.Vars(r)

		var output string
		// This is bad... don't do this.... omg
		query := `SELECT json_agg(r) FROM (select EXTRACT(epoch FROM day) as day, end_of_day_total from trello.dailytallies order by day DESC limit $1) r;`
		err := db.QueryRow(query, vars["num"]).Scan(&output)

		if err != nil {
			log.Println("Error retriving from DB, ", err)
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintln(w, "Error retriving from DB, ", err)
			return
		}

		// Print out returned
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, output)
	})

	r.HandleFunc("/api/diffs/last/{num}", func(w http.ResponseWriter, r *http.Request) {
		// Grab vars
		vars := mux.Vars(r)

		var output string
		// This is bad... don't do this.... omg
		query := `SELECT json_agg(r) FROM (select EXTRACT(epoch FROM day) as day, up_count, down_count, finished_count from trello.dailytallies order by day DESC limit $1) r;`
		err := db.QueryRow(query, vars["num"]).Scan(&output)

		if err != nil {
			log.Println("Error retriving from DB, ", err)
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintln(w, "Error retriving from DB, ", err)
			return
		}

		// Print out returned
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, output)
	})
	r.PathPrefix("/").Handler(http.FileServer(http.Dir("../ui")))
	http.Handle("/", r)

	// Die gracefully
	// killchan := make(chan os.Signal)
	// signal.Notify(killchan, os.Interrupt, os.Kill)

	log.Println("[i] Serving on ", servport, "\n\tWaiting...")

	log.Fatal(http.ListenAndServe(servport, nil))
	// <-killchan

	log.Println("[i] Shutting down...")
}

--- a/metrics-ui/server/srv.go
+++ b/metrics-ui/server/srv.go
@@ -1,4 +1,3 @@
-func main() {
 	log.Println("[i] Server started")

 	// Connect to DB
@@ -34,8 +33,8 @@

 		var output string
 		// This is bad... don't do this.... omg
-		query := fmt.Sprintf(`SELECT json_agg(r) FROM (select EXTRACT(epoch FROM day) as day, end_of_day_total from trello.dailytallies order by day DESC limit %s) r;`, vars["num"])
-		err := db.QueryRow(query).Scan(&output)
+		query := `SELECT json_agg(r) FROM (select EXTRACT(epoch FROM day) as day, end_of_day_total from trello.dailytallies order by day DESC limit $1) r;`
+		err := db.QueryRow(query, vars["num"]).Scan(&output)

 		if err != nil {
 			log.Println("Error retriving from DB, ", err)
@@ -55,8 +54,8 @@

 		var output string
 		// This is bad... don't do this.... omg
-		query := fmt.Sprintf(`SELECT json_agg(r) FROM (select EXTRACT(epoch FROM day) as day, up_count, down_count, finished_count from trello.dailytallies order by day DESC limit %s) r;`, vars["num"])
-		err := db.QueryRow(query).Scan(&output)
+		query := `SELECT json_agg(r) FROM (select EXTRACT(epoch FROM day) as day, up_count, down_count, finished_count from trello.dailytallies order by day DESC limit $1) r;`
+		err := db.QueryRow(query, vars["num"]).Scan(&output)

 		if err != nil {
 			log.Println("Error retriving from DB, ", err)


OAuth2 Proxy before 5.0 has an open redirect vulnerability. Authentication tokens could be silently harvested by an attacker. This has been patched in version 5.0.

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 (p *OAuthProxy) IsValidRedirect(redirect string) bool {
	switch {
	case strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//"):
		return true
	case strings.HasPrefix(redirect, "http://") || strings.HasPrefix(redirect, "https://"):
		redirectURL, err := url.Parse(redirect)
		if err != nil {
			return false
		}
		redirectHostname := redirectURL.Hostname()

		for _, domain := range p.whitelistDomains {
			domainHostname, domainPort := splitHostPort(strings.TrimLeft(domain, "."))
			if domainHostname == "" {
				continue
			}

			if (redirectHostname == domainHostname) || (strings.HasPrefix(domain, ".") && strings.HasSuffix(redirectHostname, domainHostname)) {
				// the domain names match, now validate the ports
				// if the whitelisted domain's port is '*', allow all ports
				// if the whitelisted domain contains a specific port, only allow that port
				// if the whitelisted domain doesn't contain a port at all, only allow empty redirect ports ie http and https
				redirectPort := redirectURL.Port()
				if (domainPort == "*") ||
					(domainPort == redirectPort) ||
					(domainPort == "" && redirectPort == "") {
					return true
				}
			}
		}

		return false
	default:
		return false
	}
}

func (p *OAuthProxy) IsValidRedirect(redirect string) bool {
	switch {
	case strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//") && !strings.HasPrefix(redirect, "/\\"):
		return true
	case strings.HasPrefix(redirect, "http://") || strings.HasPrefix(redirect, "https://"):
		redirectURL, err := url.Parse(redirect)
		if err != nil {
			return false
		}
		redirectHostname := redirectURL.Hostname()

		for _, domain := range p.whitelistDomains {
			domainHostname, domainPort := splitHostPort(strings.TrimLeft(domain, "."))
			if domainHostname == "" {
				continue
			}

			if (redirectHostname == domainHostname) || (strings.HasPrefix(domain, ".") && strings.HasSuffix(redirectHostname, domainHostname)) {
				// the domain names match, now validate the ports
				// if the whitelisted domain's port is '*', allow all ports
				// if the whitelisted domain contains a specific port, only allow that port
				// if the whitelisted domain doesn't contain a port at all, only allow empty redirect ports ie http and https
				redirectPort := redirectURL.Port()
				if (domainPort == "*") ||
					(domainPort == redirectPort) ||
					(domainPort == "" && redirectPort == "") {
					return true
				}
			}
		}

		return false
	default:
		return false
	}
}

--- a/oauthproxy.go
+++ b/oauthproxy.go
@@ -1,6 +1,6 @@
 func (p *OAuthProxy) IsValidRedirect(redirect string) bool {
 	switch {
-	case strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//"):
+	case strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//") && !strings.HasPrefix(redirect, "/\\"):
 		return true
 	case strings.HasPrefix(redirect, "http://") || strings.HasPrefix(redirect, "https://"):
 		redirectURL, err := url.Parse(redirect)


Versions of the package exec-local-bin before 1.2.0 are vulnerable to Command Injection via the theProcess() functionality due to improper user-input sanitization.

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.

const exec = require('child_process').exec;

module.exports = async function (bin, options) {
    return new Promise((resolve, reject) => {
        const cmd = `${process.cwd()}/node_modules/.bin/${bin}`;

        console.log(`Running \`${cmd}\``);

        const theProcess = exec(cmd, options, (error, stdout, stderr) => {
            if (stderr) {
                reject(error);
                return;
            }

            resolve(stdout);
        });

        theProcess.stdout.on('data', (data) => {
            process.stdout.write(data.toString());
        });

        theProcess.stderr.on('data', (data) => {
            process.stdout.write(data.toString());
        });
    });
};

const exec = require('child_process').exec;
const fs = require('fs');
const path = require('path');

module.exports = async function (bin, options) {
    return new Promise(async (resolve, reject) => {
        const binDir = `${process.cwd()}/node_modules/.bin`;
        const cmd = path.join(binDir, bin);

        if (!cmd.startsWith(binDir)) {
            reject(new Error(`${cmd} within the expected directory`));
            return;
        }

        try {
            await fs.access(cmd, fs.constants.X_OK);
        } catch (err) {
            reject(new Error(`${cmd} is not accessible: ${err.message}`));
            return;
        }

        console.log(`Running \`${cmd}\``);

        const theProcess = exec(cmd, options, (error, stdout, stderr) => {
            if (stderr) {
                reject(error);
                return;
            }

            resolve(stdout);
        });

        theProcess.stdout.on('data', (data) => {
            process.stdout.write(data.toString());
        });

        theProcess.stderr.on('data', (data) => {
            process.stdout.write(data.toString());
        });
    });
};

--- a/index.js
+++ b/index.js
@@ -1,8 +1,23 @@
 const exec = require('child_process').exec;
+const fs = require('fs');
+const path = require('path');

 module.exports = async function (bin, options) {
-    return new Promise((resolve, reject) => {
-        const cmd = `${process.cwd()}/node_modules/.bin/${bin}`;
+    return new Promise(async (resolve, reject) => {
+        const binDir = `${process.cwd()}/node_modules/.bin`;
+        const cmd = path.join(binDir, bin);
+
+        if (!cmd.startsWith(binDir)) {
+            reject(new Error(`${cmd} within the expected directory`));
+            return;
+        }
+
+        try {
+            await fs.access(cmd, fs.constants.X_OK);
+        } catch (err) {
+            reject(new Error(`${cmd} is not accessible: ${err.message}`));
+            return;
+        }

         console.log(`Running \`${cmd}\``);


An issue in the upload_documents method of libre-chat v0.0.6 allows attackers to execute a path traversal via supplying a crafted filename in an uploaded file.

        def upload_documents(
            files: List[UploadFile] = File(...),
            admin_pass: Optional[str] = None,
            # current_user: User = Depends(get_current_user),
        ) -> JSONResponse:
            os.makedirs(self.conf.vector.documents_path, exist_ok=True)
            if self.conf.auth.admin_pass and admin_pass != self.conf.auth.admin_pass:
                raise HTTPException(
                    status_code=403,
                    detail="The admin pass key provided was wrong",
                )
            for uploaded in files:
                if uploaded.filename:  # no cov
                    file_path = os.path.join(self.conf.vector.documents_path, uploaded.filename)
                    with open(file_path, "wb") as file:
                        file.write(uploaded.file.read())
                    # Check if the uploaded file is a zip file
                    if uploaded.filename.endswith(".zip"):
                        log.info(f"\ud83e\udd10 Unzipping {file_path}")
                        with zipfile.ZipFile(file_path, "r") as zip_ref:
                            zip_ref.extractall(self.conf.vector.documents_path)
                        os.remove(file_path)
            # TODO: add just the uploaded files instead of rebuilding the triplestore
            self.llm.build_vectorstore()
            self.llm.setup_dbqa()
            return JSONResponse(
                {
                    "message": f"Documents uploaded in {self.conf.vector.documents_path}, vectorstore rebuilt."
                }
            )

        def upload_documents(
            files: List[UploadFile] = File(...),
            admin_pass: Optional[str] = None,
            # current_user: User = Depends(get_current_user),
        ) -> JSONResponse:
            os.makedirs(self.conf.vector.documents_path, exist_ok=True)
            if self.conf.auth.admin_pass and admin_pass != self.conf.auth.admin_pass:
                raise HTTPException(
                    status_code=403,
                    detail="The admin pass key provided was wrong",
                )
            for uploaded in files:
                if uploaded.filename:  # no cov
                    file_path = werkzeug.utils.safe_join(self.conf.vector.documents_path, uploaded.filename)
                    if file_path is None:
                        raise HTTPException(
                            status_code=403,
                            detail=f"Invalid file name: {uploaded.filename}",
                        )

                    with open(file_path, "wb") as file:
                        file.write(uploaded.file.read())
                    # Check if the uploaded file is a zip file
                    if uploaded.filename.endswith(".zip"):
                        log.info(f"\ud83e\udd10 Unzipping {file_path}")
                        with zipfile.ZipFile(file_path, "r") as zip_ref:
                            zip_ref.extractall(self.conf.vector.documents_path)
                        os.remove(file_path)
            # TODO: add just the uploaded files instead of rebuilding the triplestore
            self.llm.build_vectorstore()
            self.llm.setup_dbqa()
            return JSONResponse(
                {
                    "message": f"Documents uploaded in {self.conf.vector.documents_path}, vectorstore rebuilt."
                }
            )

--- a/src/libre_chat/router.py
+++ b/src/libre_chat/router.py
@@ -11,7 +11,13 @@
                 )
             for uploaded in files:
                 if uploaded.filename:  # no cov
-                    file_path = os.path.join(self.conf.vector.documents_path, uploaded.filename)
+                    file_path = werkzeug.utils.safe_join(self.conf.vector.documents_path, uploaded.filename)
+                    if file_path is None:
+                        raise HTTPException(
+                            status_code=403,
+                            detail=f"Invalid file name: {uploaded.filename}",
+                        )
+
                     with open(file_path, "wb") as file:
                         file.write(uploaded.file.read())
                     # Check if the uploaded file is a zip file


python-fedora 0.8.0 and lower is vulnerable to an open redirect resulting in loss of CSRF protection

def url(tgpath, tgparams=None, **kwargs):
    '''Computes URLs.

    This is a replacement for :func:`turbogears.controllers.url` (aka
    :func:`tg.url` in the template).  In addition to the functionality that
    :func:`tg.url` provides, it adds a token to prevent :term:`CSRF` attacks.

    :arg tgpath:  a list or a string. If the path is absolute (starts
        with a "/"), the :attr:`server.webpath`, :envvar:`SCRIPT_NAME` and
        the approot of the application are prepended to the path. In order for
        the approot to be detected properly, the root object should extend
        :class:`turbogears.controllers.RootController`.
    :kwarg tgparams: See param: ``kwargs``
    :kwarg kwargs: Query parameters for the URL can be passed in as a
        dictionary in the second argument *or* as keyword parameters.
        Values which are a list or a tuple are used to create multiple
        key-value pairs.
    :returns: The changed path

    .. versionadded:: 0.3.10
       Modified from turbogears.controllers.url for :ref:`CSRF-Protection`
    '''
    if not isinstance(tgpath, six.string_types):
        tgpath = '/'.join(list(tgpath))
    if tgpath.startswith('/'):
        webpath = (config.get('server.webpath') or '').rstrip('/')
        if tg_util.request_available():
            check_app_root()
            tgpath = request.app_root + tgpath
            try:
                webpath += request.wsgi_environ['SCRIPT_NAME'].rstrip('/')
            except (AttributeError, KeyError):  # pylint: disable-msg=W0704
                # :W0704: Lack of wsgi environ is fine... we still have
                # server.webpath
                pass
        tgpath = webpath + tgpath
    if tgparams is None:
        tgparams = kwargs
    else:
        try:
            tgparams = tgparams.copy()
            tgparams.update(kwargs)
        except AttributeError:
            raise TypeError(
                'url() expects a dictionary for query parameters')
    args = []
    # Add the _csrf_token
    try:
        if identity.current.csrf_token:
            tgparams.update({'_csrf_token': identity.current.csrf_token})
    except RequestRequiredException:  # pylint: disable-msg=W0704
        # :W0704: If we are outside of a request (called from non-controller
        # methods/ templates) just don't set the _csrf_token.
        pass

    # Check for query params in the current url
    query_params = six.iteritems(tgparams)
    scheme, netloc, path, params, query_s, fragment = urlparse(tgpath)
    if query_s:
        query_params = chain((p for p in cgi.parse_qsl(query_s) if p[0] !=
                              '_csrf_token'), query_params)

    for key, value in query_params:
        if value is None:
            continue
        if isinstance(value, (list, tuple)):
            pairs = [(key, v) for v in value]
        else:
            pairs = [(key, value)]
        for key, value in pairs:
            if value is None:
                continue
            if isinstance(value, unicode):
                value = value.encode('utf8')
            args.append((key, str(value)))
    query_string = urlencode(args, True)
    tgpath = urlunparse((scheme, netloc, path, params, query_string, fragment))
    return tgpath

def url(tgpath, tgparams=None, **kwargs):
    '''Computes URLs.

    This is a replacement for :func:`turbogears.controllers.url` (aka
    :func:`tg.url` in the template).  In addition to the functionality that
    :func:`tg.url` provides, it adds a token to prevent :term:`CSRF` attacks.

    :arg tgpath:  a list or a string. If the path is absolute (starts
        with a "/"), the :attr:`server.webpath`, :envvar:`SCRIPT_NAME` and
        the approot of the application are prepended to the path. In order for
        the approot to be detected properly, the root object should extend
        :class:`turbogears.controllers.RootController`.
    :kwarg tgparams: See param: ``kwargs``
    :kwarg kwargs: Query parameters for the URL can be passed in as a
        dictionary in the second argument *or* as keyword parameters.
        Values which are a list or a tuple are used to create multiple
        key-value pairs.
    :returns: The changed path

    .. versionadded:: 0.3.10
       Modified from turbogears.controllers.url for :ref:`CSRF-Protection`
    '''
    if not isinstance(tgpath, six.string_types):
        tgpath = '/'.join(list(tgpath))
    if not tgpath.startswith('/'):
        # Do not allow the url() function to be used for external urls.
        # This function is primarily used in redirect() calls, so this prevents
        # covert redirects and thus CSRF leaking.
        tgpath = '/'
    if tgpath.startswith('/'):
        webpath = (config.get('server.webpath') or '').rstrip('/')
        if tg_util.request_available():
            check_app_root()
            tgpath = request.app_root + tgpath
            try:
                webpath += request.wsgi_environ['SCRIPT_NAME'].rstrip('/')
            except (AttributeError, KeyError):  # pylint: disable-msg=W0704
                # :W0704: Lack of wsgi environ is fine... we still have
                # server.webpath
                pass
        tgpath = webpath + tgpath
    if tgparams is None:
        tgparams = kwargs
    else:
        try:
            tgparams = tgparams.copy()
            tgparams.update(kwargs)
        except AttributeError:
            raise TypeError(
                'url() expects a dictionary for query parameters')
    args = []
    # Add the _csrf_token
    try:
        if identity.current.csrf_token:
            tgparams.update({'_csrf_token': identity.current.csrf_token})
    except RequestRequiredException:  # pylint: disable-msg=W0704
        # :W0704: If we are outside of a request (called from non-controller
        # methods/ templates) just don't set the _csrf_token.
        pass

    # Check for query params in the current url
    query_params = six.iteritems(tgparams)
    scheme, netloc, path, params, query_s, fragment = urlparse(tgpath)
    if query_s:
        query_params = chain((p for p in cgi.parse_qsl(query_s) if p[0] !=
                              '_csrf_token'), query_params)

    for key, value in query_params:
        if value is None:
            continue
        if isinstance(value, (list, tuple)):
            pairs = [(key, v) for v in value]
        else:
            pairs = [(key, value)]
        for key, value in pairs:
            if value is None:
                continue
            if isinstance(value, unicode):
                value = value.encode('utf8')
            args.append((key, str(value)))
    query_string = urlencode(args, True)
    tgpath = urlunparse((scheme, netloc, path, params, query_string, fragment))
    return tgpath

--- a/fedora/tg/utils.py
+++ b/fedora/tg/utils.py
@@ -22,6 +22,11 @@
     '''
     if not isinstance(tgpath, six.string_types):
         tgpath = '/'.join(list(tgpath))
+    if not tgpath.startswith('/'):
+        # Do not allow the url() function to be used for external urls.
+        # This function is primarily used in redirect() calls, so this prevents
+        # covert redirects and thus CSRF leaking.
+        tgpath = '/'
     if tgpath.startswith('/'):
         webpath = (config.get('server.webpath') or '').rstrip('/')
         if tg_util.request_available():


Discord Recon Server is a bot that allows you to do your reconnaissance process from your Discord. Remote code execution in version 0.0.1 would allow remote users to execute commands on the server resulting in serious issues. This flaw is patched in 0.0.2.

async def dirsearch(ctx , *, argument):
    Path = TOOLS['dirsearch']; MainPath = getcwd(); chdir(Path)
    await ctx.send(f"**Running Your Dirsearch Scan, We Will Send The Results When It's Done**")
    Process = subprocess.Popen(f'python3 dirsearch.py -u {argument} -e * -b' , shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
    Output = Process.communicate()[0].decode('UTF-8')
    Output = removeColors.Remove(Output); chdir(MainPath)

    if len(Output) > 2000:
        RandomStr = randomStrings.Genrate()

        with open(f'messages/{RandomStr}' , 'w') as Message:
            Message.write(Output); Message.close()
            await ctx.send("Results: ", file=discord.File(f"messages/{RandomStr}"))
            await ctx.send(f"\n**- {ctx.message.author}**")
    else:
        await ctx.send(f'Results:')
        await ctx.send(f'```{Output}```')
        await ctx.send(f"\n**- {ctx.message.author}**")

async def arjun(ctx , *, argument):
    Path = TOOLS['arjun']; MainPath = getcwd(); chdir(Path)
    await ctx.send(f"**Running Your Arjun Scan, We Will Send The Results When It's Done**")
    await ctx.send(f"**Note: The Bot Won't Respond Until The Scan is Done. All Of Your Commands Now Will Be Executed After This Process is Done.")
    Process = subprocess.Popen(f'python3 arjun.py -u {argument}' , shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
    Output = Process.communicate()[0].decode('UTF-8')
    Output = removeColors.Remove(Output); chdir(MainPath)
    Output = removeString.removeString('Processing' , Output=Output)

    if len(Output) > 2000:
        RandomStr = randomStrings.Genrate()

        with open(f'messages/{RandomStr}' , 'w') as Message:
            Message.write(Output); Message.close()
            await ctx.send("**Arjun Results:**", file=discord.File(f"messages/{RandomStr}"))
            await ctx.send(f"\n**- {ctx.message.author}**")
    else:
        targetName = argument.split(' ')[0].replace('http://' , '').replace('https://' , '')
        await ctx.send(f'Arjun Results For {targetName}:')
        await ctx.send(f'```{Output}```')
        await ctx.send(f"\n**- {ctx.message.author}**")

async def dirsearch(ctx , *, argument):
    if not CommandInjection.commandInjection(argument=argument , RCE=RCE):
        await ctx.send("**Your Command Contains Unallowed Chars. Don't Try To Use It Again.**")
        return

    Path = TOOLS['dirsearch']; MainPath = getcwd(); chdir(Path)
    await ctx.send(f"**Running Your Dirsearch Scan, We Will Send The Results When It's Done**")
    Process = subprocess.Popen(f'python3 dirsearch.py -u {argument} -e * -b' , shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
    Output = Process.communicate()[0].decode('UTF-8')
    Output = removeColors.Remove(Output); chdir(MainPath)

    if len(Output) > 2000:
        RandomStr = randomStrings.Genrate()

        with open(f'messages/{RandomStr}' , 'w') as Message:
            Message.write(Output); Message.close()
            await ctx.send("Results: ", file=discord.File(f"messages/{RandomStr}"))
            await ctx.send(f"\n**- {ctx.message.author}**")
    else:
        await ctx.send(f'Results:')
        await ctx.send(f'```{Output}```')
        await ctx.send(f"\n**- {ctx.message.author}**")

async def arjun(ctx , *, argument):
    if not CommandInjection.commandInjection(argument=argument , RCE=RCE):
        await ctx.send("**Your Command Contains Unallowed Chars. Don't Try To Use It Again.**")
        return

    Path = TOOLS['arjun']; MainPath = getcwd(); chdir(Path)
    await ctx.send(f"**Running Your Arjun Scan, We Will Send The Results When It's Done**")
    await ctx.send(f"**Note: The Bot Won't Respond Until The Scan is Done. All Of Your Commands Now Will Be Executed After This Process is Done.")
    Process = subprocess.Popen(f'python3 arjun.py -u {argument}' , shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
    Output = Process.communicate()[0].decode('UTF-8')
    Output = removeColors.Remove(Output); chdir(MainPath)
    Output = removeString.removeString('Processing' , Output=Output)

    if len(Output) > 2000:
        RandomStr = randomStrings.Genrate()

        with open(f'messages/{RandomStr}' , 'w') as Message:
            Message.write(Output); Message.close()
            await ctx.send("**Arjun Results:**", file=discord.File(f"messages/{RandomStr}"))
            await ctx.send(f"\n**- {ctx.message.author}**")
    else:
        targetName = argument.split(' ')[0].replace('http://' , '').replace('https://' , '')
        await ctx.send(f'Arjun Results For {targetName}:')
        await ctx.send(f'```{Output}```')
        await ctx.send(f"\n**- {ctx.message.author}**")

--- a/app.py
+++ b/app.py
@@ -1,4 +1,8 @@
 async def dirsearch(ctx , *, argument):
+    if not CommandInjection.commandInjection(argument=argument , RCE=RCE):
+        await ctx.send("**Your Command Contains Unallowed Chars. Don't Try To Use It Again.**")
+        return
+
     Path = TOOLS['dirsearch']; MainPath = getcwd(); chdir(Path)
     await ctx.send(f"**Running Your Dirsearch Scan, We Will Send The Results When It's Done**")
     Process = subprocess.Popen(f'python3 dirsearch.py -u {argument} -e * -b' , shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT)

--- a/app.py
+++ b/app.py
@@ -1,4 +1,8 @@
 async def arjun(ctx , *, argument):
+    if not CommandInjection.commandInjection(argument=argument , RCE=RCE):
+        await ctx.send("**Your Command Contains Unallowed Chars. Don't Try To Use It Again.**")
+        return
+
     Path = TOOLS['arjun']; MainPath = getcwd(); chdir(Path)
     await ctx.send(f"**Running Your Arjun Scan, We Will Send The Results When It's Done**")
     await ctx.send(f"**Note: The Bot Won't Respond Until The Scan is Done. All Of Your Commands Now Will Be Executed After This Process is Done.")