File manager - Edit - /usr/local/cpanel/base/3rdparty/cloudlinux/cl-selector.cgi
Back
#!/bin/bash eval 'if [ -x /usr/local/cpanel/3rdparty/bin/perl ]; then exec /usr/local/cpanel/3rdparty/bin/perl -x -- $0 ${1+"$@"}; else exec /usr/bin/perl -x -- $0 ${1+"$@"};fi' if 0; #!/usr/bin/perl # CloudLinux LVE Manager VERSION:0.83 # # CSRF/Referer note: # Application-level CSRF protection is intentionally absent in this CGI. # cPanel's cpsrvd enforces a per-session URL security token # (/cpsessNNNNNNNNNN/...) that gates every authenticated request before # the CGI is dispatched: requests without a matching token are rejected # with HTTP 401 and the Perl below never executes. The token is bound # to the session record (cp_security_token in Cpanel::Session) and is # not exposed to JavaScript via cookies, so a cross-site attacker # cannot construct a valid URL. Adding an in-CGI CSRF token would be # redundant and risks breaking legitimate non-browser callers (Hooks, # automation) that do not ship the application's csrftoken cookie. # CGI handlers also operate strictly on $ENV{REMOTE_USER}, so even a # hypothetical bypass would be confined to the user's own account. BEGIN { unshift @INC, "/usr/local/cpanel"; } use strict; use Cpanel::Form (); use Cpanel::SafeRun (); use Data::Dumper (); use Cpanel::JSON (); use POSIX (); my %FORM = Cpanel::Form::parseform(); my $user = ($ENV{'TEAM_OWNER'} ? $ENV{'TEAM_OWNER'} : $ENV{'REMOTE_USER'})||$ARGV[0]; my $action = exists $FORM{action} ? $FORM{action} : 'extlist'; my %dispatchTable = ( extlist => \&getExtensionList, optlist => \&getOptionList, extsave => \&saveExtensionSet, optsave => \&saveOptionSet, extdefaults => \&extensionsToDefaults, altappcreate => \&altappcreate, altappdelete => \&altappdelete, altappupdate => \&altappupdate, altapprestart => \&altapprestart, altappexecute => \&altappexecute, pymodlist => \&pymodlist, rbmodlist => \&rbmodlist, pythonmodver => \&pymodver, rubymodver => \&rbmodver, ); $action = 'extlist' unless exists $dispatchTable{$action}; $dispatchTable{$action}->(); sub getExtensionList { my %data; my ( $version, $status, $message ) = ( $FORM{version}, 'OK', [] ); my @params = ( '/usr/bin/selectorctl', '--list-user-extensions', "--user=$user", "--version=$version", '--all', ); my $result = Cpanel::SafeRun::Errors::saferunallerrors(@params); for my $line ( split( /\n/, $result ) ) { if ( index( $line, 'ERROR:' ) != -1) { $status = 'ERROR'; push @{ $message }, substr( $line, 6 ); last; } if ( index( $line, 'WARN:' ) != -1) { $status = 'WARN'; push @{ $message }, substr( $line, 5 ); next; } my ( $status, $extension ) = split /\s+/, $line; $status = $status eq '+' ? 1 : 0; $data{$extension} = $status; } my $output = { status => $status, data => [ map +{ title=>$_, status=>$data{$_} }, sort keys %data ], }; $output->{message} = $message if @{ $message }; my $d = Data::Dumper->new([ $output ]); print "Content-Type: application/json\n\n"; print $d->Terse(1)->Pair(':')->Useqq(1)->Deepcopy(1)->Dump; } sub getOptionList { my ( $version, $status, $message ) = ( $FORM{version}, 'OK', [] ); my @params = ( '/usr/bin/selectorctl', "--user=$user", "--version=$version", '--print-options', '--json', ); print "Content-Type: application/json\n\n"; print Cpanel::SafeRun::Errors::saferunallerrors(@params); } sub saveExtensionSet { my ( $version, $extset, $status, $message ) = ( $FORM{version}, $FORM{extset}, 'OK', [] ); my %exthash = map { $_ => 1 } split( /,/, $extset ); if ( exists $FORM{version} ) { $version = $FORM{version}; } my @params = ( '/usr/bin/selectorctl', '--interpreter=php', "--replace-user-extensions=$extset", "--user=$user", "--version=$version" ); my $result = Cpanel::SafeRun::Errors::saferunallerrors(@params); if ( $result ) { for my $line ( split( /\n/, $result ) ) { if ( index( $line, 'ERROR:' ) != -1) { $status = 'ERROR'; push @{ $message }, substr( $line, 6 ); last; } if ( index( $line, 'WARN:' ) != -1) { $status = 'WARN'; push @{ $message }, substr( $line, 5 ); if ( $line =~ /WARN:(\S+)\s(\S+).*?(?:\((.*)\))?\.?$/ ) { my ( $ext, $action, $arg ) = ( $1, $2, $3 ); if ( $action eq 'skipped' ) { $exthash{$ext} = 0; } elsif ( $action eq 'enabled' ) { $exthash{$ext} = 1 } elsif ( $action eq 'left' ) { $exthash{$ext} = 1 } } } } } my $output = { status => $status, data => [ map +{ title=>$_, status=>$exthash{$_} }, sort keys %exthash ], }; $output->{message} = $message if @{ $message }; my $d = Data::Dumper->new([ $output ]); print "Content-Type: application/json\n\n"; print $d->Terse(1)->Pair(':')->Useqq(1)->Deepcopy(1)->Dump; } sub saveOptionSet { my ( $version, $optset, $status, $message ) = ( $FORM{version}, $FORM{optset}, 'OK', [] ); my @params = ( '/usr/bin/selectorctl', "--user=$user", "--version=$version", "--replace-options=$optset", '--base64', '--json' ); print "Content-Type: application/json\n\n"; print Cpanel::SafeRun::Errors::saferunallerrors(@params); } sub extensionsToDefaults { my %exthash = (); my %buffer = (); my ( $version, $status, $message ) = ( $FORM{version}, 'OK', [] ); my @params = ( '/usr/bin/selectorctl', '--interpreter=php', '--reset-user-extensions', "--user=$user", "--version=$version", ); my $result = Cpanel::SafeRun::Errors::saferunallerrors(@params); if ( $result ) { for my $line ( split( /\n/, $result ) ) { if ( index( $line, 'ERROR:' ) != -1) { $status = 'ERROR'; push @{ $message }, substr( $line, 6 ); last; } if ( index( $line, 'WARN:' ) != -1) { $status = 'WARN'; push @{ $message }, substr( $line, 5 ); if ( $line =~ /WARN:(\S+)\s(\S+).*?(?:\((.*)\))?\.?$/ ) { my ( $ext, $action, $arg ) = ( $1, $2, $3 ); if ( $action eq 'skipped' ) { $buffer{$ext} = 0; } elsif ( $action eq 'enabled' ) { $buffer{$ext} = 1 } elsif ( $action eq 'left' ) { $buffer{$ext} = 1 } } next; } else { %exthash = map { $_ => 1 } split( /,/, $line ); } } } for my $ext ( keys %buffer ) { if ( $buffer{$ext} == 1 ) { $exthash{$ext} = 1; } elsif ( $buffer{$ext} == 0 ) { delete $exthash{$ext} if exists $exthash{$ext}; } } my $output = { status => $status, data => [ map +{ title=>$_, status=>$exthash{$_} }, sort keys %exthash ], }; $output->{message} = $message if @{ $message }; my $d = Data::Dumper->new([ $output ]); print "Content-Type: application/json\n\n"; print $d->Terse(1)->Pair(':')->Useqq(1)->Deepcopy(1)->Dump; } sub altappcreate { my ($app_path, $app_version, $app_uri, $alt, $domain) = ($FORM{path}, $FORM{version}, $FORM{uri}, $FORM{alt}, $FORM{domain}); # Validate every argv-bound value before constructing @params: $alt, # $app_version, and (when present) $domain are passed as separate argv # elements, so an unguarded '--user=victim' would otherwise smuggle a # flag into selectorctl. $app_path / $app_uri are positional but # isAllowedText now also rejects leading '-'. return if(isAllowedArg($alt) == 0); return if(isAllowedArg($app_version) == 0); return if($domain && isAllowedArg($domain) == 0); return if(isAllowedText($app_path) == 0); return if(isAllowedText($app_uri) == 0); my $data = {}; my @params = ( '/usr/bin/selectorctl', '--interpreter', $alt, '--user', $user); if ( $domain ) { push @params, ('--domain', $domain); # using --domain only if present in request data }; push @params, ( '--version', $app_version, '--print-summary', '--json', '--create-webapp', $app_path, $app_uri); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK') { for my $key ( keys %{ $rv->{data} } ) { next unless $key eq $app_path; $data->{data}->{path} = $key; $data->{data}->{version} = $rv->{data}->{$key}->{interpreter}->{version}; $data->{data}->{uri} = $rv->{data}->{$key}->{alias}; $data->{data}->{domain} = $rv->{data}->{$key}->{domain}; if ($rv->{data}->{$key}->{domains}) { $data->{data}->{domains} = $rv->{data}->{$key}->{domains}; } $data->{data}->{activate} = "source $rv->{data}->{$key}->{interpreter}->{prefix}/bin/activate"; $data->{data}->{modules} = { map { $_=>$rv->{data}->{$key}->{extensions}->{$_}->{version} } keys %{ $rv->{data}->{$key}->{extensions} } }; } @params = ( '/usr/bin/selectorctl', '--interpreter', $alt, '--list', '--json', ); $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); $rv = Cpanel::JSON::Load($rv); my $versions = [sort keys %{$rv->{data}}]; $data->{data}->{interpreters} = $versions; $data->{status} = 'OK'; } else { $data->{status} = lc $rv->{status}; $data->{message} = $rv->{message}; } my $d = Data::Dumper->new([$data]); print "Content-Type: application/json\n\n"; print $d->Terse(1)->Pair(':')->Useqq(1)->Deepcopy(1)->Dump; } sub altappdelete { my ($app_path, $alt) = ($FORM{path}, $FORM{alt}); return if(isAllowedArg($alt) == 0); return if(isAllowedText($app_path) == 0); my $data = {}; my @params = ( '/usr/bin/selectorctl', '--interpreter', $alt, '--user', $user, '--json', '--destroy-webapp', $app_path, ); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); chomp $rv; $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK') { $data->{data}->{path} = $app_path; $data->{status} = 'OK'; } else { $data = $rv; } my $d = Data::Dumper->new([$data]); print "Content-Type: application/json\n\n"; print $d->Terse(1)->Pair(':')->Useqq(1)->Deepcopy(1)->Dump; } sub altapprestart { my ($app_path, $alt) = ($FORM{path}, $FORM{alt}); return if(isAllowedArg($alt) == 0); return if(isAllowedText($app_path) == 0); my $data = {}; my @params = ( '/usr/bin/selectorctl', '--interpreter', $alt, '--user', $user, '--json', '--restart-webapp', $app_path, ); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); chomp $rv; $rv = Cpanel::JSON::Load($rv); $rv->{status} = $rv->{status} eq 'OK' ? $rv->{status} : lc($rv->{status}); my $d = Data::Dumper->new([$rv]); print "Content-Type: application/json\n\n"; print $d->Terse(1)->Pair(':')->Useqq(1)->Deepcopy(1)->Dump; }; sub altappexecute { chdir; my ($app_path, $alt, $app_version, $cmd) = ($FORM{path}, $FORM{alt}, $FORM{version}, $FORM{cmd}); my @allowed_symb = (0..9, 'a'..'z', 'A'..'Z', ' ', '-', '_', '/', '.', ',', '"', '~', '>', '<'); my %json_output = ('status' => 'OK'); my @bad_symb = grep ( (index("@allowed_symb", $_) == -1), split('',$cmd)); if ( @bad_symb ) { push_json( { "status" => "warn", 'message' => "Can not run command '$cmd'; disallowed symbols present: '@bad_symb' " } ); }; my $user_summary_raw = Cpanel::SafeRun::Errors::saferunallerrors("/usr/bin/selectorctl", "--interpreter=$alt", "--user-summary", "--json"); chomp $user_summary_raw; my $user_summary = Cpanel::JSON::Load($user_summary_raw); my $prefix; if ( $user_summary->{status} eq 'OK' ) { $prefix = $user_summary->{data}->{$app_path}->{interpreter}->{prefix}; } else { push_json( { 'status' => 'ERROR', 'message' => $user_summary->{'message'} } ); } my $activate_path = "$prefix/bin/activate"; my @params = ("/bin/cagefs_enter", "/bin/bash", "-c", "source $activate_path && $cmd"); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); if ( $? ) { push_json( { 'status' => 'ERROR', 'message' => $rv } ); } else { push_json( { 'status' => 'info', 'message' => 'Command execution succeeded' } ); } } sub altappupdate { my $dump_dispatch = sub { my ($info) = @_; my $d = Data::Dumper->new([$info]); print "Content-Type: application/json\n\n"; print $d->Terse(1)->Pair(':')->Useqq(1)->Deepcopy(1)->Dump; }; # $FORM{alt} feeds every branch below as a separate argv element to # selectorctl. Validate unconditionally — isAllowedArg rejects an # undef value, matching altappcreate / altappdelete / altapprestart. # $FORM{'path-orig'} is validated only when present because each # branch is already gated on `exists $FORM{'path-orig'}`; calling # altappupdate without it is a documented no-op that should keep # returning {status: OK}. return if(isAllowedArg($FORM{alt}) == 0); return if(exists $FORM{'path-orig'} && isAllowedText($FORM{'path-orig'}) == 0); my $data = {}; if (exists $FORM{'uri-curr'} and exists $FORM{'path-orig'}) { my($domain, $uri) = split('/', $FORM{'uri-curr'}, 2); if (!$uri) {$uri='/'}; return if(isAllowedText($uri) == 0); return if($domain && isAllowedArg($domain) == 0); my @params = ( '/usr/bin/selectorctl', '--interpreter', $FORM{alt}, '--user', $user); if ( $domain ) { push @params, ('--domain', $domain); # using --domain only if present in request data }; push @params, ( '--print-summary', '--json', '--transit-webapp', $FORM{'path-orig'}, $uri); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK' and exists $rv->{data}->{$FORM{'path-orig'}}) { $data->{uri} = "$domain/" . $rv->{data}->{$FORM{'path-orig'}}->{alias}; if ($rv->{data}->{$FORM{'path-orig'}}->{domains}) { $data->{domains} = $rv->{data}->{$FORM{'path-orig'}}->{domains}; } } else { my $data_to_return = {status=>lc($rv->{status})}; $data_to_return->{message} = $rv->{message} if exists $rv->{message}; $data_to_return->{data} = $rv->{data} if exists $rv->{data}; $dump_dispatch->($data_to_return); return; } } if (exists $FORM{'version-curr'} and exists $FORM{'path-orig'}) { return if(isAllowedArg($FORM{'version-curr'}) == 0); my @params = ('/usr/bin/selectorctl', '--interpreter', $FORM{alt}, '--user', $user, '--print-summary', '--json', '--version', $FORM{'version-curr'}, '--set-user-current', $FORM{'path-orig'}); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK' and exists $rv->{data}->{$FORM{'path-orig'}}) { my $activate_path = $rv->{data}->{$FORM{'path-orig'}}->{interpreter}->{prefix}.'/bin/activate'; $data->{version} = $rv->{data}->{$FORM{'path-orig'}}->{interpreter}->{version}; $data->{activate} = "source $activate_path"; } else { my $data_to_return = {status=>lc($rv->{status})}; $data_to_return->{message} = $rv->{message} if exists $rv->{message}; $data_to_return->{data} = $rv->{data} if exists $rv->{data}; for my $key (keys %{$data}){ $data_to_return->{data}->{$key} = $data->{$key}; } $dump_dispatch->($data_to_return); return; } } if (exists $FORM{'wsgi-curr'} and exists $FORM{'path-orig'}) { return if(isAllowedArg($FORM{'wsgi-curr'}) == 0); my @params = ('/usr/bin/selectorctl', '--interpreter', $FORM{alt}, '--user', $user, '--print-summary', '--json', '--setup-wsgi', $FORM{'wsgi-curr'}, $FORM{'path-orig'}); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK' and exists $rv->{data}->{$FORM{'path-orig'}}) { $data->{wsgi} = $rv->{data}->{$FORM{'path-orig'}}->{wsgi}; } else { my $data_to_return = {status=>lc($rv->{status})}; $data_to_return->{message} = $rv->{message} if exists $rv->{message}; $data_to_return->{data} = $rv->{data} if exists $rv->{data}; for my $key (keys %{$data}){ $data_to_return->{data}->{$key} = $data->{$key}; } $dump_dispatch->($data_to_return); return; } } if (exists $FORM{remmod} and exists $FORM{'path-orig'}) { return if(isAllowedArg($FORM{remmod}) == 0); my @params = ('/usr/bin/selectorctl', '--interpreter', $FORM{alt}, '--user', $user, '--print-summary', '--json', '--disable-user-extensions', $FORM{remmod}, $FORM{'path-orig'}); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK') { my @params = ('/usr/bin/selectorctl', '--interpreter', $FORM{alt}, '--user', $user, '--json', '--list-user-extensions', $FORM{'path-orig'}); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK') { $data->{modules} = {map {$_=>$rv->{data}->{$_}->{version}} keys %{$rv->{data}}}; } } else { my $data_to_return = {status=>lc($rv->{status})}; $data_to_return->{message} = $rv->{message} if exists $rv->{message}; $data_to_return->{data} = $rv->{data} if exists $rv->{data}; for my $key (keys %{$data}){ $data_to_return->{data}->{$key} = $data->{$key}; } $dump_dispatch->($data_to_return); return; } } if (exists $FORM{addmod} and exists $FORM{'path-orig'}) { return if(isAllowedArg($FORM{addmod}) == 0); my @params = ('/usr/bin/selectorctl', '--interpreter', $FORM{alt}, '--user', $user, '--print-summary', '--json', '--enable-user-extensions', $FORM{addmod}, $FORM{'path-orig'}); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK') { my @params = ('/usr/bin/selectorctl', '--interpreter', $FORM{alt}, '--user', $user, '--json', '--list-user-extensions', $FORM{'path-orig'}); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK') { $data->{modules} = {map {$_=>$rv->{data}->{$_}->{version}} keys %{$rv->{data}}}; } else { $data->{modules} = {}; } } else { my $data_to_return = {status=>lc($rv->{status})}; $data_to_return->{message} = $rv->{message} if exists $rv->{message}; $data_to_return->{data} = $rv->{data} if exists $rv->{data}; for my $key (keys %{$data}){ $data_to_return->{data}->{$key} = $data->{$key}; } $dump_dispatch->($data_to_return); return; } } if (exists $FORM{'path-curr'} and exists $FORM{'path-orig'}) { return if(isAllowedText($FORM{'path-curr'}) == 0); my @params = ('/usr/bin/selectorctl', '--user', $user, '--interpreter', $FORM{alt}, '--print-summary', '--json', '--relocate-webapp', $FORM{'path-orig'}, $FORM{'path-curr'}); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK') { my $activate_path = $rv->{data}->{$FORM{'path-curr'}}->{interpreter}->{prefix}.'/bin/activate'; my ($uid, $gid, $homedir) = (getpwnam($user))[2,3,7]; my @params = ('/bin/cp', '-r', $homedir.'/'.$FORM{'path-orig'}.'/*', $homedir.'/'.$FORM{'path-curr'}); # Drop root before copying into the tenant's homedir: the # destination ($homedir/$FORM{path-curr}) is a tenant-writable # path and the tenant can replace it with a symlink to a # root-owned target between --relocate-webapp and cp (symlink # TOCTOU). Running cp as the tenant ensures any followed link # is bounded by the tenant's own filesystem permissions. if (!defined $uid || !defined $gid) { # getpwnam failed (or returned an incomplete entry). # Without the privilege-dropped cp the destination # homedir is never populated, so we MUST NOT mark the # response activate-ready — that's the same UI-misleads- # operator failure mode the cp-child error branch below # already guards against. my $data_to_return = { status => 'error', message => "Cannot resolve uid/gid for user '$user'; " . "refusing to mark relocation activate-ready " . "without copying webapp files.", }; for my $key (keys %{$data}) { $data_to_return->{data}->{$key} = $data->{$key}; } $dump_dispatch->($data_to_return); return; } else { my $pid = fork(); if (!defined $pid) { die "fork failed: $!"; } elsif ($pid == 0) { $) = "$gid $gid"; $( = $gid; POSIX::setgid($gid) or POSIX::_exit(71); POSIX::setuid($uid) or POSIX::_exit(71); if ($> != $uid || $< != $uid) { POSIX::_exit(71); } exec({$params[0]} @params) or POSIX::_exit(71); } else { waitpid($pid, 0); # Bugbot d2fcddc3: the cp child runs as the tenant # after privilege drop, so a tenant-induced failure # (missing source, EPERM, signal) must NOT yield an # OK relocation response. Propagate the failure to # the caller using the script's standard error shape # so the UI does not mark the operation activate-ready # while the homedir copy never happened. my $status = $?; if ($status != 0) { my $exit_code = $status >> 8; my $signal = $status & 0x7f; my $reason = $signal ? "killed by signal $signal" : "exited with status $exit_code"; my $data_to_return = { status => 'error', message => "Failed to copy webapp files to new " . "location: cp child $reason", }; for my $key (keys %{$data}) { $data_to_return->{data}->{$key} = $data->{$key}; } $dump_dispatch->($data_to_return); return; } } $data->{path} = $FORM{'path-curr'}; $data->{activate} = "source $activate_path"; } } else { my $data_to_return = {status=>lc($rv->{status})}; $data_to_return->{message} = $rv->{message} if exists $rv->{message}; $data_to_return->{data} = $rv->{data} if exists $rv->{data}; for my $key (keys %{$data}){ $data_to_return->{data}->{$key} = $data->{$key}; } $dump_dispatch->($data_to_return); return; } } $dump_dispatch->({status=>'OK', data=>$data}); } sub pymodlist { my @params = ( '/usr/bin/selectorctl', '--interpreter=python', '--list-extensions', '--json', ); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); chomp $rv; my $data; $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK') { $data = {status=>'OK', data=>[sort { "\L$a" cmp "\L$b" } keys %{ $rv->{data} }]}; } else { $data = $rv; } my $d = Data::Dumper->new([$data]); print "Content-Type: application/json\n\n"; print $d->Terse(1)->Pair(':')->Useqq(1)->Deepcopy(1)->Dump; } sub rbmodlist { my @params = ( '/usr/bin/selectorctl', '--interpreter=ruby', '--list-extensions', '--json', ); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); chomp $rv; my $data; $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK') { $data = {status=>'OK', data=>[sort { "\L$a" cmp "\L$b" } keys %{ $rv->{data} }]}; } else { $data = $rv; } my $d = Data::Dumper->new([$data]); print "Content-Type: application/json\n\n"; print $d->Terse(1)->Pair(':')->Useqq(1)->Deepcopy(1)->Dump; } sub pymodver { return if(isAllowedArg($FORM{'extension'}) == 0); my @params = ( '/usr/bin/selectorctl', '--interpreter=python', '--list-extensions-version', $FORM{'extension'}, '--json', ); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); chomp $rv; my $data; $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK') { $data = {status=>'OK', data=>$rv->{data}}; } else { $data = $rv; } my $d = Data::Dumper->new([$data]); print "Content-Type: application/json\n\n"; print $d->Terse(1)->Pair(':')->Useqq(1)->Deepcopy(1)->Dump; } sub rbmodver { return if(isAllowedArg($FORM{'extension'}) == 0); my @params = ( '/usr/bin/selectorctl', '--interpreter=ruby', '--list-extensions-version', $FORM{'extension'}, '--json', ); my $rv = Cpanel::SafeRun::Errors::saferunallerrors(@params); chomp $rv; my $data; $rv = Cpanel::JSON::Load($rv); if ($rv->{status} eq 'OK') { $data = {status=>'OK', data=>$rv->{data}}; } else { $data = $rv; } my $d = Data::Dumper->new([$data]); print "Content-Type: application/json\n\n"; print $d->Terse(1)->Pair(':')->Useqq(1)->Deepcopy(1)->Dump; } sub get_user_domain { my $path = shift; my $user = getpwuid($<); my $full_path = qq#$path/users/$user#; my $dns; return unless -e $full_path; open my $f, '<', $full_path or return; while (my $line = <$f>) { if ($line =~ /^DNS\s?=\s?(\S*)/) { $dns = $1; last; } } close $f; return $dns; } sub isAllowedText() { my $text = shift; my $status = 1; # Allowlist [\w./-]; \A/\z anchors so a trailing newline cannot slip past $. # Also reject: # * any '..' path segment (traversal) # * any segment starting with '-' (argv flag injection into selectorctl, # which receives $text as a separate argv element in the callers below). if($text !~ /\A[\w.\/-]+\z/ or $text =~ /(?:\A|\/)\.\.(?:\/|\z)/ or $text =~ /(?:\A|\/)-/) { print "Content-Type: application/json\n\n"; print Cpanel::JSON::Dump( {status => 'error', message => 'String ' . $text . ' contains not allowed symbols'} ); $status = 0; } return $status; } # Minimal anti-flag-injection check for argv values that are not path-shaped # (e.g. interpreter name, version, domain, extension name). Rejects empty # strings and any value starting with '-' so a request parameter cannot # smuggle a flag like '--user=victim' into selectorctl's argument list. sub isAllowedArg { my $text = shift; if(!defined($text) || $text eq '' || $text =~ /\A-/) { print "Content-Type: application/json\n\n"; print Cpanel::JSON::Dump( {status => 'error', message => 'Invalid argument: ' . (defined($text) ? $text : '<undef>')} ); return 0; } return 1; } # return json data end finalization http session sub push_json { my $json_output_rel = shift; print "Content-Type: application/json\n\n"; print Cpanel::JSON::Dump($json_output_rel); exit; };
| ver. 1.4 |
Github
|
.
| PHP 8.1.34 | Generation time: 0.05 |
proxy
|
phpinfo
|
Settings