
Educational write-up and test-mode PoC for CVE-2026-92162, a path traversal in Flatpak's DeployAppstream arch parameter enabling root directory creation.
Educational write up and test mode proof of concept for a path traversal in the
Flatpak system helper (flatpak-system-helper). The DeployAppstream D-Bus
method accepts an arch string that is placed into a filesystem path and created
as root, with no validation. An active local user can supply ../ components and
cause the root privileged service to create directories outside the intended
appstream tree.
This repository accompanies the full technical analysis and research post-mortem. It exists for education, for defenders who want to understand the mechanism, and for authorized testing on systems you own or have written permission to assess.
This material is published after coordinated disclosure and after a fixed Flatpak release was made available. Run the proof of concept only against a lab machine that you own or are explicitly authorized to test. Do not run it against systems you do not control. You are responsible for staying within the law and within the scope of any authorization you hold.
handle_deploy_appstream() in the system helper validates the origin argument
but not the arch argument. For an OCI remote, arch flows into
flatpak_build_file and then g_mkdir_with_parents, which runs as root and
resolves ../ lexically. The polkit action for the method is
org.freedesktop.Flatpak.appstream-update, which is allow_active=yes, so an
active local session reaches the code with no password prompt. On a system that
already has an OCI remote, such as a default Fedora Workstation with its fedora
remote, a low privileged user can create root owned directories at an arbitrary
path. The payload in this repository targets /root/.ssh itself.
This vulnerability, Finding A, was demonstrated as part of a two vulnerability
chain used to escalate from an unauthenticated local user to root on the
machine, in a controlled and authorized lab. The chain works as follows:
Finding A creates /root/.ssh as root. Finding B, a secondary write primitive,
can then write attacker controlled content into that directory. Together they
give an attacker a root owned file at an attacker chosen path. Each bug alone
is limited: Finding A only creates directories, and Finding B can only write
where a parent directory already exists. Finding A supplies the parent
directory that Finding B needs, and the pair becomes root file write.
Finding B was independently identified by this research, but it is not a new
finding: it is the known, still unpatched weakness in the extra data write path
of Deploy documented in the research post-mortem. This write up therefore
covers and is credited for Finding A, and uses Finding B only as the known,
unpatched component that the chain relies on.
This repository demonstrates Finding A only. The chaining methodology and the defensive lessons are documented in the research post-mortem.
The project already ships flatpak_is_valid_arch, which restricts an
architecture name to [A-Za-z0-9_]. The remediation is to call it on the arch
argument at the handler boundary and inside the path building helpers. A ../
value cannot pass that predicate. Update to the fixed Flatpak release.
.
├── README.md This file.
├── requirements.txt System package prerequisites for the PoC.
└── poc_arch_traversal.sh PoC for the unvalidated arch parameter.
Sandboxed test mode by default, production
mode targets /root/.ssh. Educational use only.
requirements.txt packages
present.allow_active=yes. Check with:
loginctl list-sessions and confirm your session shows a seat./usr/libexec/flatpak-system-helper; the
script first looks for a freshly built helper in builddir/ or _build/ of
the Flatpak source tree, and FLATPAK_SYSTEM_HELPER overrides everything.
See requirements.txt for the system packages needed.The production mode auto-detects an existing OCI remote on the system installation and uses it. Any OCI remote works, even a fake or unreachable one, because the mkdir happens before the registry is ever contacted. If no OCI remote exists, the script cancels with a "No OCI remote found" message and sends nothing.
Fedora Workstation already ships the OCI fedora remote, so nothing needs to
be done there. Check what exists with flatpak remotes --system (an OCI
remote shows oci in its options column).
This is the only command in the whole flow that needs sudo, and the script never runs it for you. Add one with:
sudo flatpak remote-add --system --no-gpg-verify oci-poc oci+http://127.0.0.1:19876
(flatpak has no --oci flag, at least through 1.19.0, the version this work
is based on; the oci+ URL prefix is what makes the remote an OCI registry.) A "Warning: Could not update extra metadata" message
is expected when no registry is running; the remote is still added.
bash poc_arch_traversal.sh
The script starts a private D-Bus bus and a helper in --session mode, all
under a temporary directory. It calls DeployAppstream with the payload
../../../../../root/.ssh and verifies that the escaped directory was created
at <workdir>/root/.ssh, mirroring the real target. Expect a D-Bus error in
the middle of the output: that is the OCI index fetch failing after the
directory was already created, and it is the expected behavior.
bash poc_arch_traversal.sh prod
The script never asks for a password. It auto-detects an OCI remote, triggers
DeployAppstream unauthenticated with the traversal payload, and prints the
helper's response. The root helper creates /root/.ssh and then fails on the
unreachable registry, which prints the expected D-Bus error. If the response
is an authentication error instead, you are not in an active local session;
run it from the machine's console. A specific remote can be forced with
bash poc_arch_traversal.sh prod <remote>.
The trigger itself runs unprivileged, but confirming the result needs root, so the script leaves that to you. After the run, on the same machine:
sudo ls -laR /root/.ssh
sudo stat -c '%U:%G' /root/.ssh
The directory must exist and be owned by root:root. The D-Bus error in the
script output is not a failure: it proves the mkdir ran before the fetch, since
the escaped directory exists even though the method call failed.
If /root/.ssh already existed before the run (for example on a host with a
running sshd), the run is a no-op on that path and leaves nothing behind: the
helper's lock file is transient and is removed when the method returns, and
icons is only written after a successful index fetch. For a clean
verification, run against a path that does not exist yet.
sudo rm -rf /root/.ssh # only if it did not exist before the run
sudo flatpak remote-delete --system oci-poc
Research and write up by Yehia Ali Mohamed Ezzat.
Documentation and code in this repository are provided for educational purposes. Use at your own risk and only where you are authorized.