Linux evo.fastest-server.com 5.14.0-284.1101.el9.tuxcare.11.els11.x86_64 #1 SMP PREEMPT_DYNAMIC Fri Aug 14 13:30:35 UTC 2026 x86_64
LiteSpeed
Server IP : 103.249.112.113 & Your IP : 216.73.217.135
Domains : 988 Domain
User : tanishks
Terminal
Auto Root
Create File
Create Folder
Localroot Suggester
Backdoor Destroyer
Readme
/
usr /
share /
l.v.e-manager /
cpanel /
cgi /
Delete
Unzip
Name
Size
Permission
Date
Action
CloudLinux
[ DIR ]
drwxr-xr-x
2026-09-04 11:42
CloudLinux.cgi
9.97
KB
-rwxr-xr-x
2026-08-11 11:38
CloudLinux.pm
18.83
KB
-rw-r--r--
2026-08-11 11:38
Save
Rename
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENSE.TXT package CloudLinux; use strict; use warnings; use JSON::XS; use Text::Trim qw(trim); use Cpanel::SafeRun::Object(); use Whostmgr::HTMLInterface (); use Whostmgr::ACLS (); use MIME::Base64; use CloudLinux::SafeFile qw(safe_open_in); use constant ASSETS_PATH => "/3rdparty/cloudlinux/assets"; use constant OWNER_ADMIN => 'admin'; use constant OWNER_USER => 'user'; use constant OWNER_RESELLER => 'reseller'; use constant APP_MODE => 'PRODUCTION_MODE'; use constant DEFAULT_LANGUAGE => 'en'; use constant DOC_ROOT => "/usr/local/cpanel/whostmgr/docroot"; use constant CLOUDLINUX_CLI => '/usr/share/l.v.e-manager/utils/cloudlinux-cli.py'; use constant CLOUDLINUX_CLI_USER => '/usr/share/l.v.e-manager/utils/cloudlinux-cli-user.py'; my $CURRENT_USER = $ENV{'TEAM_OWNER'} ? $ENV{'TEAM_OWNER'} : $ENV{'REMOTE_USER'}; my $current_locale; my $user_type; sub detectLocale { $current_locale = _getCurrentLocale($_[0]); } sub parseForm { my (%DATA) = @_; my %result; foreach my $key (keys %DATA) { if ($key =~ /^file\-/) { if ($key =~ /^file-(.+)-key$/) { my $fileName = $1; my $filePath = $DATA{"file-$1"}; if ($filePath =~ /\/Cpanel_Form_file\.upload\.[a-z0-9]{8,10}$/) { unshift (@{$result{$DATA{$key}}}, {'name' => $fileName, 'file' => $filePath}); } } } elsif ($key =~ /^([^\[\]]+)(\[.+\])$/) { my $name_of_param = $1; my $path = $2; my @parts = $path=~/\[([^\[\]]+)\]/g; unshift(@parts, $name_of_param); creatBranch(\@parts, \%result, $DATA{$key}); } else { $result{$key} = $DATA{$key}; } } return %result; } sub creatBranch { my ($parts, $post, $value) = @_; my $first = shift(@$parts); if (ref($_[1]) eq 'HASH') { if (exists $_[1]{$first}) { creatBranch(\@$parts, $_[1]{$first}, $value); } else { $_[1]{$first} = @$parts ? \%{getInnerValues($value, @$parts)} : $value; } } } sub getInnerValues { my ($value, @parts) = @_; my $first = shift(@parts); if (@parts) { return {$first => getInnerValues($value, @parts)} ; } else { return {$first => $value}; } } sub _getApplicationMode { my $modeFile = '/usr/share/l.v.e-manager/spa/app_mode.status'; if (-e $modeFile) { return trim(safeRun('cat', $modeFile)); } return APP_MODE; } # Scanner note (F-06, CLOS-5520, 2026-07-21): the AI security scanner # reads this as a cPanel API2 handler (`cpanel_jsonapi_module=CloudLinux` # / `cpanel_jsonapi_func=safeRun`) where an authed cPanel user could # smuggle an arbitrary `$program` argv. Refuted: this file is `package # CloudLinux;` (not `Cpanel::CloudLinux;`), loaded via `use CloudLinux;` # from CGI scripts under `cpanel/cgi/`, `cpanel/lveversion/`, and # `cpanel/resource_usage/` — see e.g. cpanel/lveversion/cloudlinux- # selector.cgi:64 and cpanel/cgi/CloudLinux.cgi. API2 dispatch requires # a `Cpanel::` package namespace; without it the module is never bound # to `cpanel_jsonapi_func` at all. Every in-tree call to safeRun in this # file passes `$program` as a hardcoded string literal ('cat', 'id', # 'cpapi2', CLOUDLINUX_CLI, CLOUDLINUX_CLI_USER); no `$program` comes # from CGI/HTTP input. Do not follow the scanner's fix hint. # # safeRun() runs an external program via Cpanel::SafeRun::Object with # argv-level safety. It MUST NOT route commands through a shell — every # argument is passed as a discrete argv element to execve(), so shell # metacharacters in any argument are inert. # # Preferred signature: safeRun(<program>, <arg1>, <arg2>, ...) # Legacy form: safeRun([<program>, <arg1>, ...]) (arrayref) # Compatibility form: safeRun("<program> <arg1> <arg2>") — only for # space-separated constant strings the project controls; rejected with # die() if any shell metachar is present so a future caller that # smuggles user input fails loudly. # # For callers that genuinely need a shell prefix (e.g. `ulimit`), use # _runWithUlimits() — it pins bash to a constant `-c` template and # passes the real argv positionally via `exec "$@"`, never as command- # string bytes. sub safeRun { my @args = @_; if (ref($args[0]) eq 'ARRAY') { @args = @{ $args[0] }; } elsif (@args == 1 && $args[0] =~ /\s/) { my $line = $args[0]; # Reject the legacy single-string form if it contains anything # the shell would interpret; the migration target is argv form. if ($line =~ tr{<>|;&`$()\\\n\r\t}{}) { die "safeRun: shell metacharacters in single-string form are not allowed — pass an argv list instead. Got: $line"; } @args = split ' ', $line; } my ($program, @cmd_args) = @args; my $proc = Cpanel::SafeRun::Object->new( 'program' => $program, 'args' => [ @cmd_args ], 'keep_env' => 1 ); my $stdout = trim($proc->stdout()); my $stderr = $proc->stderr(); return $stdout eq '' ? $stderr : $stdout; } # Run program+argv under a `bash -c` prefix that sets ulimits for the # child process. Bash's -c template is a fixed constant; the program and # its argv flow through `exec "$@"` which is argv-safe (positional # parameters are not word-split or re-parsed). Used by jsonHandler and # lvemanagerHandler for ADMIN-mode CLI invocations that need the cpsrvd # memory/vmem caps lifted before the cloudlinux-cli child starts. sub _runWithUlimits { my ($program, @args) = @_; my $proc = Cpanel::SafeRun::Object->new( 'program' => '/bin/bash', 'args' => [ '-c', 'ulimit -m unlimited -v unlimited && exec "$@"', '--', $program, @args, ], 'keep_env' => 1, ); my $stdout = trim($proc->stdout()); my $stderr = $proc->stderr(); return $stdout eq '' ? $stderr : $stdout; } sub _getUserIdByName { my ($user_name) = @_; # argv form — $user_name is delivered as a single argv element to # `id`, so shell metacharacters in $CURRENT_USER cannot be parsed # as a command (finding [20]). return trim(safeRun('id', '-u', $user_name)); } sub setJsonHeader { my ($content) = @_; responseCustomHeaders("Content-type: application/json\n\n", $content); } # Scanner note (F-31, CLOS-5545, 2026-07-21): the AI security scanner # reads responseFile as a cPanel API2 handler that lets an authed cPanel # user stream `/var/log/secure` etc. by passing an attacker-selected # `$filename`. Refuted for the same reason as F-06 (safeRun above): this # module is `package CloudLinux;` — not `Cpanel::CloudLinux;` — so cPanel # API2 does NOT dispatch it. The only in-tree caller is lvemanagerHandler # at line 493 below, which invokes responseFile with a `$response{'filepath'}` # that the trusted backend CLI (`cloudlinux-cli.py` / `cloudlinux-cli-user.py`) # produced — the filename is server-picked, not caller-picked. Do not follow # the scanner's fix hint. sub responseFile { my ($filename) = @_; my @allowed_dirs = ('/var/lve/', '/var/log/'); my ($fh, $err) = safe_open_in($filename, \@allowed_dirs); if (!defined $fh) { sendError("File download error", 0, 0, $err); } binmode $fh; my $filesize = -s $fh; print "Content-Type:application/x-download\n"; print "Content-Length: $filesize\n\n"; local $/ = \10240; while (<$fh>){ print $_; } close($fh); exit; } sub responseCustomHeaders { my ($headers, $content) = @_; print "HTTP/1.1 200 OK\n"; print $headers; print $content; } sub knockKnock { setJsonHeader('{"result":"success"}'); } sub sendError { my ($errorMessage, $isJSON, $logoutSignal, $details) = @_; print "HTTP/1.1 503 Service Unavailable\n"; print "Content-type: application/json\n\n"; if ($isJSON) { print $errorMessage; } else { my %res = ( 'result' => $errorMessage, 'logoutSignal' => $logoutSignal ? $logoutSignal : 0, 'details' => $details || '' ); print encode_json \%res; } exit; } sub sendUnavailableError { my ($pluginName) = @_; print "HTTP/1.1 503 Service Unavailable\n"; print "Content-type: application/json\n\n"; my %res = ( 'result' => '', 'code' => 503, 'error_id' => 'ERROR.not_available_plugin', 'context' => { 'pluginName' => $pluginName, }, 'icon' => 'disabled' ); print encode_json \%res; exit; } sub checkMethod { if(($ENV{REQUEST_METHOD} ne $_[0]) && (!defined($_[1]) || $ENV{REQUEST_METHOD} ne $_[1])) { print "HTTP/1.1 405 Method Not Allowed\n"; print "Content-type: text/html\n\n"; print "Method Not Allowed"; exit; } } sub getPluginVersion { return safeRun('cat', '/usr/share/l.v.e-manager/version'); } sub _getCurrentLocale { my $cgi = $_[0]; my $locale = _getLocaleFromCookie($cgi) || _getSystemLocale(); return $locale; } sub _getLocaleFromCookie { my $cgi = $_[0]; my $val = $cgi->cookie('session_locale'); # session_locale is fully attacker-controllable (cookie) and is interpolated into a # <script>-context JS string literal at loadGlobalVariables(); reject anything that # is not a locale-shaped identifier so the value cannot break out of the JS string. return (defined $val && $val =~ /\A[A-Za-z][A-Za-z0-9_-]*\z/) ? $val : undef; } sub _getSystemLocale { # argv form — $CURRENT_USER is delivered as a single argv element so # shell metacharacters in TEAM_OWNER/REMOTE_USER cannot be parsed # (finding [20]). Stderr is intentionally suppressed (cpapi2 emits # noisy warnings the caller does not care about); we drop it on the # floor by passing an open /dev/null filehandle to SafeRun. my @cpapi2 = ('cpapi2'); push @cpapi2, ('--user=' . $CURRENT_USER) if $user_type ne OWNER_USER; push @cpapi2, ('Locale', 'get_user_locale', '--output=json'); open(my $devnull, '>', '/dev/null') or die "cannot open /dev/null: $!"; my $proc = Cpanel::SafeRun::Object->new( program => $cpapi2[0], args => [ @cpapi2[1..$#cpapi2] ], stderr => $devnull, keep_env => 1, ); my $responseInJson = trim($proc->stdout()); close $devnull; my %response; eval { %response = %{decode_json($responseInJson)}; }; # If decode_json is catched an exeption or specified key in result doesn't exist # set default language if ($@ || !exists $response{'cpanelresult'}{'data'}[0]{'locale'}) { return DEFAULT_LANGUAGE; } else { return $response{'cpanelresult'}{'data'}[0]{'locale'}; } } sub loadAssets { my ($assetsPath, $mainBundle, $config, $assetsStaticPath) = @_; Whostmgr::HTMLInterface::load_css($assetsPath.'/css/bootstrap.min.css'); Whostmgr::HTMLInterface::load_css($assetsPath.'/css/lvemanager.css'); Whostmgr::HTMLInterface::load_css($assetsPath.'/static/common-styles.css'); loadGlobalVariables($assetsStaticPath); Whostmgr::HTMLInterface::load_js($assetsPath.'/js/'.$config.'.js'); # For integration tests, don't remove comment in production in line below #Whostmgr::HTMLInterface::load_js($assetsPath.'/js/interceptor.js'); #for integration tests my $pluginVersion = getPluginVersion(); Whostmgr::HTMLInterface::load_js( sprintf('%s/static/polyfills.bundle.min.js?v=%s', $assetsPath, $pluginVersion) ); my $vendorBundle = $mainBundle eq 'main' ? 'vendor' : $mainBundle.'.vendor'; Whostmgr::HTMLInterface::load_js( sprintf('%s/static/%s.bundle.min.js?v=%s', $assetsPath, $vendorBundle, $pluginVersion) ); Whostmgr::HTMLInterface::load_js( sprintf('%s/static/%s.bundle.min.js?v=%s', $assetsPath, $mainBundle, $pluginVersion) ); } sub getDataContent { my ($folder, $file_name, $print) = @_; # Defense-in-depth: even though every in-tree caller hardcodes both args, # the function dereferences $folder/$file_name as filesystem path components # via 2-arg open(). A future caller that forwards request input would land # the value at this sink directly. Reject path-separator and dot-dot # sequences in either component (locale codes / template basenames don't # need them), and switch to 3-arg open with explicit '<' mode so a leading # '|' / '>' in $file cannot be interpreted as a pipe/redirect open. if ($folder =~ m{[/\\]} || $folder =~ /\.\./ || $file_name =~ m{[/\\]} || $file_name =~ /\.\./) { my $err = qq{<div class="error_block"> The specified file does not exist</div>}; if ($print) { print $err; return; } else { return $err; } } my $file = DOC_ROOT.ASSETS_PATH."/$folder/$file_name"; my $content = ''; if (-e $file) { open(my $fh, '<', $file) or do { $content = qq{<div class="error_block"> The specified file does not exist</div>}; if ($print) { print $content; return; } else { return $content; } }; while (<$fh>){ $content .= $_; } close($fh); } else { $content = qq{<div class="error_block"> The specified file does not exist</div>}; } if ($print) { print $content; } else { return $content; } } sub jsonHandler { my %data; my %REQUEST = %{$_[0]}; my $requestBody = $_[1]; my @ALLOWED_COMMANDS = qw(lvectl cloudlinux-awp-admin cloudlinux-limits); $data{'owner'} = $user_type; $data{'command'} = 'lvectl'; $data{'plugin_name'} = 'jsonhandler'; foreach my $param (keys %REQUEST) { if ($param eq 'handler') { $data{'method'} = $REQUEST{'handler'}; } elsif ($param eq 'command') { if (grep {$REQUEST{'command'} eq $_} @ALLOWED_COMMANDS) { $data{'command'} = $REQUEST{'command'}; } else { sendError('COMMAND NOT ALLOWED'); } } elsif (ref($param) ne 'HASH' && $param ne 'cgiaction' ) { if (exists $REQUEST{'command'} && $REQUEST{'command'} eq 'cloudlinux-limits' && $param eq 'lveid') { $data{'params'}{'lve-id'} = $REQUEST{$param}; } else { $data{'params'}{$param} = $REQUEST{$param}; } } } if(defined $requestBody) { $data{'params'}{'stdin'} = $requestBody; } # ADMIN/jsonhandler path: ulimit prefix is required so cpsrvd's # memory/vmem caps don't kill cloudlinux-cli. _runWithUlimits pins # the shell template to a constant string and passes the CLI's argv # via `exec "$@"`, so the base64 --data value cannot be re-parsed # by the shell (finding [20]). my $b64 = encode_base64(encode_json(\%data), ''); my $responseInJson = _runWithUlimits(CLOUDLINUX_CLI, "--data=$b64"); setJsonHeader($responseInJson); } sub lvemanagerHandler { my ($REQUEST_REF, $plugin_name) = @_; my %REQUEST = %$REQUEST_REF; unless (exists $REQUEST{'command'}) { sendError('COMMAND NOT SPECIFIED'); } my %data; $data{'owner'} = $user_type; $data{'command'} = $REQUEST{'command'}; $data{'plugin_name'} = $plugin_name; if (exists $REQUEST{'method'}) { $data{'method'} = $REQUEST{'method'}; } if (exists $REQUEST{'params'}) { $data{'params'} = $REQUEST{'params'}; } if (exists $REQUEST{'attachments[]'}) { $data{'attachments'} = []; foreach my $file ( @{$REQUEST{'attachments[]'}} ) { unshift (@{$data{'attachments'}}, $file); } } if ($data{'owner'} ne OWNER_ADMIN) { $data{'user_info'} = { 'username' => $CURRENT_USER, 'lve-id' => _getUserIdByName($CURRENT_USER) }; } if (exists $REQUEST{'mockJson'} && $REQUEST{'mockJson'}) { $data{'mockJson'} = $REQUEST{'mockJson'}; } if (exists $REQUEST{'lang'} && $REQUEST{'lang'}) { $data{'lang'} = $REQUEST{'lang'}; } my $b64 = encode_base64(JSON::XS->new->encode(\%data), ''); my $responseInJson; if ($data{'owner'} eq OWNER_ADMIN) { # Needs ulimit prefix; constant shell template, argv via exec "$@". $responseInJson = _runWithUlimits(CLOUDLINUX_CLI, "--data=$b64"); } elsif ($data{'owner'} eq OWNER_RESELLER) { $responseInJson = safeRun(CLOUDLINUX_CLI, "--data=$b64"); } elsif ($data{'owner'} eq OWNER_USER) { $responseInJson = safeRun(CLOUDLINUX_CLI_USER, "--data=$b64"); } my %response; eval { %response = %{decode_json($responseInJson)}; }; # If decode_json is catched an exeption, send error header with backtrace if ($@ && $responseInJson ne '') { sendError('ERROR.wrong_received_data', 0, 0, $responseInJson); } if (exists $response{'result'} && $response{'result'} eq 'file') { responseFile($response{'filepath'}) } if (exists $response{'result'} && $response{'result'} ne 'success' && $response{'result'} ne 'rollback') { sendError($responseInJson, 1); } if ($responseInJson eq '') { sendError('RESPONSE OF COMMAND IS EMPTY'); } setJsonHeader($responseInJson); } sub detectOwner { if (_isAdmin()) { return setOwner(OWNER_ADMIN); } if (_isReseller()) { return setOwner(OWNER_RESELLER); } return setOwner(OWNER_USER); } sub setOwner { my ($owner) = @_; $user_type = $owner; return $owner; } sub _isAdmin { if (Whostmgr::ACLS::hasroot()) { return 1; } return 0; } sub _isReseller { my $RESELLER_LIST_FILE = '/var/cpanel/resellers'; my $result = 0; if (-e $RESELLER_LIST_FILE) { open my $f, $RESELLER_LIST_FILE or die "Could not open $RESELLER_LIST_FILE: $!"; while( my $line = <$f>) { my @data = split /:/, $line; if ($CURRENT_USER eq $data[0]) { $result = 1; last; } } close $f; } return $result; } sub loadGlobalVariables { my $appMode = _getApplicationMode(); my $pluginVersion = getPluginVersion(); my ($assetsStaticPath) = @_; my %globals = ( userType => $user_type, userName => $CURRENT_USER, currentLanguage => $current_locale, APP_MODE => $appMode, localePath => $assetsStaticPath . '/i18n/', assetsStaticPath => $assetsStaticPath . '/', pluginVersion => trim($pluginVersion), ); print '<script type="text/javascript">'; for my $name (qw(userType userName currentLanguage APP_MODE localePath assetsStaticPath pluginVersion)) { print 'var ' . $name . ' = ' . encode_json($globals{$name}) . ';'; } print '</script>'; } # Should be present for require 1;