CVE-2023-1326 (published 2023-04-13T23:15Z)

A privilege escalation attack was found in apport-cli 2.26.0 and earlier which is similar to CVE-2023-26604. If a system is specially configured to allow unprivileged users to run sudo apport-cli, less is configured as the pager, and the terminal size can be set: a local attacker can escalate privilege. It is extremely unlikely that a system administrator would configure sudo to allow unprivileged users to perform this class of exploit.

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

Weakness information:
cwe_id: CWE-269
cwe_name: Improper Privilege Management
description: The product does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor.
extended_description: 
            
         
         

Fix information:
repo_url: https://github.com/canonical/apport
commit_details: repo_url: https://github.com/canonical/apport
author: Benjamin Drung
author_date: 2023-04-12 12:35:15+02:00
msg: fix: Do not run sensible-pager as root if using sudo/pkexec

The apport-cli supports view a crash. These features invoke the default
pager, which is likely to be less, other functions may apply.

It can be used to break out from restricted environments by spawning an
interactive system shell. If the binary is allowed to run as superuser
by sudo, it does not drop the elevated privileges and may be used to
access the file system, escalate or maintain privileged access.

apport-cli should normally not be called with sudo or pkexec. In case it
is called via sudo or pkexec execute `sensible-pager` as the original
user to avoid privilege elevation.

Proof of concept:

```
$ sudo apport-cli -c /var/crash/xxx.crash
[...]
Please choose (S/E/V/K/I/C): v
!id
uid=0(root) gid=0(root) groups=0(root)
!done  (press RETURN)
```

This fixes CVE-2023-1326.

Bug: https://launchpad.net/bugs/2016023
Signed-off-by: Benjamin Drung <benjamin.drung@canonical.com>
repository: repo_url: https://github.com/canonical/apport
repo_name: canonical/apport
description: Apport intercepts Program crashes, collects debugging information about the crash and the operating system environment, and sends it to bug trackers in a standardized form. It also offers the user to report a bug about a package, with again collecting as much information about it as possible.
repo_language: Python
owner: canonical
file_changes: filename: ui.py
change_type: ModificationType.MODIFY
diff: @@ -119,15 +119,19 @@ def _get_users_environ(uid: int) -> dict[str, str]:
     }
 
 
-def run_as_real_user(args: list[str]) -> None:
+def run_as_real_user(
+    args: list[str], *, get_user_env: bool = False, **kwargs
+) -> None:
     """Call subprocess.run as real user if called via sudo/pkexec.
 
     If we are called through pkexec/sudo, determine the real user ID and
     run the command with it to get the user's web browser settings.
+    If get_user_env is set to True, the D-BUS address and XDG_DATA_DIRS
+    is grabbed from a running gvfsd and added to the process environment.
     """
     uid = _get_env_int("SUDO_UID", _get_env_int("PKEXEC_UID"))
     if uid is None or not get_process_user_and_group().is_root():
-        subprocess.run(args, check=False)
+        subprocess.run(args, check=False, **kwargs)
         return
 
     pwuid = pwd.getpwuid(uid)
@@ -140,7 +144,9 @@ def run_as_real_user(args: list[str]) -> None:
         k: v
         for k, v in os.environ.items()
         if not k.startswith("SUDO_") and k != "PKEXEC_UID"
-    } | _get_users_environ(uid)
+    }
+    if get_user_env:
+        env |= _get_users_environ(uid)
     env["HOME"] = pwuid.pw_dir
     subprocess.run(
         args,
@@ -149,6 +155,7 @@ def run_as_real_user(args: list[str]) -> None:
         user=uid,
         group=gid,
         extra_groups=os.getgrouplist(pwuid.pw_name, gid),
+        **kwargs,
     )
 
 
@@ -1817,7 +1824,7 @@ class UserInterface:
 
         try:
             try:
-                run_as_real_user(["xdg-open", url])
+                run_as_real_user(["xdg-open", url], get_user_env=True)
             except OSError:
                 # fall back to webbrowser
                 webbrowser.open(url, new=True, autoraise=True)

programming_language: Python
filename: apport-cli
change_type: ModificationType.MODIFY
diff: @@ -185,9 +185,8 @@ class CLIUserInterface(apport.ui.UserInterface):
         self.in_update_view = True
         report = self._get_details()
         try:
-            subprocess.run(
+            apport.ui.run_as_real_user(
                 ["/usr/bin/sensible-pager"],
-                check=False,
                 input=report.encode("UTF-8"),
                 stdout=stdout,
             )

programming_language: Python
filename: test_ui.py
change_type: ModificationType.MODIFY
diff: @@ -2717,7 +2717,7 @@ class T(unittest.TestCase):
                 with unittest.mock.patch(
                     "subprocess.run", side_effect=mock_run_calls_except_pgrep
                 ) as run_mock:
-                    run_as_real_user(["/bin/true"])
+                    run_as_real_user(["/bin/true"], get_user_env=True)
 
         run_mock.assert_called_with(
             ["/bin/true"],
@@ -2755,7 +2755,7 @@ class T(unittest.TestCase):
         with unittest.mock.patch(
             "subprocess.run", side_effect=mock_run_calls_except_pgrep
         ) as run_mock:
-            run_as_real_user(["/bin/true"])
+            run_as_real_user(["/bin/true"], get_user_env=True)
 
         run_mock.assert_called_with(
             ["/bin/true"],

programming_language: Python