├── .gitignore
├── .perltidyrc
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── pkg
├── Cpanel
│ └── Security
│ │ ├── Advisor.pm
│ │ └── Advisor
│ │ ├── Assessors.pm
│ │ └── Assessors
│ │ ├── Apache.pm
│ │ ├── Brute.pm
│ │ ├── ClamAV.pm
│ │ ├── Imunify360.pm
│ │ ├── Iptables.pm
│ │ ├── Jail.pm
│ │ ├── Kernel.pm
│ │ ├── Mysql.pm
│ │ ├── PHP.pm
│ │ ├── Passwords.pm
│ │ ├── Permissions.pm
│ │ ├── Processes.pm
│ │ ├── SSH.pm
│ │ ├── Scgiwrap.pm
│ │ ├── Spam.pm
│ │ ├── Symlinks.pm
│ │ ├── Tomcat.pm
│ │ ├── Trojans.pm
│ │ ├── Usernames.pm
│ │ └── _Self.pm
├── appconfig
│ └── securityadvisor.conf
├── bin
│ └── upgrade
├── cgi
│ └── addon_securityadvisor.cgi
├── icon
│ └── ico-security-advisor.png
├── install
├── install-dist
├── templates
│ └── main.tmpl
└── uninstall
└── t
├── lib
├── Cpanel
│ └── Security
│ │ └── Advisor
│ │ └── Assessors
│ │ ├── MockAssessor.pm
│ │ ├── MockLoadFail.pm
│ │ └── MockNewFail.pm
└── Test
│ ├── Assessor.pm
│ └── Mock
│ └── SecurityAdvisor.pm
├── pkg-Cpanel-Security-Advisor-Assessors-Apache.t
├── pkg-Cpanel-Security-Advisor-Assessors-Imunify360.t
├── pkg-Cpanel-Security-Advisor-Assessors-Kernel.t
├── pkg-Cpanel-Security-Advisor-Assessors-Mysql.t
├── pkg-Cpanel-Security-Advisor-Assessors-Processes.t
├── pkg-Cpanel-Security-Advisor-Assessors-_Self.t
└── pkg-Cpanel-Security-Advisor.t
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore perltidy files
2 | *.bak
3 | *.ERR
4 | # Ignore SVN
5 | *.svn*
6 | *.pyc
7 | *.so
8 | # Ignore tags files.
9 | tags
10 | TAGS
11 | # Ignore GCC generated files
12 | *.o
13 | *.gcno
14 | *.gcda
15 | gmon.out
16 | # Ignore perl tidy backup files
17 | *.tdy
18 | # Ignore patch files
19 | *.rej
20 | *.orig
21 | # ignore vim swap files.
22 | *.swp
23 | # Ignore Apple AFP files
24 | .DS_Store
25 |
--------------------------------------------------------------------------------
/.perltidyrc:
--------------------------------------------------------------------------------
1 | -l=400
2 | -i=4
3 | -dt=4
4 | -it=4
5 | -bar
6 | -nsfs
7 | -nolq
8 | --break-at-old-comma-breakpoints
9 | --format-skipping
10 | --format-skipping-begin='#\s*tidyoff'
11 | --format-skipping-end='#\s*tidyon'
12 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | Please contribute using [GitHub Flow](https://guides.github.com/introduction/flow/). Create a branch, add commits, and [open a pull request](https://github.com/cpanelinc/addon_securityadvisor/compare/).
4 |
5 | # Contributing Developer Notes
6 |
7 | ## Assessor Modules
8 |
9 | The Security Advisor encapsulates all of its checks inside of the Perl modules that are located in the [Assessors directory](pkg/Cpanel/Security/Advisor/Assessors).
10 |
11 | Each module is a subclass of [Cpanel::Security::Advisor::Assessors](pkg/Cpanel/Security/Advisor/Assessors.pm), as such each module has access to the methods that can add to the notifications.
12 |
13 | ### Adding New Checks
14 |
15 | Please evaluate the list of [Assessors](pkg/Cpanel/Security/Advisor/Assessors) to see if the check you wish to add fits in an existing module.
16 |
17 | For existing modules, it is usually preferable to your new check as an isolated subroutine that can be called from the `generate_advice` subroutine. This is the "main" subroutine that is called to drive all the checks.
18 |
19 | If you must create a new module, please study the structure of existing modules. Be prepared to justify why a new module is required and why an existing module is no place for the new check.
20 |
21 | ## Advice on Advice
22 |
23 | ### All Advisory Messages Require Unique Static Keys
24 |
25 | There are four types of advice defined in `pkg/Cpanel/Security/Advisor/Assessors.pm`, they are:
26 |
27 | * `$Cpanel::Security::Advisor::Assessors::ADVISE_GOOD`
28 | * `$Cpanel::Security::Advisor::Assessors::ADVISE_INFO`
29 | * `$Cpanel::Security::Advisor::Assessors::ADVISE_WARN`
30 | * `$Cpanel::Security::Advisor::Assessors::ADVISE_BAD`
31 |
32 | They are pretty self explanatory, but it is preferred that any message type of `ADVISE_WARN` or `ADVISE_BAD` also include a `suggestion` for further explanation of the message and what actions may be taken.
33 |
34 | Example,
35 |
36 | ```perl
37 | $security_advisor_obj->add_advice(
38 | {
39 | 'key' => 'EntropyChat_is_running', #<-- required, globally unique static message key
40 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
41 | 'text' => ['Entropy Chat is running.'],
42 | 'suggestion' => [
43 | 'Turn off Entropy Chat in the “[output,url,_1,Service Manager,_2,_3]” page.',
44 | $self->base_path('scripts/srvmng'),
45 | 'target',
46 | '_blank'
47 | ],
48 | }
49 | );
50 | ```
51 |
52 | ### All Advisory Messages Require Unique Static Keys
53 |
54 | The general method available to add an Advisory Message is called add_advice. You must specify the advice text and a globally unique static key that is shared by no other messages.
55 |
56 | Pull requests that contain new messages without this globally unique static key will be rejected.
57 |
58 | The convention being used for determining the static key is as follows. The key must begin with name of the assessor. For example, all keys in `Cpanel::Security::Advisor::Assessors::SSH` begin with *SSH*. What follows is a terse, but meaningful phrase using underscores rather than spaces.
59 |
60 | Here are some additional examples of helpful keys that are currently in use:
61 |
62 | * `Apache_vhosts_not_segmented`
63 | * `Brute_protection_enabled`
64 | * `Kernel_kernelcare_update_available`
65 |
66 | Once a pull request is merged into master, the static keys should never change. The keys are used to track message history, and changing them will result in the same issue that they are meant to solve; i.e., duplicate notifications for previously reported alerts.
67 |
68 | ### Preventing notification of some advice
69 |
70 | It may be desirable, in some cases, to have a piece of advice available interactively from WHM, but not send automated notifications about it. If so, you can direct this behavior in the advice creation:
71 |
72 | ```perl
73 | $security_advisor_obj->add_advice(
74 | {
75 | 'key' => 'EntropyChat_is_running',
76 | 'block_notify' => 1, # <--- any defined, nonzero value will do!
77 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
78 | 'text' => ['Entropy Chat is running.'],
79 | 'suggestion' => [
80 | 'Turn off Entropy Chat in the “[output,url,_1,Service Manager,_2,_3]” page.',
81 | $self->base_path('scripts/srvmng'),
82 | 'target',
83 | '_blank'
84 | ],
85 | }
86 | );
87 | ```
88 |
89 | It won't change the behavior when Security Advisor is used in WHM, but the automated notification script will skip this advice.
90 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2013, cPanel, Inc.
2 | # All rights reserved.
3 | # http://cpanel.net
4 | #
5 | # Redistribution and use in source and binary forms, with or without
6 | # modification, are permitted provided that the following conditions are met:
7 | # * Redistributions of source code must retain the above copyright
8 | # notice, this list of conditions and the following disclaimer.
9 | # * Redistributions in binary form must reproduce the above copyright
10 | # notice, this list of conditions and the following disclaimer in the
11 | # documentation and/or other materials provided with the distribution.
12 | # * Neither the name of the owner nor the names of its contributors may
13 | # be used to endorse or promote products derived from this software
14 | # without specific prior written permission.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | # DISCLAIMED. IN NO EVENT SHALL cPanel, L.L.C. BE LIABLE FOR ANY
20 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | IS_SANDBOX=$(shell perl -e 'print "yes" if -e q{/var/cpanel/dev_sandbox}')
2 | PKG=$(shell pwd)/pkg
3 |
4 | all:
5 | @echo 'This Makefile is only used during the build process.'
6 | @echo 'Please update to cPanel & WHM 11.40 or use the pkg/install script.'
7 |
8 | define build_rules
9 | @[ $(IS_SANDBOX) = yes ] || exit 1
10 | rm -fr $(1)/Cpanel/Security/Advisor.pm $(1)/Cpanel/Security/Advisor $(1)/whostmgr/templates/securityadvisor
11 | mkdir -p $(1)/Cpanel/Security/Advisor/Assessors $(1)/whostmgr/docroot/templates/securityadvisor
12 | for i in $(PKG)/Cpanel/Security/Advisor.pm \
13 | $(PKG)/Cpanel/Security/Advisor/Assessors.pm $(PKG)/Cpanel/Security/Advisor/Assessors/*.pm; do \
14 | stripped=`echo $$i | sed -e 's,^$(PKG),,'`; \
15 | rm -fr $(1)/$$stripped; $(2) $$i $(1)/$$stripped; \
16 | done
17 | for i in $(PKG)/templates/*.tmpl; do \
18 | stripped=`basename $$i`; \
19 | cp -f $$i $(1)/whostmgr/docroot/templates/securityadvisor/$$stripped; \
20 | perl -i -pe 's{/addon_plugins/}{}g' $(1)/whostmgr/docroot/templates/securityadvisor/$$stripped; \
21 | done
22 | $(2) $(PKG)/icon/ico-security-advisor.png $(1)/whostmgr/docroot/themes/x/icons/
23 | mkdir -m 700 -p $(1)/whostmgr/docroot/cgi/securityadvisor
24 | $(2) $(PKG)/cgi/addon_securityadvisor.cgi $(1)/whostmgr/docroot/cgi/securityadvisor/index.cgi
25 | endef
26 |
27 | sandbox:
28 | $(call build_rules,/usr/local/cpanel,ln -sf)
29 |
30 | publish:
31 | [ -n "$(DESTDIR)" ] || exit 1
32 | $(call build_rules,$(DESTDIR)/cpanel,cp -f)
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cPanel Security Advisor README
2 |
3 | The cPanel Security Advisor analyzes the configuration of a cPanel & WHM system to make recommendations that improve system security.
4 |
5 | ## Installation
6 |
7 | Installing from the GitHub repository is only needed if you want to contribute to the development of the security advisor, or you simply want the latest changes before they are distributed with cPanel & WHM. To install from here, you need to clone the repository, then run the installer.
8 |
9 | 1. /usr/local/cpanel/3rdparty/bin/git clone https://github.com/Cpanelinc/addon_securityadvisor.git
10 | 2. cd addon_securityadvisor/pkg
11 | 3. ./install
12 |
13 | The next time cPanel & WHM updates it will over write your changes. To keep the GitHub version you need to create a *postupcp* hook that re-runs the installer at the end of the update.
14 |
15 | ## Usage
16 |
17 | The Security Advisor is found within the Security Center of WHM. It requires root privileges to access. There are two ways to find the Advisor.
18 |
19 | 1. Log into WHM with root privileges
20 | 2. Click Security Center
21 | 3. Click Security Advisor
22 |
23 | OR
24 |
25 | 1. Log into WHM with root privileges
26 | 2. Search for Security Advisor
27 | 3. Click Security Advisor
28 |
29 | Accessing the Security Advisor begins the analysis of your system.
30 |
31 | ## Contributing
32 |
33 | Contributions are welcome.
34 |
35 | Please carefully read the [Contribution Document](CONTRIBUTING.md) for workflow and contributing developer information.
36 |
37 | ## License
38 |
39 | See the LICENSE file in the project root directory.
40 |
--------------------------------------------------------------------------------
/pkg/Cpanel/Security/Advisor.pm:
--------------------------------------------------------------------------------
1 | package Cpanel::Security::Advisor;
2 |
3 | # Copyright (c) 2020, cPanel, L.L.C.
4 | # All rights reserved.
5 | # http://cpanel.net
6 | #
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # * Redistributions of source code must retain the above copyright
10 | # notice, this list of conditions and the following disclaimer.
11 | # * Redistributions in binary form must reproduce the above copyright
12 | # notice, this list of conditions and the following disclaimer in the
13 | # documentation and/or other materials provided with the distribution.
14 | # * Neither the name of the owner nor the names of its contributors may
15 | # be used to endorse or promote products derived from this software
16 | # without specific prior written permission.
17 | #
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | # DISCLAIMED. IN NO EVENT SHALL cPanel, L.L.C. BE LIABLE FOR ANY
22 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | =pod
30 |
31 | =encoding utf-8
32 |
33 | =head1 NAME
34 |
35 | Cpanel::Security::Advisor - cPanel Security Advisor
36 |
37 | =head1 SYNOPSYS
38 |
39 | my $comet = Cpanel::Comet::Mock->new();
40 |
41 | Cpanel::LoadModule::load_perl_module('Cpanel::Security::Advisor');
42 | my $advisor = Cpanel::Security::Advisor->new( 'comet' => $comet, 'channel' => $CHANNEL );
43 |
44 | my ( $merged, @result ) = Capture::Tiny::capture_merged(
45 | sub {
46 | $advisor->generate_advice();
47 | }
48 | );
49 |
50 | my $msgs = $comet->get_messages($CHANNEL);
51 | foreach my $msg ( @{$msgs} ) {
52 | my $msg_ref = Cpanel::AdminBin::Serializer::Load($msg);
53 |
54 | ....
55 |
56 | }
57 |
58 | =cut
59 |
60 | use strict;
61 |
62 | our $VERSION = 1.04;
63 |
64 | use Cpanel::Config::LoadCpConf ();
65 | use Cpanel::Logger ();
66 | use Cpanel::JSON ();
67 | use Cpanel::Locale ();
68 | use Cpanel::LoadModule ();
69 | use Cpanel::LoadModule::AllNames ();
70 |
71 | use Try::Tiny;
72 |
73 | our $ADVISE_GOOD = 1;
74 | our $ADVISE_INFO = 2;
75 | our $ADVISE_WARN = 4;
76 | our $ADVISE_BAD = 8;
77 |
78 | # provides string type given integer value
79 | sub _lookup_advise_type {
80 | my $int = shift;
81 | my $ADVISE_TYPES_LOOKUP = {
82 | $ADVISE_GOOD => q{ADVISE_GOOD},
83 | $ADVISE_INFO => q{ADVISE_INFO},
84 | $ADVISE_WARN => q{ADVISE_WARN},
85 | $ADVISE_BAD => q{ADVISE_BAD},
86 | };
87 | return ( $int and $ADVISE_TYPES_LOOKUP->{$int} ) ? $ADVISE_TYPES_LOOKUP->{$int} : undef;
88 | }
89 |
90 | # provides integer value given type string
91 | sub _lookup_advise_type_by_value {
92 | my $type = shift;
93 | my $ADVISE_VALUES_LOOKUP = {
94 | q{ADVISE_GOOD} => $ADVISE_GOOD,
95 | q{ADVISE_INFO} => $ADVISE_INFO,
96 | q{ADVISE_WARN} => $ADVISE_WARN,
97 | q{ADVISE_BAD} => $ADVISE_BAD,
98 | };
99 | return ( $type and $ADVISE_VALUES_LOOKUP->{$type} ) ? $ADVISE_VALUES_LOOKUP->{$type} : undef;
100 | }
101 |
102 | =head1 ADVISE TYPES
103 |
104 | =head2 ADVISE_GOOD
105 |
106 | =over
107 |
108 | Changes DO NOT send iContact notices
109 |
110 | =back
111 |
112 | All is well
113 |
114 | =head2 ADVISE_INFO
115 |
116 | =over
117 |
118 | Changes send iContact notices for 11.56.0.14 and below, Changes DO NOT send iContact notices for 11.56.0.15 and above
119 |
120 | =back
121 |
122 | For items that may not be be actionable soon but should know about.
123 | If there is uncertainty if the admin has control over the item or
124 | if we have less than 90% confidence that its not a false positive.
125 |
126 | =head2 ADVISE_WARN
127 |
128 | =over
129 |
130 | Changes send iContact notices
131 |
132 | =back
133 |
134 | For items that should be actioned soon. These should be
135 | 95% confidence or better that it is not a false positive.
136 |
137 | =head2 ADVISE_BAD
138 |
139 | =over
140 |
141 | Changes send iContact notices
142 |
143 | =back
144 |
145 | For items that should be actioned now. These should be 99%
146 | confidence or better that it is not a false positive.
147 |
148 | =cut
149 |
150 | sub new {
151 | my ( $class, %options ) = @_;
152 |
153 | die "No comet object provided" unless ( $options{'comet'} );
154 | die "No comet channel provided" unless ( $options{'channel'} );
155 |
156 | my @assessors;
157 |
158 | my $self = bless {
159 | 'assessors' => \@assessors,
160 | 'logger' => Cpanel::Logger->new(),
161 | 'cpconf' => scalar Cpanel::Config::LoadCpConf::loadcpconf(),
162 | '_version' => $VERSION,
163 | '_cache' => {},
164 | 'comet' => $options{'comet'},
165 | 'channel' => $options{'channel'},
166 | 'locale' => Cpanel::Locale->get_handle(),
167 | }, $class;
168 |
169 | local @INC = ( @INC, '/var/cpanel/addons/securityadvisor/perl' );
170 | my @modules = sort keys %{ Cpanel::LoadModule::AllNames::get_loadable_modules_in_namespace('Cpanel::Security::Advisor::Assessors') };
171 | foreach my $module_name (@modules) {
172 | my $object;
173 | try {
174 | Cpanel::LoadModule::load_perl_module($module_name);
175 | $object = $module_name->new($self);
176 | }
177 | catch {
178 | $self->{'logger'}->warn("Failed to load $module_name: $_");
179 | $self->_internal_message( { type => 'mod_load', state => 0, module => $module_name, message => "$_" } );
180 | };
181 | next unless $object;
182 |
183 | push @assessors, { name => $module_name, object => $object };
184 | my $runtime = ( $object->can('estimated_runtime') ? $object->estimated_runtime() : 1 );
185 | $self->_internal_message( { type => 'mod_load', state => 1, module => $module_name, runtime => $runtime } );
186 | }
187 |
188 | return $self;
189 | }
190 |
191 | sub generate_advice {
192 | my ($self) = @_;
193 |
194 | $self->_internal_message( { type => 'scan_run', state => 0 } );
195 | foreach my $mod ( sort { lc $a->{'name'} cmp lc $b->{'name'} } @{ $self->{'assessors'} } ) {
196 | my $module = $mod->{'name'};
197 | my $version_ref = "$module"->can('version');
198 | my $module_version = $version_ref ? $version_ref->() : '';
199 |
200 | $self->_internal_message( { type => 'mod_run', state => 0, module => $mod->{name}, 'version' => $module_version } );
201 | eval { $mod->{object}->generate_advice(); };
202 | $self->_internal_message( { type => 'mod_run', state => ( $@ ? -1 : 1 ), module => $mod->{name}, message => "$@", 'version' => $module_version } );
203 | }
204 | $self->_internal_message( { type => 'scan_run', state => 1 } );
205 | $self->{'comet'}->purgeclient();
206 | return;
207 | }
208 |
209 | sub _internal_message {
210 | my ( $self, $data ) = @_;
211 | $self->{'comet'}->add_message(
212 | $self->{'channel'},
213 | Cpanel::JSON::Dump(
214 | {
215 | channel => $self->{'channel'},
216 | data => $data
217 | }
218 | ),
219 | );
220 | return;
221 | }
222 |
223 | sub add_advice {
224 | my ( $self, $advice ) = @_;
225 |
226 | # Some assessor modules call methods directly on instances of this class,
227 | # and some use wrapper methods, so try to figure out the module name
228 | # regardless of which path we took.
229 | my ( $module, $function );
230 | foreach my $level ( 1, 3 ) {
231 | my $caller = ( caller($level) )[3];
232 | if ( $caller =~ /(Cpanel::Security::Advisor::Assessors::.+)::([^:]+)$/ ) {
233 | ( $module, $function ) = ( $1, $2 );
234 | last;
235 | }
236 | }
237 |
238 | $self->{'comet'}->add_message(
239 | $self->{'channel'},
240 | Cpanel::JSON::Dump(
241 | {
242 | channel => $self->{'channel'},
243 | data => {
244 | type => 'mod_advice',
245 | module => $module,
246 | function => $function,
247 | advice => $advice,
248 | }
249 | }
250 | ),
251 | );
252 | return;
253 | }
254 |
255 | 1;
256 |
--------------------------------------------------------------------------------
/pkg/Cpanel/Security/Advisor/Assessors.pm:
--------------------------------------------------------------------------------
1 | package Cpanel::Security::Advisor::Assessors;
2 |
3 | # Copyright (c) 2021, cPanel, L.L.C.
4 | # All rights reserved.
5 | # http://cpanel.net
6 | #
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # * Redistributions of source code must retain the above copyright
10 | # notice, this list of conditions and the following disclaimer.
11 | # * Redistributions in binary form must reproduce the above copyright
12 | # notice, this list of conditions and the following disclaimer in the
13 | # documentation and/or other materials provided with the distribution.
14 | # * Neither the name of the owner nor the names of its contributors may
15 | # be used to endorse or promote products derived from this software
16 | # without specific prior written permission.
17 | #
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | # DISCLAIMED. IN NO EVENT SHALL cPanel, L.L.C. BE LIABLE FOR ANY
22 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | use strict;
30 | use warnings;
31 |
32 | our $VERSION = 1.1;
33 |
34 | use Cpanel::SafeRun::Full ();
35 | use Cpanel::Version::Compare ();
36 | use Cpanel::Exception ();
37 |
38 | sub new {
39 | my ( $class, $security_advisor_obj ) = @_;
40 |
41 | my $self = bless {
42 | 'security_advisor_obj' => $security_advisor_obj,
43 | '_version' => $VERSION
44 | }, $class;
45 |
46 | return $self;
47 | }
48 |
49 | sub base_path {
50 | my ( $self, $path ) = @_;
51 |
52 | if ( $ENV{'REQUEST_URI'} =~ m{cgi/securityadvisor} ) {
53 | return '../../' . $path;
54 | }
55 | elsif ( $ENV{'REQUEST_URI'} =~ m{cgi/addons/securityadvisor} ) {
56 | return '../../../' . $path;
57 | }
58 |
59 | return '../' . $path;
60 | }
61 |
62 | sub add_advice {
63 | my ( $self, %opts ) = @_;
64 |
65 | return $self->{'security_advisor_obj'}->add_advice( {%opts} );
66 | }
67 |
68 | sub add_good_advice {
69 | my ( $self, %opts ) = @_;
70 |
71 | return $self->add_advice( %opts, 'type' => $Cpanel::Security::Advisor::ADVISE_GOOD );
72 | }
73 |
74 | sub add_info_advice {
75 | my ( $self, %opts ) = @_;
76 |
77 | return $self->add_advice( %opts, 'type' => $Cpanel::Security::Advisor::ADVISE_INFO );
78 | }
79 |
80 | sub add_warn_advice {
81 | my ( $self, %opts ) = @_;
82 |
83 | return $self->add_advice( %opts, 'type' => $Cpanel::Security::Advisor::ADVISE_WARN );
84 | }
85 |
86 | sub add_bad_advice {
87 | my ( $self, %opts ) = @_;
88 |
89 | return $self->add_advice( %opts, 'type' => $Cpanel::Security::Advisor::ADVISE_BAD );
90 | }
91 |
92 | sub get_available_rpms {
93 | my ($self) = @_;
94 |
95 | my $cache = ( $self->{'security_advisor_obj'}->{'_cache'} //= {} );
96 |
97 | return $cache->{'available_rpms'} if $cache->{'available_rpms'};
98 |
99 | require Cpanel::FindBin;
100 | my $output = Cpanel::SafeRun::Full::run(
101 | 'program' => Cpanel::FindBin::findbin('yum'),
102 | 'args' => [qw/-d 0 list all/],
103 | 'timeout' => 90,
104 | );
105 |
106 | if ( $output->{'status'} ) {
107 | $cache->{'available_rpms'} = {
108 | map { m{\A(\S+)\.[^.\s]+\s+(\S+)} ? ( $1 => $2 ) : () }
109 | split( m/\n/, $output->{'stdout'} )
110 | };
111 | }
112 |
113 | $cache->{'timed_out'} = 1 if $output->{'timeout'};
114 | $cache->{'died'} = 1 if $output->{'exit_value'} || $output->{'died_from_signal'};
115 |
116 | return $cache->{'available_rpms'};
117 | }
118 |
119 | sub get_installed_rpms {
120 | my ($self) = @_;
121 |
122 | my $cache = ( $self->{'security_advisor_obj'}->{'_cache'} //= {} );
123 |
124 | return $cache->{'installed_rpms'} if $cache->{'installed_rpms'};
125 |
126 | require Cpanel::FindBin;
127 | my $output = Cpanel::SafeRun::Full::run(
128 | 'program' => Cpanel::FindBin::findbin('rpm'),
129 | 'args' => [ '-qa', '--queryformat', '%{NAME} %{VERSION}-%{RELEASE}\n' ],
130 | 'timeout' => 30,
131 | );
132 |
133 | if ( $output->{'status'} ) {
134 | my %installed;
135 | for my $line ( split( "\n", $output->{'stdout'} ) ) {
136 | chomp $line;
137 | my ( $rpm, $version ) = split( qr/\s+/, $line, 2 );
138 | if ( $installed{$rpm} ) {
139 | my ( $this_version, $this_release ) = split( m/-/, $version, 2 );
140 | my ( $installed_version, $installed_release ) = split( m/-/, $installed{$rpm}, 2 );
141 |
142 | next if ( Cpanel::Version::Compare::compare( $installed_version, '>', $this_version ) || ( $this_version eq $installed_version && Cpanel::Version::Compare::compare( $installed_release, '>', $this_release ) ) );
143 | }
144 | $installed{$rpm} = $version;
145 | }
146 | $cache->{'installed_rpms'} = \%installed;
147 | }
148 |
149 | $cache->{'timed_out'} = 1 if $output->{'timeout'};
150 | $cache->{'died'} = 1 if $output->{'exit_value'} || $output->{'died_from_signal'};
151 |
152 | return $cache->{'installed_rpms'};
153 | }
154 |
155 | sub get_running_kernel_type {
156 | my ($kallsyms) = Cpanel::LoadFile::loadfile('/proc/kallsyms');
157 |
158 | my $redhat_release = Cpanel::LoadFile::loadfile('/etc/redhat-release');
159 | my $kernel_type =
160 | ( ( $kallsyms =~ /\[(kmod)?lve\]/ ) && ( $redhat_release =~ /CloudLinux/ ) ) ? 'cloudlinux'
161 | : ( $kallsyms =~ /grsec/ ) ? 'grsec'
162 | : ( -e '/etc/redhat-release' ) ? 'other'
163 | : '';
164 | return $kernel_type;
165 | }
166 |
167 | sub _lh {
168 | my ($self) = @_;
169 | return $self->{'security_advisor_obj'}{'locale'};
170 | }
171 |
172 | sub get_cagefsctl_bin {
173 | my ($self) = @_;
174 |
175 | my @bins = (
176 | '/usr/bin/cagefsctl',
177 | '/usr/sbin/cagefsctl',
178 | );
179 |
180 | foreach my $bin (@bins) {
181 | return $bin if ( -x $bin );
182 | }
183 |
184 | return;
185 | }
186 |
187 | sub cagefs_is_enabled {
188 | my ($self) = @_;
189 |
190 | my $cagefsctl = $self->get_cagefsctl_bin();
191 | return 0 unless $cagefsctl;
192 |
193 | require Cpanel::SafeRun::Object;
194 | my $run = Cpanel::SafeRun::Object->new(
195 | program => $cagefsctl,
196 | args => ["--cagefs-status"]
197 | );
198 |
199 | return ( $run->stdout() =~ /enabled/i ) ? 1 : 0;
200 | }
201 |
202 | 1;
203 |
--------------------------------------------------------------------------------
/pkg/Cpanel/Security/Advisor/Assessors/Brute.pm:
--------------------------------------------------------------------------------
1 | package Cpanel::Security::Advisor::Assessors::Brute;
2 |
3 | # Copyright (c) 2013, cPanel, Inc.
4 | # All rights reserved.
5 | # http://cpanel.net
6 | #
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # * Redistributions of source code must retain the above copyright
10 | # notice, this list of conditions and the following disclaimer.
11 | # * Redistributions in binary form must reproduce the above copyright
12 | # notice, this list of conditions and the following disclaimer in the
13 | # documentation and/or other materials provided with the distribution.
14 | # * Neither the name of the owner nor the names of its contributors may
15 | # be used to endorse or promote products derived from this software
16 | # without specific prior written permission.
17 | #
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | # DISCLAIMED. IN NO EVENT SHALL cPanel, L.L.C. BE LIABLE FOR ANY
22 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | use strict;
30 | use Cpanel::Config::Hulk ();
31 | use Cpanel::PsParser ();
32 | use Cpanel::LoadFile ();
33 |
34 | use base 'Cpanel::Security::Advisor::Assessors';
35 |
36 | sub generate_advice {
37 | my ($self) = @_;
38 | $self->_check_for_brute_force_protection();
39 |
40 | return 1;
41 | }
42 |
43 | sub _check_for_brute_force_protection {
44 | my ($self) = @_;
45 |
46 | my $cphulk_enabled = Cpanel::Config::Hulk::is_enabled();
47 |
48 | my $security_advisor_obj = $self->{'security_advisor_obj'};
49 |
50 | if ($cphulk_enabled) {
51 | $security_advisor_obj->add_advice(
52 | {
53 | 'key' => 'Brute_protection_enabled',
54 | 'type' => $Cpanel::Security::Advisor::ADVISE_GOOD,
55 | 'text' => $self->_lh->maketext('cPHulk Brute Force Protection is enabled.'),
56 | }
57 | );
58 |
59 | }
60 | elsif ( -x "/usr/sbin/csf" ) {
61 | if ( -e "/etc/csf/csf.disable" ) {
62 | if ( -e "/usr/local/cpanel/whostmgr/docroot/cgi/configserver/csf" ) {
63 | $security_advisor_obj->add_advice(
64 | {
65 | 'key' => 'Brute_csf_installed_but_disabled_1',
66 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
67 | 'text' => $self->_lh->maketext('CSF is installed, but appears to be disabled.'),
68 | 'suggestion' => $self->_lh->maketext(
69 | 'Click “Firewall Enable“ in the “[output,url,_1,ConfigServer Security & Firewall,_2,_3]” area. Alternately, run “csf -e“ from the command line.',
70 | $self->base_path('cgi/configserver/csf.cgi'),
71 | 'target',
72 | '_blank'
73 | ),
74 | }
75 | );
76 | }
77 | else {
78 | $security_advisor_obj->add_advice(
79 | {
80 | 'key' => 'Brute_csf_installed_but_disabled_2',
81 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
82 | 'text' => $self->_lh->maketext('CSF is installed, but appears to be disabled.'),
83 | 'suggestion' => $self->_lh->maketext('Run “csf -e“ from the command line.'),
84 | }
85 | );
86 | }
87 | }
88 | elsif ( check_lfd_running() ) {
89 | $security_advisor_obj->add_advice(
90 | {
91 | 'key' => 'Brute_csf_installed_lfd_running',
92 | 'type' => $Cpanel::Security::Advisor::ADVISE_GOOD,
93 | 'text' => $self->_lh->maketext('CSF is installed, and LFD is running.'),
94 | }
95 | );
96 | }
97 | else {
98 | if ( -e "/usr/local/cpanel/whostmgr/docroot/cgi/configserver/csf" ) {
99 | $security_advisor_obj->add_advice(
100 | {
101 | 'key' => 'Brute_csf_installed_lfd_not_running_1',
102 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
103 | 'text' => $self->_lh->maketext('CSF is installed, but LFD is not running.'),
104 | 'suggestion' => $self->_lh->maketext(
105 | 'Click “lfd Restart“ in the “[output,url,_1,ConfigServer Security & Firewall,_2,_3]” area. Alternately, run “csf --lfd restart“ from the command line.',
106 | $self->base_path('cgi/configserver/csf.cgi'),
107 | 'target',
108 | '_blank'
109 | ),
110 | }
111 | );
112 | }
113 | else {
114 | $security_advisor_obj->add_advice(
115 | {
116 | 'key' => 'Brute_csf_installed_lfd_not_running_2',
117 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
118 | 'text' => $self->_lh->maketext('CSF is installed, but LFD is not running.'),
119 | 'suggestion' => $self->_lh->maketext('Run “csf --lfd restart“ from the command line.'),
120 | }
121 | );
122 | }
123 | }
124 | }
125 | else {
126 | $security_advisor_obj->add_advice(
127 | {
128 | 'key' => 'Brute_force_protection_not_enabled',
129 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
130 | 'text' => $self->_lh->maketext('No brute force protection detected'),
131 | 'suggestion' => $self->_lh->maketext(
132 | 'Enable cPHulk Brute Force Protection in the “[output,url,_1,cPHulk Brute Force Protection,_2,_3]” area.',
133 | $self->base_path('scripts7/cphulk/config'),
134 | 'target',
135 | '_blank'
136 |
137 | ),
138 | }
139 | );
140 | }
141 |
142 | return 1;
143 | }
144 |
145 | sub check_lfd_running {
146 | my $v_pid = Cpanel::LoadFile::load_if_exists("/var/run/lfd.pid");
147 | if ( $v_pid && $v_pid =~ m/^[0-9]+$/ ) {
148 | chomp($v_pid);
149 | my $parsed_ps = Cpanel::PsParser::fast_parse_ps( 'want_pid' => $v_pid );
150 | if ( $parsed_ps && $parsed_ps->[0]->{'command'} =~ m{^lfd\b} ) {
151 | return 1;
152 | }
153 | }
154 | return 0;
155 | }
156 |
157 | 1;
158 |
--------------------------------------------------------------------------------
/pkg/Cpanel/Security/Advisor/Assessors/ClamAV.pm:
--------------------------------------------------------------------------------
1 | package Cpanel::Security::Advisor::Assessors::ClamAV;
2 |
3 | # Copyright (c) 2013, cPanel, Inc.
4 | # All rights reserved.
5 | # http://cpanel.net
6 | #
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # * Redistributions of source code must retain the above copyright
10 | # notice, this list of conditions and the following disclaimer.
11 | # * Redistributions in binary form must reproduce the above copyright
12 | # notice, this list of conditions and the following disclaimer in the
13 | # documentation and/or other materials provided with the distribution.
14 | # * Neither the name of the owner nor the names of its contributors may
15 | # be used to endorse or promote products derived from this software
16 | # without specific prior written permission.
17 | #
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | # DISCLAIMED. IN NO EVENT SHALL cPanel, L.L.C. BE LIABLE FOR ANY
22 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | use strict;
30 | use warnings;
31 | use Cpanel::FindBin ();
32 | use Cpanel::SafeRun::Errors ();
33 | use Cpanel::Version ();
34 |
35 | use base 'Cpanel::Security::Advisor::Assessors';
36 |
37 | sub generate_advice {
38 | my ($self) = @_;
39 |
40 | my $cpanel_version = Cpanel::Version::getversionnumber();
41 |
42 | # Only show the clamav advice if running version 86 and lower.
43 | # Otherwise we will recommend ImunifyAV in the Imunify360 module.
44 | if ( Cpanel::Version::compare( $cpanel_version, '<=', '11.86' ) ) {
45 | return 0 if $self->_check_clamav();
46 | }
47 | return 1;
48 | }
49 |
50 | sub _check_clamav {
51 | my ($self) = @_;
52 |
53 | my $security_advisor_obj = $self->{'security_advisor_obj'};
54 |
55 | my $install_clamav = 'scripts2/manage_plugins';
56 |
57 | $self->_find_clamav();
58 |
59 | if ( !$self->{clamav}{clamscan}{bin} && !$self->{clamav}{freshclam}{bin} ) {
60 | $security_advisor_obj->add_advice(
61 | {
62 | 'key' => 'ClamAV_not_installed',
63 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
64 | 'text' => $self->_lh->maketext('ClamAV is not installed.'),
65 | 'suggestion' => $self->_lh->maketext(
66 | 'Install ClamAV within "[output,url,_1,Manage Plugins,_2,_3]".',
67 | $self->base_path($install_clamav), 'target', '_blank',
68 | ),
69 | }
70 | );
71 | }
72 | elsif ( !$self->{clamav}{clamscan}{bin} ) {
73 | $security_advisor_obj->add_advice(
74 | {
75 | 'key' => 'ClamAV_binary_not_installed',
76 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
77 | 'text' => $self->_lh->maketext('ClamAV clamscan binary is not installed.'),
78 | 'suggestion' => $self->_lh->maketext(
79 | 'Install ClamAV within "[output,url,_1,Manage Plugins,_2,_3]".',
80 | $self->base_path($install_clamav), 'target', '_blank',
81 | ),
82 | }
83 | );
84 | }
85 | elsif ( !$self->{clamav}{freshclam}{bin} ) {
86 | $security_advisor_obj->add_advice(
87 | {
88 | 'key' => 'ClamAV_freshclam_not_installed',
89 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
90 | 'text' => $self->_lh->maketext('ClamAV freshclam binary is not installed.'),
91 | 'suggestion' => $self->_lh->maketext(
92 | 'Install ClamAV within "[output,url,_1,Manage Plugins,_2,_3]".',
93 | $self->base_path($install_clamav), 'target', '_blank',
94 | ),
95 | }
96 | );
97 | }
98 | else {
99 | $self->_get_clam_version();
100 |
101 | my @bad_clams;
102 |
103 | push( @bad_clams, 'clamscan' ) if !defined $self->{clamav}{clamscan}{version};
104 | push( @bad_clams, 'freshclam' ) if !defined $self->{clamav}{freshclam}{version};
105 |
106 | if (@bad_clams) {
107 |
108 | $security_advisor_obj->add_advice(
109 | {
110 | 'key' => 'ClamAV_failed_to_get_version',
111 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
112 | 'text' => $self->_lh->maketext( 'Failed to get version for [list_and,_1].', \@bad_clams ),
113 | 'suggestion' => $self->_lh->maketext(
114 | 'clamscan version: [_1]
freshclam version: [_2]
Reinstall ClamAV within "[output,url,_3,Manage Plugins,_4,_5]".', $self->{clamav}{clamscan}{version_str}, $self->{clamav}{freshclam}{version_str}, $self->base_path($install_clamav), 'target', '_blank',
115 | ),
116 | }
117 | );
118 |
119 | return $self;
120 | }
121 |
122 | if ( $self->{clamav}{clamscan}{version} ne $self->{clamav}{freshclam}{version} ) {
123 | $security_advisor_obj->add_advice(
124 | {
125 | 'key' => 'ClamAV_freshclam_and_clamscan_binaries_different_versions',
126 | 'type' => $Cpanel::Security::Advisor::ADVISE_WARN,
127 | 'text' => $self->_lh->maketext('ClamAV freshclam and clamscan binaries are different versions.'),
128 | 'suggestion' => $self->_lh->maketext(
129 | 'clamscan version: [_1]
freshclam version: [_2]
Reinstall ClamAV within "[output,url,_3,Manage Plugins,_4,_5]".', $self->{clamav}{clamscan}{version_str}, $self->{clamav}{freshclam}{version_str}, $self->base_path($install_clamav), 'target', '_blank',
130 | ),
131 | }
132 | );
133 | }
134 | }
135 | return $self;
136 | }
137 |
138 | sub _find_clamav {
139 | my ($self) = @_;
140 |
141 | my @paths = qw{ /usr/local/cpanel/3rdparty/bin /usr/bin /usr/local/bin /bin /sbin /usr/sbin /usr/local/sbin };
142 |
143 | $self->{clamav}{clamscan}{bin} = Cpanel::FindBin::findbin( 'clamscan', 'path' => @paths );
144 | $self->{clamav}{freshclam}{bin} = Cpanel::FindBin::findbin( 'freshclam', 'path' => @paths );
145 |
146 | return $self;
147 | }
148 |
149 | sub _get_clam_version {
150 | my ($self) = @_;
151 |
152 | foreach my $clam ( 'clamscan', 'freshclam' ) {
153 | chomp( my $version = Cpanel::SafeRun::Errors::saferunallerrors( $self->{clamav}{$clam}{bin}, '-V' ) );
154 | $self->{clamav}{$clam}{version_str} = $version || 'Failed to obtain version!';
155 | if ( $version =~ /^ClamAV (\d+\.\d+\.\d+)/m ) {
156 | $self->{clamav}{$clam}{version} = $1;
157 | }
158 | }
159 |
160 | return $self;
161 | }
162 |
163 | 1;
164 |
--------------------------------------------------------------------------------
/pkg/Cpanel/Security/Advisor/Assessors/Iptables.pm:
--------------------------------------------------------------------------------
1 | package Cpanel::Security::Advisor::Assessors::Iptables;
2 |
3 | # Copyright (c) 2013, cPanel, Inc.
4 | # All rights reserved.
5 | # http://cpanel.net
6 | #
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # * Redistributions of source code must retain the above copyright
10 | # notice, this list of conditions and the following disclaimer.
11 | # * Redistributions in binary form must reproduce the above copyright
12 | # notice, this list of conditions and the following disclaimer in the
13 | # documentation and/or other materials provided with the distribution.
14 | # * Neither the name of the owner nor the names of its contributors may
15 | # be used to endorse or promote products derived from this software
16 | # without specific prior written permission.
17 | #
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | # DISCLAIMED. IN NO EVENT SHALL cPanel, L.L.C. BE LIABLE FOR ANY
22 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | use strict;
30 | use base 'Cpanel::Security::Advisor::Assessors';
31 | use Cpanel::SafeRun::Simple;
32 |
33 | sub generate_advice {
34 | my ($self) = @_;
35 | $self->_is_iptables_active();
36 |
37 | return 1;
38 | }
39 |
40 | sub _is_iptables_active {
41 |
42 | my ($self) = @_;
43 |
44 | my $security_advisor_obj = $self->{'security_advisor_obj'};
45 |
46 | if ( -x '/etc/init.d/iptables' ) {
47 | my $status_check = `/etc/init.d/iptables status`;
48 |
49 | # need a better way to check this
50 | if ( $status_check =~ m/not running/i ) {
51 | $security_advisor_obj->add_advice(
52 | {
53 | 'key' => 'Iptables_firewall_not_running',
54 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
55 | 'text' => $self->_lh->maketext('Firewall is not running'),
56 | 'suggestion' => $self->_lh->maketext('This might be a simple matter of executing "/etc/init.d/iptables start"'),
57 | },
58 | );
59 | }
60 | }
61 |
62 | return 1;
63 | }
64 |
65 | 1;
66 |
--------------------------------------------------------------------------------
/pkg/Cpanel/Security/Advisor/Assessors/Jail.pm:
--------------------------------------------------------------------------------
1 | package Cpanel::Security::Advisor::Assessors::Jail;
2 |
3 | # Copyright (c) 2021, cPanel, L.L.C.
4 | # All rights reserved.
5 | # http://cpanel.net
6 | #
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # * Redistributions of source code must retain the above copyright
10 | # notice, this list of conditions and the following disclaimer.
11 | # * Redistributions in binary form must reproduce the above copyright
12 | # notice, this list of conditions and the following disclaimer in the
13 | # documentation and/or other materials provided with the distribution.
14 | # * Neither the name of the owner nor the names of its contributors may
15 | # be used to endorse or promote products derived from this software
16 | # without specific prior written permission.
17 | #
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | # DISCLAIMED. IN NO EVENT SHALL cPanel, L.L.C. BE LIABLE FOR ANY
22 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | use strict;
30 | use Cpanel::Config::LoadCpConf ();
31 | use Cpanel::PwCache ();
32 | use Cpanel::Config::Users ();
33 | use Cpanel::Version ();
34 |
35 | use base 'Cpanel::Security::Advisor::Assessors';
36 |
37 | sub generate_advice {
38 | my ($self) = @_;
39 | $self->_check_for_unjailed_users();
40 |
41 | return 1;
42 | }
43 |
44 | sub _check_for_unjailed_users {
45 | my ($self) = @_;
46 |
47 | my $security_advisor_obj = $self->{'security_advisor_obj'};
48 |
49 | if ( !$self->cagefs_is_enabled() ) {
50 | if ( -e '/var/cpanel/conf/jail/flags/mount_usr_bin_suid' ) {
51 | $security_advisor_obj->add_advice(
52 | {
53 | 'key' => 'Jail_mounted_user_bin_suid',
54 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
55 | 'text' => $self->_lh->maketext('Jailshell is mounting /usr/bin suid, which allows escaping the jail via crontab.'),
56 | 'suggestion' => $self->_lh->maketext(
57 | 'Disable “Jailed /usr/bin mounted suid" in the “[output,url,_1,Tweak Settings,_2,_3]” area',
58 | $self->base_path('scripts2/tweaksettings?find=jailmountusrbinsuid'),
59 | 'target',
60 | '_blank'
61 | ),
62 | }
63 | );
64 | }
65 |
66 | Cpanel::PwCache::init_passwdless_pwcache();
67 | my %cpusers = map { $_ => undef } Cpanel::Config::Users::getcpusers();
68 | my %wheel_users_hash = map { $_ => 1 } split( ' ', ( getgrnam('wheel') )[3] );
69 | delete $wheel_users_hash{'root'}; # We don't care about root being in the wheel group
70 |
71 | # Versions older than 11.60 will need to use the old PwCache location
72 | my $curr_version = $Cpanel::Version::MAJORVERSION;
73 | my $pwcache_ref;
74 |
75 | if ( Cpanel::Version::compare( $curr_version, '>=', '11.60' ) ) {
76 | require Cpanel::PwCache::Build;
77 | $pwcache_ref = Cpanel::PwCache::Build::fetch_pwcache();
78 | }
79 | else {
80 | $pwcache_ref = Cpanel::PwCache::fetch_pwcache();
81 | }
82 |
83 | my @users = map { $_->[0] } grep { exists $cpusers{ $_->[0] } && $_->[8] && $_->[8] !~ m{(?:false|nologin|(?:no|jail)shell)} } @$pwcache_ref; #aka users without jail or noshell
84 | my @users_without_jail;
85 | my @wheel_users;
86 |
87 | foreach my $user (@users) {
88 | if ( $wheel_users_hash{$user} ) {
89 | push( @wheel_users, $user );
90 | }
91 | else {
92 | push( @users_without_jail, $user );
93 | }
94 | }
95 |
96 | @users_without_jail = sort @users_without_jail; # Always notify in the same order
97 | if ( scalar @users_without_jail > 100 ) {
98 | splice( @users_without_jail, 100 );
99 | push @users_without_jail, '..truncated..';
100 | }
101 |
102 | if (@wheel_users) {
103 | $security_advisor_obj->add_advice(
104 | {
105 | 'key' => 'Jail_wheel_users_exist',
106 | 'type' => $Cpanel::Security::Advisor::ADVISE_INFO,
107 | 'text' => $self->_lh->maketext('Users with wheel group access:'),
108 | 'suggestion' => $self->_lh->maketext( '[list_and,_1].', \@wheel_users ) . '
' . $self->_lh->maketext(
109 | 'Users in the “[asis,wheel]” group may run “[asis,su]”. Consider removing these users from the “[asis,wheel]” group in the “[output,url,_1,Manage Wheel Group Users,_2,_3]” area if they do not need to be in the “[asis,wheel]” group.',
110 | $self->base_path('scripts/modwheel'),
111 | 'target',
112 | '_blank'
113 | ),
114 | }
115 | );
116 | }
117 |
118 | if (@users_without_jail) {
119 | $security_advisor_obj->add_advice(
120 | {
121 | 'key' => 'Jail_users_running_outside_of_jail',
122 | 'type' => $Cpanel::Security::Advisor::ADVISE_WARN,
123 | 'text' => $self->_lh->maketext('Users running outside of the jail:'),
124 | 'suggestion' => $self->_lh->maketext( '[list_and,_1].', \@users_without_jail ) . '
' . $self->_lh->maketext(
125 | 'Change these users to jailshell or noshell in the “[output,url,_1,Manage Shell Access,_2,_3]” area.',
126 | $self->base_path('scripts2/manageshells'),
127 | 'target',
128 | '_blank'
129 |
130 | ),
131 | }
132 | );
133 | }
134 | }
135 |
136 | return 1;
137 | }
138 |
139 | 1;
140 |
--------------------------------------------------------------------------------
/pkg/Cpanel/Security/Advisor/Assessors/Mysql.pm:
--------------------------------------------------------------------------------
1 | package Cpanel::Security::Advisor::Assessors::Mysql;
2 |
3 | # Copyright (c) 2019, cPanel, L.L.C.
4 | # All rights reserved.
5 | # http://cpanel.net
6 | #
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # * Redistributions of source code must retain the above copyright
10 | # notice, this list of conditions and the following disclaimer.
11 | # * Redistributions in binary form must reproduce the above copyright
12 | # notice, this list of conditions and the following disclaimer in the
13 | # documentation and/or other materials provided with the distribution.
14 | # * Neither the name of the owner nor the names of its contributors may
15 | # be used to endorse or promote products derived from this software
16 | # without specific prior written permission.
17 | #
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | # DISCLAIMED. IN NO EVENT SHALL cPanel, L.L.C. BE LIABLE FOR ANY
22 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | use strict;
30 | use Cpanel::Hostname ();
31 | use Cpanel::IP::Loopback ();
32 | use Cpanel::IP::Parse ();
33 | use Cpanel::IPv6::Has ();
34 | use Cpanel::MysqlUtils ();
35 | use Cpanel::MysqlUtils::MyCnf::Full ();
36 | use Cpanel::SafeRun::Errors ();
37 | use Cpanel::LoadFile ();
38 | eval { local $SIG{__DIE__}; require Cpanel::MysqlUtils::Connect; };
39 |
40 | use base 'Cpanel::Security::Advisor::Assessors';
41 |
42 | sub generate_advice {
43 | my ($self) = @_;
44 |
45 | eval { Cpanel::MysqlUtils::Connect::connect(); } if $INC{'Cpanel/MysqlUtils/Connect.pm'};
46 |
47 | if ( !$self->_sqlcmd('SELECT 1;') ) {
48 | $self->add_bad_advice(
49 | 'key' => 'Mysql_can_not_connect_to_mysql',
50 | 'text' => $self->_lh->maketext('Cannot connect to MySQL server.'),
51 | 'suggestion' => $self->_lh->maketext(
52 | 'Enable the [output,url,_1,MySQL database service,_2].',
53 | $self->base_path('scripts/srvmng'),
54 | { 'target' => '_blank' },
55 | ),
56 |
57 | );
58 | return;
59 | }
60 |
61 | $self->_check_for_db_test();
62 | $self->_check_for_anonymous_users();
63 | $self->_check_for_public_bind_address();
64 |
65 | return 1;
66 | }
67 |
68 | sub _sqlcmd {
69 | my ( $self, $cmd ) = @_;
70 | return Cpanel::MysqlUtils::sqlcmd( $cmd, { quiet => 1 } );
71 | }
72 |
73 | sub _check_for_db_test {
74 |
75 | my $self = shift;
76 |
77 | my $exists = $self->_sqlcmd(qq{show databases like 'test'});
78 |
79 | if ( !$exists ) {
80 | $self->add_good_advice(
81 | 'key' => 'Mysql_test_database_does_not_exist',
82 | 'text' => $self->_lh->maketext("[asis,MySQL] test database does not exist.")
83 | );
84 | }
85 | else {
86 | $self->add_bad_advice(
87 | 'key' => 'Mysql_test_database_exists',
88 | 'text' => $self->_lh->maketext("[asis,MySQL] test database exists."),
89 | 'suggestion' => $self->_lh->maketext(
90 | 'Numerous attacks exploit the [asis,MySQL] test database. To remove it, run “[_1]”.',
91 | "mysql -e 'drop database test'"
92 | ),
93 | );
94 |
95 | }
96 |
97 | return 1;
98 | }
99 |
100 | sub _check_for_anonymous_users {
101 | my $self = shift;
102 |
103 | my $ok = 1;
104 | my $ano = $self->_sqlcmd(qq{select 1 from mysql.user where user="" limit 1});
105 | if ($ano) {
106 | $ok = 0;
107 | }
108 |
109 | for my $h ( 'localhost', Cpanel::Hostname::gethostname ) {
110 | eval {
111 | my $grant = $self->_sqlcmd(qq{SHOW GRANTS FOR ''\@'$h'});
112 | $ok = 0 if $grant;
113 | };
114 | }
115 |
116 | if ($ok) {
117 | $self->add_good_advice(
118 | 'key' => 'Mysql_no_anonymous_users',
119 | 'text' => $self->_lh->maketext("[asis,MySQL] check for anonymous users")
120 | );
121 | }
122 | else {
123 | $self->add_bad_advice(
124 | 'key' => 'Mysql_found_anonymous_users',
125 | 'text' => $self->_lh->maketext("You have some anonymous [asis,MySQL] users"),
126 | 'suggestion' => $self->_lh->maketext( 'Remove [asis,MySQL] anonymous [asis,MySQL] users: [_1]', "mysql -e \"DELETE FROM mysql.user WHERE User=''; FLUSH PRIVILEGES;\"" )
127 | );
128 | }
129 |
130 | return 1;
131 | }
132 |
133 | sub _check_for_public_bind_address {
134 | my $self = shift;
135 |
136 | my $mycnf = Cpanel::MysqlUtils::MyCnf::Full::etc_my_cnf();
137 | my $bind_address = $mycnf->{'mysqld'}->{'bind-address'};
138 | my $port = $mycnf->{'mysqld'}->{'port'} || '3306';
139 |
140 | my @deny_rules = grep { /--dport \Q$port\E/ && /-j (DROP|REJECT)/ } split /\n/, Cpanel::SafeRun::Errors::saferunnoerror( '/sbin/iptables', '--list-rules' );
141 | my @deny_rules_6 = grep { /--dport \Q$port\E/ && /-j (DROP|REJECT)/ } split /\n/, Cpanel::SafeRun::Errors::saferunnoerror( '/sbin/ip6tables', '--list-rules' );
142 |
143 | # From: http://dev.mysql.com/doc/refman/5.5/en/server-options.html
144 | # The server treats different types of addresses as follows:
145 | #
146 | # If the address is *, the server accepts TCP/IP connections on all server
147 | # host IPv6 and IPv4 interfaces if the server host supports IPv6, or accepts
148 | # TCP/IP connections on all IPv4 addresses otherwise. Use this address to
149 | # permit both IPv4 and IPv6 connections on all server interfaces. This value
150 | # is permitted (and is the default) as of MySQL 5.6.6.
151 | #
152 | # If the address is 0.0.0.0, the server accepts TCP/IP connections on all
153 | # server host IPv4 interfaces. This is the default before MySQL 5.6.6.
154 | #
155 | # If the address is ::, the server accepts TCP/IP connections on all server
156 | # host IPv4 and IPv6 interfaces.
157 | #
158 | # If the address is an IPv4-mapped address, the server accepts TCP/IP
159 | # connections for that address, in either IPv4 or IPv6 format. For example,
160 | # if the server is bound to ::ffff:127.0.0.1, clients can connect using
161 | # --host=127.0.0.1 or --host=::ffff:127.0.0.1.
162 | #
163 | # If the address is a “regular” IPv4 or IPv6 address (such as 127.0.0.1 or
164 | # ::1), the server accepts TCP/IP connections only for that IPv4 or IPv6
165 | # address.
166 |
167 | if ( defined($bind_address) ) {
168 | my $version = ( Cpanel::IP::Parse::parse($bind_address) )[0];
169 |
170 | if ( Cpanel::IP::Loopback::is_loopback($bind_address) ) {
171 | $self->add_good_advice(
172 | 'key' => 'Mysql_listening_only_to_local_address',
173 | 'text' => $self->_lh->maketext("MySQL is listening only on a local address.")
174 | );
175 | }
176 | elsif ( ( ( $version == 4 ) && @deny_rules && ( ( $bind_address =~ /ffff/i ) ? @deny_rules_6 : 1 ) ) || ( ( $version == 6 ) && @deny_rules_6 ) || ( csf_port_closed($port) ) ) {
177 | $self->add_good_advice(
178 | 'key' => 'Mysql_port_blocked_by_firewall_1',
179 | 'text' => $self->_lh->maketext("The MySQL port is blocked by the firewall, effectively allowing only local connections.")
180 | );
181 | }
182 | else {
183 | $self->add_bad_advice(
184 | 'key' => 'Mysql_listening_on_public_address',
185 | 'text' => $self->_lh->maketext( "The MySQL service is currently configured to listen on a public address: (bind-address=[_1])", $bind_address ),
186 | 'suggestion' => $self->_lh->maketext(
187 | 'Configure bind-address=127.0.0.1 in /etc/my.cnf, or close port [_1] in the server’s firewall.',
188 | $port
189 | ),
190 | );
191 | }
192 | }
193 | else {
194 | if ( ( @deny_rules && ( !Cpanel::IPv6::Has::system_has_ipv6() || @deny_rules_6 ) ) || ( csf_port_closed($port) ) ) {
195 | $self->add_good_advice(
196 | 'key' => 'Mysql_port_blocked_by_firewall_2',
197 | 'text' => $self->_lh->maketext("The MySQL port is blocked by the firewall, effectively allowing only local connections.")
198 | );
199 | }
200 | else {
201 | $self->add_bad_advice(
202 | 'key' => 'Mysql_listening_on_all_interfaces',
203 | 'text' => $self->_lh->maketext('The MySQL service is currently configured to listen on all interfaces: (bind-address=*)'),
204 | 'suggestion' => $self->_lh->maketext(
205 | 'Configure bind-address=127.0.0.1 in /etc/my.cnf, or close port [_1] in the server’s firewall.',
206 | $port
207 | ),
208 | );
209 | }
210 | }
211 |
212 | return 1;
213 | }
214 |
215 | sub config_key_contains_port {
216 | my ( $file, $key, $port ) = @_;
217 |
218 | my $csf_conf = Cpanel::LoadFile::load_if_exists($file);
219 | return if !$csf_conf;
220 |
221 | foreach my $line ( split m/\n/, $csf_conf ) {
222 | if ( $line =~ m/^\s*\Q$key\E\s*=\s*(['"])([^'"]*)\1/a ) {
223 | my $port_list = $2;
224 | foreach my $entry ( split m/,/, $port_list ) {
225 | my ( $first, $last ) = split m/:/, $entry;
226 | if ($last) {
227 | return 1 if $first <= $port && $port <= $last;
228 | }
229 | else {
230 | return 1 if $port == $first;
231 | }
232 | }
233 | }
234 | }
235 |
236 | return 0;
237 | }
238 |
239 | sub csf_port_closed {
240 | my ($port) = @_;
241 | my $contains = config_key_contains_port( '/etc/csf/csf.conf', 'TCP_IN', $port );
242 | return if !defined $contains;
243 | return !$contains;
244 | }
245 |
246 | 1;
247 |
--------------------------------------------------------------------------------
/pkg/Cpanel/Security/Advisor/Assessors/PHP.pm:
--------------------------------------------------------------------------------
1 | package Cpanel::Security::Advisor::Assessors::PHP;
2 |
3 | # cpanel - pkg/Cpanel/Security/Advisor/Assessors/PHP.pm
4 | # Copyright 2019 cPanel, L.L.C.
5 | # All rights Reserved.
6 | # copyright@cpanel.net http://cpanel.net
7 |
8 | #
9 | # Redistribution and use in source and binary forms, with or without
10 | # modification, are permitted provided that the following conditions are met:
11 | # * Redistributions of source code must retain the above copyright
12 | # notice, this list of conditions and the following disclaimer.
13 | # * Redistributions in binary form must reproduce the above copyright
14 | # notice, this list of conditions and the following disclaimer in the
15 | # documentation and/or other materials provided with the distribution.
16 | # * Neither the name of the owner nor the names of its contributors may
17 | # be used to endorse or promote products derived from this software
18 | # without specific prior written permission.
19 | #
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | # DISCLAIMED. IN NO EVENT SHALL cPanel, L.L.C. BE LIABLE FOR ANY
24 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | use strict;
32 | use warnings;
33 | use base 'Cpanel::Security::Advisor::Assessors';
34 | use Cpanel::Result ();
35 |
36 | my $php_ver_regex = '^ea-php(\d{2,3})$';
37 |
38 | sub generate_advice {
39 | my ($self) = @_;
40 | $self->_check_for_php_eol();
41 | return 1;
42 | }
43 |
44 | sub _check_for_php_eol {
45 | my $self = shift;
46 | require Cpanel::API::EA4;
47 | require Cpanel::ProgLang;
48 |
49 | my $installed_php_versions = Cpanel::ProgLang->new( type => 'php' )->get_installed_packages();
50 |
51 | my $result = Cpanel::Result->new();
52 | Cpanel::API::EA4::get_recommendations( undef, $result );
53 | my $reco_data = $result->{'data'} if $result;
54 |
55 | my @reco_keys_to_consider = ();
56 | foreach my $installed_version (@$installed_php_versions) {
57 | if ( grep { $_ eq $installed_version } keys %$reco_data ) {
58 | push @reco_keys_to_consider, $installed_version;
59 | }
60 | }
61 |
62 | my @eol_php_versions = ();
63 | my $eol_reco_data;
64 | foreach my $key ( sort @reco_keys_to_consider ) {
65 | my @recos = @{ $reco_data->{$key} };
66 | foreach my $reco (@recos) {
67 | if ( grep { $_ eq 'eol' } @{ $reco->{'filter'} } ) {
68 |
69 | # Recommendation data is same for all EOL PHP versions. Storing only one such instance
70 | # here to use later in the advice.
71 | $eol_reco_data = $reco if ( !$eol_reco_data );
72 | push @eol_php_versions, _get_readable_php_version_format($key);
73 | }
74 | }
75 | }
76 |
77 | # Return if there is no EOL PHPs.
78 | return if scalar @eol_php_versions == 0;
79 |
80 | my $security_advisor_obj = $self->{'security_advisor_obj'};
81 |
82 | $security_advisor_obj->add_advice(
83 | {
84 | 'key' => 'Php_versions_going_eol',
85 | 'type' => $Cpanel::Security::Advisor::ADVISE_BAD,
86 | 'text' => $self->_lh->maketext( '[list_and,_1] reached [output,acronym,EOL,End of Life][comment,title]', \@eol_php_versions ),
87 | 'suggestion' => _make_unordered_list( map { $_->{'text'} } @{ $eol_reco_data->{'options'} } ) . $self->_lh->maketext(
88 | 'We recommend that you use the [output,url,_1,MultiPHP Manager,_4,_5] interface to upgrade your domains to a supported version. Then, uninstall [numerate,_2,this version,these versions] in the [output,url,_3,EasyApache 4,_4,_5] interface.',
89 | $self->base_path('scripts7/multiphp-manager'),
90 | scalar @eol_php_versions,
91 | $self->base_path('scripts7/EasyApache4'),
92 | 'target',
93 | '_blank'
94 | )
95 | . ' '
96 | . $self->_lh->maketext( 'For more information, read [output,url,_1,PHP EOL Documentation,target,_blank].', 'https://www.php.net/supported-versions.php' ),
97 | }
98 | );
99 |
100 | return 1;
101 | }
102 |
103 | sub _get_readable_php_version_format {
104 | my ($php_version) = @_;
105 | my $readable_php_version;
106 | if ( $php_version =~ /$php_ver_regex/ ) {
107 | my $second_part = $1;
108 | $second_part =~ s/(\d)$/\.$1/;
109 | $readable_php_version = "PHP $second_part";
110 | }
111 | return $readable_php_version;
112 | }
113 |
114 | sub _make_unordered_list {
115 | my (@items) = @_;
116 |
117 | my $output = '