����JFIF���������
__ __ __ __ _____ _ _ _____ _ _ _ | \/ | \ \ / / | __ \ (_) | | / ____| | | | | | \ / |_ __\ V / | |__) | __ ___ ____ _| |_ ___ | (___ | |__ ___| | | | |\/| | '__|> < | ___/ '__| \ \ / / _` | __/ _ \ \___ \| '_ \ / _ \ | | | | | | |_ / . \ | | | | | |\ V / (_| | || __/ ____) | | | | __/ | | |_| |_|_(_)_/ \_\ |_| |_| |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1 if you need WebShell for Seo everyday contact me on Telegram Telegram Address : @jackleetFor_More_Tools:
#!/opt/cloudlinux/venv/bin/python3 -sbb
# -*- coding: utf-8 -*-
#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2026 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENCE.TXT
#
"""Trust-boundary validation for panel-supplied document root strings.
The docroot value originates from the hosting panel (cPanel /
DirectAdmin / Plesk) and is consumed by privileged code that writes
jail.c mount configuration files read by root. The jail.c mount syntax
is whitespace-delimited (MountEntry.render in mount_types.py joins
source/target/options with spaces) and section headers are bracketed
(`[<docroot>]` in jail_config.MountConfig.render), so any whitespace,
control character, newline, or bracket inside the docroot corrupts the
parser. A sibling module already rejects newlines/carriage-returns on
the analogous crontab write path (crontab/libhooks.py and
crontab/parser.py); this module is the equivalent guard for the jail
mount config write path.
Validation is centralized at the trust boundary - call sites are
`enable_website_isolation` (where a tenant-owned domain is resolved to
a docroot for the first time) and `write_jail_mounts_config` (where
the docroot map is re-read from the panel for every regeneration). The
helper raises ValueError on rejection, matching the sibling crontab
pattern.
"""
from __future__ import annotations
import os
import re
# Strict allowlist: alnum, `_`, `-`, `.`, `/`. This excludes whitespace
# (which would split mount-line tokens), brackets (which would close the
# jail section header), quotes, and any control character. Production
# docroots in shared-hosting deployments are conventionally of the form
# `/home/<user>/<subdir>` and well inside this allowlist; anything
# outside it is either a panel misconfiguration or an injection attempt.
_DOCROOT_ALLOWED_RE = re.compile(r"^/[A-Za-z0-9_./-]*$")
# Hard cap to bound the size of strings that flow into mount lines and
# section headers. A 4 KiB ceiling is well above any realistic docroot
# (Linux PATH_MAX is 4096) and well below pathological mount-line
# sizes.
_DOCROOT_MAX_LEN = 4096
def validate_docroot(docroot: str) -> str:
"""Validate a panel-supplied document root before it reaches the
jail mount config writer.
Args:
docroot: Document root string returned by the panel (e.g. from
``clcommon.cpapi.docroot`` or ``clcommon.cpapi.userdomains``).
Returns:
The validated docroot string, unchanged.
Raises:
ValueError: If the docroot is empty, not an absolute path, too
long, contains a path-traversal segment, or contains any
character outside the strict allowlist (alnum, `_`, `-`,
`.`, `/`).
"""
if not isinstance(docroot, str):
raise ValueError(f"Invalid docroot (not a string): {docroot!r}")
if not docroot:
raise ValueError("Invalid docroot: empty string")
if len(docroot) > _DOCROOT_MAX_LEN:
raise ValueError(
f"Invalid docroot (length {len(docroot)} exceeds {_DOCROOT_MAX_LEN}): {docroot!r}"
)
if not docroot.startswith("/"):
raise ValueError(f"Invalid docroot (not absolute): {docroot!r}")
if not _DOCROOT_ALLOWED_RE.match(docroot):
raise ValueError(f"Invalid docroot (disallowed characters): {docroot!r}")
# Reject traversal segments. The allowlist permits `.` and `..` as
# path components even though it forbids most metacharacters, so
# screen explicitly: `..` lets a tenant's owned-domain docroot point
# at a sibling tenant's tree once it is bind-mounted as the jail
# source.
for segment in docroot.split("/"):
if segment == "..":
raise ValueError(f"Invalid docroot (parent traversal): {docroot!r}")
return docroot
def validate_docroot_no_symlinks(docroot: str, allowed_prefix: str) -> str:
"""Reject a docroot whose on-disk path escapes ``allowed_prefix``.
The lexical ``validate_docroot`` above pins the *string shape* of a
panel-supplied docroot, but the value still flows verbatim into
jail.c mount-line ``source`` fields and is later consumed by a
root-run mount executor that calls ``bind(2)``. Per Linux mount(2)
semantics, MS_BIND on a symlinked source dereferences the symlink
and bind-mounts the resolved path - which lets a tenant who can
write anywhere along the docroot's ancestor chain
(typically ``/home/<user>/public_html/<sub>`` on cPanel / DA / Plesk)
pre-aim the bind source at an arbitrary host path
(``ln -s / /home/<user>/public_html/evil``). The root-run jail then
happily bind-mounts ``/`` (or any other operator path the tenant
chose) inside the tenant's isolated namespace, exposing
``/etc/shadow``, other tenants' homes, etc.
The fix shape is the playbook's ``realpath() + assert the resolved
path begins with the allowed prefix``: canonicalise both the
docroot and the allowed prefix (so a benign operator-installed
symlink like ``/home -> /home2`` does not produce a false reject),
then require that the resolved docroot stays under the resolved
prefix. A tenant-planted symlink aimed outside the home tree
(``/home/u/public_html/evil -> /``) resolves to ``/``, which is
not a descendant of the user's resolved home and is rejected.
The sibling ``create_overlay_storage_directory`` already takes
the equivalent O_NOFOLLOW component-walk stance (see
``py/clcagefslib/webisolation/jail_utils.py``); this is the
docroot-side analogue called at the same trust boundary as
``validate_docroot``.
Args:
docroot: Document root string returned by the panel. Must
already have passed the lexical ``validate_docroot``
check (so it is absolute, ``..``-free, and within the
character allowlist) - this function does *not* re-run
those checks.
allowed_prefix: Absolute path the resolved docroot must be
inside. Typically the user's home directory; passing the
unresolved value is fine because the function canonicalises
it too.
Returns:
The docroot string, unchanged.
Raises:
ValueError: If the resolved docroot escapes the resolved
``allowed_prefix``, or if either path cannot be
canonicalised (e.g. because of an OS error other than
ENOENT).
"""
# strict=False so a not-yet-existent leaf does not raise; bind(2)
# resolves whatever exists on disk at mount time, and an entirely
# absent path resolves to itself - safe. The dangerous case is
# "some ancestor IS a symlink that points outside the user tree",
# which realpath surfaces by returning a path outside the resolved
# prefix.
try:
resolved_docroot = os.path.realpath(docroot, strict=False)
resolved_prefix = os.path.realpath(allowed_prefix, strict=False)
except OSError as exc:
raise ValueError(
f"Invalid docroot (cannot resolve: {exc}): {docroot!r}"
) from exc
# Component-aware prefix check: ``resolved_docroot == prefix`` (the
# user's home itself is a valid docroot in some panel
# configurations) OR ``resolved_docroot`` starts with
# ``prefix + '/'`` so ``/home/userfoo`` does not pass for prefix
# ``/home/user``.
if resolved_docroot != resolved_prefix and not resolved_docroot.startswith(
resolved_prefix + "/"
):
raise ValueError(
f"Invalid docroot (resolves to {resolved_docroot!r} outside "
f"allowed prefix {resolved_prefix!r}): {docroot!r}"
)
return docroot
| Name | Type | Size | Permission | Actions |
|---|---|---|---|---|
| __pycache__ | Folder | 0755 |
|
|
| crontab | Folder | 0755 |
|
|
| __init__.py | File | 248 B | 0644 |
|
| admin_config.py | File | 3.07 KB | 0644 |
|
| config.py | File | 1.74 KB | 0644 |
|
| docroot_validation.py | File | 7.53 KB | 0644 |
|
| jail_config.py | File | 2.72 KB | 0644 |
|
| jail_config_builder.py | File | 9.6 KB | 0644 |
|
| jail_utils.py | File | 7.88 KB | 0644 |
|
| libenter.py | File | 2.36 KB | 0644 |
|
| litespeed.py | File | 13.09 KB | 0644 |
|
| mount_config.py | File | 1.72 KB | 0644 |
|
| mount_ordering.py | File | 4.96 KB | 0644 |
|
| mount_types.py | File | 1.26 KB | 0644 |
|
| php.py | File | 3.73 KB | 0644 |
|
| service.py | File | 1.76 KB | 0644 |
|
| triggers.py | File | 1.9 KB | 0644 |
|