Skip to content

Commit 2a8a640

Browse files
carlospolopHackTricks PEASS New-Check Agent
andauthored
Auto-merge PR #680 (Chack Agent)
Co-authored-by: HackTricks PEASS New-Check Agent <peass-new-checks@hacktricks.xyz>
1 parent 5ac48fb commit 2a8a640

3 files changed

Lines changed: 303 additions & 5 deletions

File tree

linPEAS/builder/linpeas_parts/8_interesting_perms_files/4_Capabilities.sh

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# Title: Interesting Permissions Files - Capabilities
22
# ID: IP_Capabilities
33
# Author: Carlos Polop
4-
# Last Update: 22-08-2023
5-
# Description: Capabilities
4+
# Last Update: 14-08-2026
5+
# Description: Capabilities, including set-capabilities snap-confine exposure to CVE-2026-8933
66
# License: GNU GPL
7-
# Version: 1.0
8-
# Mitre: T1548.001
9-
# Functions Used: echo_not_found, print_2title, print_info, print_3title
7+
# Version: 1.1
8+
# Mitre: T1548.001,T1068
9+
# Functions Used: checkSnapConfineCVE20268933, echo_not_found, print_2title, print_info, print_3title
1010
# Global Variables: $capsB, $capsVB, $IAMROOT, $SEARCH_IN_FOLDER
1111
# Initial Functions:
1212
# Generated Global Variables: $cap_name, $cap_value, $cap_line, $cap_status_file, $cap_default_sep, $cap_sep, $cap_color, $capVB, $capname, $capbins, $capsVB_vuln, $proc_status, $proc_pid, $proc_name, $proc_uid, $user_name, $proc_inh, $proc_prm, $proc_eff, $proc_bnd, $proc_amb, $proc_inh_dec, $proc_prm_dec, $proc_eff_dec, $proc_bnd_dec, $proc_amb_dec
@@ -125,4 +125,5 @@ if ! [ "$SEARCH_IN_FOLDER" ]; then
125125
fi
126126
done
127127
echo ""
128+
checkSnapConfineCVE20268933
128129
fi
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Title: Functions - checkSnapConfineCVE20268933
2+
# ID: checkSnapConfineCVE20268933
3+
# Author: Chack Agent
4+
# Last Update: 14-08-2026
5+
# Description: Passively identify set-capabilities snap-confine binaries exposed to CVE-2026-8933.
6+
# License: GNU GPL
7+
# Version: 1.0
8+
# Mitre: T1068
9+
# Functions Used: print_3title, print_info
10+
# Global Variables: $E, $ROOT_FOLDER, $SED_GREEN, $SED_LIGHT_CYAN, $SED_RED_YELLOW
11+
# Initial Functions:
12+
# Generated Global Variables: $sc8933_caps, $sc8933_fixed, $sc8933_kind, $sc8933_os_id, $sc8933_os_release, $sc8933_path, $sc8933_reported, $sc8933_root, $sc8933_upstream, $sc8933_version, $sc8933_yaml
13+
# Fat linpeas: 0
14+
# Small linpeas: 1
15+
16+
17+
sc8933_extract_upstream_version() {
18+
printf '%s' "$1" | sed -E 's/^[0-9]+://; s/^[^0-9]*//; s/[^0-9.].*$//'
19+
}
20+
21+
sc8933_version_ge() {
22+
[ -n "$1" ] && [ -n "$2" ] || return 1
23+
if command -v dpkg >/dev/null 2>&1; then
24+
dpkg --compare-versions "$1" ge "$2"
25+
else
26+
[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V 2>/dev/null | tail -n1)" = "$1" ]
27+
fi
28+
}
29+
30+
sc8933_version_lt() {
31+
[ -n "$1" ] && [ -n "$2" ] || return 1
32+
if command -v dpkg >/dev/null 2>&1; then
33+
dpkg --compare-versions "$1" lt "$2"
34+
else
35+
[ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V 2>/dev/null | head -n1)" = "$1" ]
36+
fi
37+
}
38+
39+
sc8933_version_is_vulnerable() {
40+
sc8933_version="$1"
41+
sc8933_kind="$2"
42+
sc8933_os_release="$3"
43+
sc8933_upstream="$(sc8933_extract_upstream_version "$sc8933_version")"
44+
45+
# The upstream affected range is >= 2.75.0 and < 2.76.1. Ubuntu fixed
46+
# 2.76 with release-specific backports, so compare the complete dpkg version.
47+
sc8933_version_ge "$sc8933_upstream" "2.75" || return 1
48+
if [ "$sc8933_kind" = "deb" ]; then
49+
sc8933_fixed=""
50+
case "$sc8933_os_release" in
51+
22.04) sc8933_fixed="2.76+ubuntu22.04.1" ;;
52+
24.04) sc8933_fixed="2.76+ubuntu24.04.1" ;;
53+
26.04) sc8933_fixed="2.76+ubuntu26.04.3" ;;
54+
esac
55+
if [ -n "$sc8933_fixed" ]; then
56+
sc8933_version_lt "$sc8933_version" "$sc8933_fixed"
57+
return
58+
fi
59+
fi
60+
61+
sc8933_version_lt "$sc8933_upstream" "2.76.1"
62+
}
63+
64+
checkSnapConfineCVE20268933() {
65+
command -v getcap >/dev/null 2>&1 || return
66+
67+
sc8933_root="${ROOT_FOLDER:-/}"
68+
case "$sc8933_root" in
69+
*/) ;;
70+
*) sc8933_root="${sc8933_root}/" ;;
71+
esac
72+
sc8933_os_id="$(sed -nE 's/^ID="?([^" ]+)"?$/\1/p' "${sc8933_root}etc/os-release" 2>/dev/null | head -n1)"
73+
sc8933_os_release="$(sed -nE 's/^VERSION_ID="?([^" ]+)"?$/\1/p' "${sc8933_root}etc/os-release" 2>/dev/null | head -n1)"
74+
sc8933_reported=""
75+
76+
for sc8933_path in \
77+
"${sc8933_root}usr/lib/snapd/snap-confine" \
78+
"${sc8933_root}snap/snapd/current/usr/lib/snapd/snap-confine"; do
79+
[ -f "$sc8933_path" ] || continue
80+
[ -u "$sc8933_path" ] && continue
81+
sc8933_caps="$(getcap "$sc8933_path" 2>/dev/null)"
82+
printf '%s' "$sc8933_caps" | grep -q 'cap_sys_admin' || continue
83+
84+
if [ -z "$sc8933_reported" ]; then
85+
print_3title "Set-capabilities snap-confine (CVE-2026-8933)" "T1068"
86+
print_info "https://ubuntu.com/security/CVE-2026-8933"
87+
sc8933_reported="1"
88+
fi
89+
90+
sc8933_kind="snap"
91+
sc8933_yaml="${sc8933_root}snap/snapd/current/meta/snap.yaml"
92+
sc8933_version=""
93+
case "$sc8933_path" in
94+
*/usr/lib/snapd/snap-confine)
95+
if [ "$sc8933_path" = "${sc8933_root}usr/lib/snapd/snap-confine" ]; then
96+
sc8933_kind="deb"
97+
if command -v dpkg-query >/dev/null 2>&1; then
98+
sc8933_version="$(dpkg-query --admindir="${sc8933_root}var/lib/dpkg" -W -f='$''{Version}\n' snapd 2>/dev/null | head -n1)"
99+
fi
100+
elif [ -r "$sc8933_yaml" ]; then
101+
sc8933_version="$(sed -nE "s/^version:[[:space:]]*['\"]?([^'\"[:space:]]+).*/\1/p" "$sc8933_yaml" 2>/dev/null | head -n1)"
102+
fi
103+
;;
104+
esac
105+
106+
echo "$sc8933_caps" | sed -${E} "s,.*,${SED_LIGHT_CYAN},"
107+
if [ -z "$sc8933_version" ]; then
108+
echo "Potential CVE-2026-8933 exposure: privileged snap-confine uses file capabilities; version could not be determined" | sed -${E} "s,.*,${SED_RED_YELLOW},"
109+
elif [ "$sc8933_os_id" != "ubuntu" ] && [ "$sc8933_kind" = "deb" ]; then
110+
if sc8933_version_is_vulnerable "$sc8933_version" "$sc8933_kind" "$sc8933_os_release"; then
111+
echo "snap-confine version $sc8933_version uses file capabilities and is in the upstream CVE-2026-8933 range; verify distro backports" | sed -${E} "s,.*,${SED_RED_YELLOW},"
112+
else
113+
echo "snap-confine version $sc8933_version is not in the known CVE-2026-8933 vulnerable range" | sed -${E} "s,.*,${SED_GREEN},"
114+
fi
115+
elif sc8933_version_is_vulnerable "$sc8933_version" "$sc8933_kind" "$sc8933_os_release"; then
116+
echo "Vulnerable to CVE-2026-8933: set-capabilities snap-confine version $sc8933_version permits local privilege escalation to root" | sed -${E} "s,.*,${SED_RED_YELLOW},"
117+
else
118+
echo "snap-confine version $sc8933_version is not in the known CVE-2026-8933 vulnerable range" | sed -${E} "s,.*,${SED_GREEN},"
119+
fi
120+
done
121+
122+
if [ -n "$sc8933_reported" ]; then
123+
echo ""
124+
fi
125+
}

linPEAS/tests/test_snap_confine.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import os
2+
import shlex
3+
import subprocess
4+
import tempfile
5+
import unittest
6+
from pathlib import Path
7+
8+
9+
class SnapConfineCVE20268933Tests(unittest.TestCase):
10+
@classmethod
11+
def setUpClass(cls):
12+
cls.repo_root = Path(__file__).resolve().parents[2]
13+
cls.function_file = (
14+
cls.repo_root
15+
/ "linPEAS"
16+
/ "builder"
17+
/ "linpeas_parts"
18+
/ "functions"
19+
/ "checkSnapConfineCVE20268933.sh"
20+
)
21+
22+
def _make_root(self, base, release="24.04", snap_version=None):
23+
root = base / "root"
24+
(root / "etc").mkdir(parents=True)
25+
(root / "etc" / "os-release").write_text(
26+
f'ID=ubuntu\nVERSION_ID="{release}"\n', encoding="utf-8"
27+
)
28+
if snap_version is None:
29+
binary = root / "usr" / "lib" / "snapd" / "snap-confine"
30+
else:
31+
binary = (
32+
root
33+
/ "snap"
34+
/ "snapd"
35+
/ "current"
36+
/ "usr"
37+
/ "lib"
38+
/ "snapd"
39+
/ "snap-confine"
40+
)
41+
meta = root / "snap" / "snapd" / "current" / "meta"
42+
meta.mkdir(parents=True)
43+
(meta / "snap.yaml").write_text(
44+
f"name: snapd\nversion: {snap_version}\n", encoding="utf-8"
45+
)
46+
binary.parent.mkdir(parents=True, exist_ok=True)
47+
binary.write_text("#!/bin/sh\ntouch \"$EXECUTED_MARKER\"\n", encoding="utf-8")
48+
binary.chmod(0o755)
49+
return root, binary
50+
51+
def _run_check(self, root, package_version="2.75+ubuntu24.04"):
52+
bindir = root.parent / "bin"
53+
bindir.mkdir()
54+
getcap = bindir / "getcap"
55+
getcap.write_text(
56+
'#!/bin/sh\nprintf "%s cap_chown,cap_sys_admin=p\\n" "$1"\n',
57+
encoding="utf-8",
58+
)
59+
getcap.chmod(0o755)
60+
dpkg_query = bindir / "dpkg-query"
61+
dpkg_query.write_text(
62+
'#!/bin/sh\nprintf "%s\\n" "$FAKE_SNAPD_VERSION"\n', encoding="utf-8"
63+
)
64+
dpkg_query.chmod(0o755)
65+
66+
marker = root.parent / "executed"
67+
env = os.environ.copy()
68+
env.update(
69+
{
70+
"EXECUTED_MARKER": str(marker),
71+
"FAKE_SNAPD_VERSION": package_version,
72+
"PATH": f"{bindir}:{env['PATH']}",
73+
}
74+
)
75+
body = "\n".join(
76+
[
77+
f"ROOT_FOLDER={shlex.quote(str(root))}",
78+
"E=E",
79+
"SED_RED_YELLOW='&'",
80+
"SED_LIGHT_CYAN='&'",
81+
"SED_GREEN='&'",
82+
'print_3title() { echo "TITLE: $1"; }',
83+
"print_info() { :; }",
84+
f". {shlex.quote(str(self.function_file))}",
85+
"checkSnapConfineCVE20268933",
86+
]
87+
)
88+
result = subprocess.run(
89+
["sh", "-c", body],
90+
cwd=str(self.repo_root),
91+
env=env,
92+
capture_output=True,
93+
text=True,
94+
check=False,
95+
)
96+
return result, marker
97+
98+
def test_vulnerable_ubuntu_package_is_reported_without_executing_binary(self):
99+
with tempfile.TemporaryDirectory() as tmpdir:
100+
root, binary = self._make_root(Path(tmpdir))
101+
result, marker = self._run_check(root)
102+
self.assertFalse(marker.exists(), "the privileged binary must never be executed")
103+
104+
self.assertEqual(result.returncode, 0, result.stderr)
105+
self.assertIn("TITLE: Set-capabilities snap-confine", result.stdout)
106+
self.assertIn("Vulnerable to CVE-2026-8933", result.stdout)
107+
108+
def test_release_specific_fixed_versions(self):
109+
cases = (
110+
("2.76+ubuntu22.04", "22.04", True),
111+
("2.76+ubuntu22.04.1", "22.04", False),
112+
("2.76+ubuntu24.04", "24.04", True),
113+
("2.76+ubuntu24.04.1", "24.04", False),
114+
("2.76+ubuntu26.04.2", "26.04", True),
115+
("2.76+ubuntu26.04.3", "26.04", False),
116+
("2.75.1", "snap", True),
117+
("2.76.1", "snap", False),
118+
)
119+
checks = []
120+
for version, release, vulnerable in cases:
121+
kind = "snap" if release == "snap" else "deb"
122+
checks.append(
123+
"sc8933_version_is_vulnerable "
124+
f"{shlex.quote(version)} {kind} {release} && "
125+
f"echo {version}=yes || echo {version}=no"
126+
)
127+
body = "\n".join(
128+
[f". {shlex.quote(str(self.function_file))}"] + checks
129+
)
130+
result = subprocess.run(
131+
["sh", "-c", body],
132+
cwd=str(self.repo_root),
133+
capture_output=True,
134+
text=True,
135+
check=False,
136+
)
137+
138+
self.assertEqual(result.returncode, 0, result.stderr)
139+
for version, _, vulnerable in cases:
140+
expected = "yes" if vulnerable else "no"
141+
self.assertIn(f"{version}={expected}", result.stdout)
142+
143+
def test_fixed_ubuntu_package_is_not_flagged(self):
144+
with tempfile.TemporaryDirectory() as tmpdir:
145+
root, binary = self._make_root(Path(tmpdir))
146+
result, _ = self._run_check(root, package_version="2.76+ubuntu24.04.1")
147+
148+
self.assertEqual(result.returncode, 0, result.stderr)
149+
self.assertIn("not in the known CVE-2026-8933 vulnerable range", result.stdout)
150+
self.assertNotIn("Vulnerable to CVE-2026-8933", result.stdout)
151+
152+
def test_vulnerable_snap_revision_is_reported(self):
153+
with tempfile.TemporaryDirectory() as tmpdir:
154+
root, binary = self._make_root(Path(tmpdir), snap_version="2.75.1")
155+
result, _ = self._run_check(root)
156+
157+
self.assertEqual(result.returncode, 0, result.stderr)
158+
self.assertIn("Vulnerable to CVE-2026-8933", result.stdout)
159+
self.assertIn("version 2.75.1", result.stdout)
160+
161+
def test_setuid_snap_confine_is_excluded(self):
162+
with tempfile.TemporaryDirectory() as tmpdir:
163+
root, binary = self._make_root(Path(tmpdir))
164+
binary.chmod(0o4755)
165+
result, _ = self._run_check(root)
166+
167+
self.assertEqual(result.returncode, 0, result.stderr)
168+
self.assertEqual("", result.stdout)
169+
170+
171+
if __name__ == "__main__":
172+
unittest.main()

0 commit comments

Comments
 (0)