9MjJ` UddlZddlZddlZddlZddlZddlmZddlmZddl m Z ddl m Z m Z ddlZddlZddlmZddlmZddlmZdd lmZdd lmZdd lmZdd lmZdd lm Z m!Z!ddl"m#Z#ddl$m%Z%dZ&e'e(d<dZ)e*e(d<dZ+e'e(d<dZ,e'e(d<ddiZ-Gdde!j.Z/GddZ0GddeZ1d ej2fd!Z3e j4d"egd#Z5ed$e*d%efd&Z6ej7Gd'd(Z8 dAd$e*d)e*d*e e*d e8fd+Z9d,Z:d-Z;ej<d.d/fej<d0d1fej<d2d3fej<d4d5ffZ=d6e e*d e e*fd7Z>d8e*d9e*d e?fd:Z@ dAd$e*d9e*d*e e*d e?fd;ZAd$e*d9e*d e?fd<ZBd$e*d=e*fd>ZC dAd$e*d?e*d*e e*d e8fd@ZDdS)BN)contextmanager)Path)Optional)urlsplit urlunsplit)drop_privileges)docroot) HTTPAdapter)ChunkedEncodingError) disable_quota)ReadTimeoutError)gettext)utils exceptions)is_allowed_ini_path)verify_user_owns_domain TIMEOUTz 127.0.0.1 _PIN_ADDRESSi_MAX_RESPONSE_BYTESi _CHUNK_BYTESz User-AgentzjMozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.13) Gecko/20101209 CentOS/3.6-2.el5.centos Firefox/3.6.13ceZdZdZdS)WebsiteNotRespondingc"||_||_dSN)urldetails)selfrrs R/opt/cloudlinux/venv/lib64/python3.11/site-packages/xray/internal/phpinfo_utils.py__init__zWebsiteNotResponding.__init__6s N)__name__ __module__ __qualname__r r!rrr5s#r!rc"eZdZdZdZdefdZdS)_BoundedResponsezDA drop-in replacement exposing `.text` from a bounded streamed read.textr)c||_dSrr()rr)s rr z_BoundedResponse.__init__@s  r!N)r"r#r$__doc__ __slots__strr r%r!rr'r';s:NNISr!r'c"eZdZdZfdZxZS)_LoopbackPinnedAdapterarequests adapter that pins every connection to loopback. The vhost is hosted on this machine; we must not follow the tenant-controlled public-DNS record. We rewrite the connect target to ``_PIN_ADDRESS`` while keeping the original hostname in the Host header so the local webserver routes to the correct vhost. The TLS SNI/cert mismatch that results is tolerated because the caller already requests verify=False. c t|j}|j}|V|j|jd<t }|jt d|j}t|||_tj |fi|S)NHost:)netloc) rrhostnamer3headersrportr_replacesupersend)rrequestkwargssplit original_host pinned_netloc __class__s rr9z_LoopbackPinnedAdapter.sendNs%%  $&+lGOF #(Mz%#/ > >%* > > $U^^=^%I%IJJGKuww|G..v...r!)r"r#r$r+r9 __classcell__)r?s@rr/r/DsB / / / / / / / / /r!r/returnctj}t}|d||d||S)Nhttp://https://)requestsSessionr/mount)sessionadapters r_pinned_sessionrJZsH  G$&&G MM)W%%% MM*g&&& Nr!c t5}||tdtd5}|t }|tD]m}|st|t|ztkr+tj td|tfz| |n|jpd} ||d}n'#t $r|dd}YnwxYwd d d n #1swxYwYd d d n #1swxYwYn#t"$rK}|jr!t'|jd t(rt+|t-| d }~wt.j$r6}tj td t-|zd }~wwxYwt3|S) zg retry on: - ChunkedEncodingError -> sometimes error happens due to network issues/glitch FT)timeoutverifyr5stream) chunk_sizezVphpinfo response for %s exceeds %d bytes; refusing to buffer attacker-controlled body.zutf-8replace)errorsNr)rzUnable to detect php version for website because it is not accessible. Try again and contact an administrator if the issue persists. Original error: %s. )rJgetrHEADERSraise_for_status bytearray iter_contentrlenrrXRayManagerError_extendencodingdecode LookupErrorConnectionErrorargs isinstancer rr-rERequestExceptionr')rrHresponsebufchunkr\r)es r _request_urlrgbs )0    =' C$+D:: ==E  % % ' ' '++C!..,.GG " "s88c%jj(+>>>$5IJJ 345666 5!!!!  (3GH =zz(9z== = = = zz')z<<  =- = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =: 888 6 j,<==  "3A7777  $000) % & &),A /00 00 D ! !!sE%E B5D6+DD6!D'$D6&D''D6* E 6D: :E =D: >E  E EEEE G1#AF))G1;1G,,G1username document_rootc#pKd}dtjdd}||z }t|5t5||dddn #1swxYwYdddn #1swxYwY |V|dS#|wxYw)Na d|jvr d|jS||jS)Nz/opt/altzalt-php)rzr)rrs rget_full_php_versionz%PhpConfiguration.get_full_php_versions5 * * *5T355 5 :$"8:::r!cd|jvrt|j|j}n|j}|r tt |d}n'#t tf$rtd|wxYwt|std|t|j||j std|jd||S)Nz link/confTstrictz2ini_scan_dir does not resolve to an existing dir: z$ini_scan_dir outside allowed paths: zini_scan_dir not bound to user z: ) rx_resolve_ini_path_in_cagefsrhr-rresolveOSError RuntimeError ValueErrorr_ini_scan_dir_belongs_to_userr{)rresolveds rabsolute_ini_scan_dirz&PhpConfiguration.absolute_ini_scan_dirs1 $+ + +24=$BSTTHH(H   tH~~55T5BBCC\*    $$$  'x00  G8GG1M8T-EGG  $dm$$$$s 0A $Bc|jdkS)Nzfpm-fcgi)ryrs r is_php_fpmzPhpConfiguration.is_php_fpms!Z//r!) r"r#r$r-__annotations__r{rpropertyrrrrr%r!rrvrvsMMMOOO*.#--- 73777X7;3;;;; ))X)V00X000r!rvrcr{ci}|dD]F}|s|d\}}|||<Gtd||d|S)N =)rhr{r%)r<striprv)rhrcr{configlinekeyvalues r_parse_configurationrsFt$$$$zz||  ZZ__ Ukkmms   /     r! /var/cagefs/z /etc/users/z^ea-php(?P\d{2})$z$/opt/cpanel/ea-php{v}/root/etc/php.dz^alt-php(?P\d{2})$z/opt/alt/php{v}/link/confz!^plesk-php(?P\d)(?P\d)$z$/opt/plesk/php/{maj}.{min}/etc/php.dz^php(?P\d{2})$z /usr/local/php{v}/lib/php.conf.dtrusted_handlerc|sdStD]J\}}|t|}|r!|jdi|cSKdS)uBuild the single global INI_LOCATION for a TRUSTED, FULL PHP handler. ``trusted_handler`` is the account's REAL provisioned handler carrying its distro family ('ea-php80', 'alt-php80', 'plesk-php80', 'php80'). Returns the ONE server-wide per-distro+version dir that handler's PHP scans, keyed BY the distro family — so the derived dir's family always equals the trusted handler's family, never the family of whatever dir the probe reported. Returns None when ``trusted_handler`` is empty or carries no distro family (a bare number such as custom's '80'): the expected dir cannot be uniquely derived, so the caller default-denies the global per-version accept branch. Nr%)_HANDLER_INI_LOCATION_TEMPLATESmatchr-format groupdict)rpatterntemplaters r_expected_ini_locationrGsx t<88 c/2233  8"8?77U__%6%677 7 7 7 8 4r!rootrch|d}||kp||dzS)zTrue if ``resolved`` is ``root`` itself or a path strictly inside it. The trailing-separator boundary stops /var/cagefs/01/aliceEVIL from being treated as inside alice's /var/cagefs/01/alice subtree. /)rstrip startswith)rrs r _is_withinr]s7 ;;s  D t  >x224#:>>>r!c$t||rdStt|z|rdSt|}|R t t |d}n#ttf$rYdSwxYw||krdSdS)u Confirm a resolved (realpath'd) ini_scan_dir is one the tenant may drive. ``resolved`` has already been realpath'd and prefix-checked against ALLOWED_INI_PREFIXES. That prefix list is intentionally broad and admits global root-owned dirs, so this is the default-deny binding gate: accept ONLY paths provably tied to the principal, reject everything else. In phpinfo mode the legitimate ini_scan_dir is one of: * the tenant's own CageFS subtree (the link/conf branch is re-rooted there by _resolve_ini_path_in_cagefs(self.username, ...)); * the tenant's own per-domain /etc/users//... dir; * for a NON-CageFS tenant (ea-php / Plesk / native), the global, root-owned per-distro+version INI_LOCATION for the account's PHP handler — e.g. /opt/cpanel/ea-php80/root/etc/php.d (see xray-ini-placement.md). This is legitimate, so rejecting it outright would regress those tenants. The danger is that ``ini_scan_dir`` AND the probe ``phpversion`` are both served from the tenant's own docroot, so a tenant could report a global dir its account does NOT actually run. A bare two-digit version compare is not enough: it is distro-BLIND, so an ea-php80 tenant could report the same-number alt-php dir /opt/alt/php80/link/conf and steer the root xray.ini write into a DIFFERENT distro family's server-wide, all-tenant-shared dir (cross-distro spoof) — on a mixed alt-php+ea-php server that dir exists, so strict resolve passes. To close that AND the cross-version spoof, the global branch accepts ONLY the EXACT INI_LOCATION derived from ``trusted_php_version`` — here the account's REAL provisioned FULL handler carrying its distro family (cPanel domain['version'] 'ea-php80', Plesk resolve_lsphp_version(handler), DA opts[...]['ver'] 'php80'), NOT the probe-reported version and NOT a distro-blind number — and requires ``resolved`` to equal it. Accepted: * the tenant's OWN CageFS subtree (/var/cagefs///...); * the tenant's OWN per-domain panel dir (/etc/users//...); * the SINGLE INI_LOCATION == _expected_ini_location(trusted_php_version). Rejected (default-deny): global config dirs with no version segment (/etc/php.d, /etc/php.scan.d, /etc/cl.php.d, /usr/share/cagefs[-skeleton] roots), a per-version dir of a DIFFERENT distro family (cross-distro spoof) or a DIFFERENT version (cross-version spoof) than the trusted handler, a trusted handler that carries no distro family (a bare number — expected dir cannot be derived), another tenant's CageFS subtree, another account's /etc/users// subtree, and any other dir not matched above. TNrF) _is_own_cagefs_subtreer_USERS_INI_ROOTrr-rrrr)rhrr{expecteds rrrgs^h11t /H,h77t&&9::H 4>>111>>??HH&   55  x  4 5s0A00BBctt|sdS tj|}n#t$rd}YnwxYw|rtt|d||S|t tdd}|d}t |dko |d|kS)alTrue if ``resolved`` is inside the tenant's OWN /var/cagefs subtree. Prefer the exact /var/cagefs// root when the server can supply the CageFS prefix (cagefsctl). When the prefix is unavailable, fall back to a structural check that still binds to the principal: the path must be /var/cagefs//[/...], i.e. ``username`` must occupy the per-user segment directly under a single shard directory. This never accepts another tenant's subtree (the username segment is pinned and realpath has already been applied) and avoids failing closed on a legitimate own-subtree path when a transient cagefsctl lookup fails. Note such a /var/cagefs path only reaches here when the prefix WAS resolvable in _resolve_ini_path_in_cagefs, so the fallback covers a mid-request race, not a routine "no prefix" case. FNrr)r _CAGEFS_ROOTrcagefsctl_get_prefix ExceptionrXlstripr<)rhrprefixrelpartss rrrs lH - -u+H55  J \>6>>H>>III 3|$$%% & - -c 2 2C IIcNNE u::? 3uQx833s . ==pathc tj|n#t$rYdSwxYwd|d|g} tj|dtj}n#tj$rYdSwxYw|drrs %%%%%%--------******""""""))))))444444""""""//////++++++++777777CCCCCC  c 'S&&& cJ  :6 /////[///,)134550"0"650"fc$6 N0N0N0N0N0N0N0N0d.2!$%c]6F*  4RZ'((+-RZ()) "RZ455+-RZ$%%') # HSMhsm,?S?C?D????.2III!$I%c]I6:IIIIX 4S 4C 4D 4 4 4 4F#SB.2+++"+%c]+6F++++++r!