#!/usr/local/cpanel/3rdparty/bin/perl #WHMADDON:addonupdates:ConfigServer ModSec Control #ACLS:configserver ############################################################################### # Copyright (C) 2006-2025 Jonathan Michaelson # # https://github.com/waytotheweb/scripts # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, see . ############################################################################### ## no critic (RequireUseWarnings, ProhibitExplicitReturnUndef, ProhibitMixedBooleanOperators, RequireBriefOpen) use strict; use CGI::Carp qw(fatalsToBrowser); use File::Basename; use File::Path; use File::Copy; use File::Find; use Fcntl qw(:DEFAULT :flock); use IPC::Open3; use lib '/usr/local/cpanel'; require Cpanel::Form; require Cpanel::Config; require Cpanel::Version::Tiny; require Whostmgr::ACLS; require Cpanel::Rlimit; require Cpanel::Template; ############################################################################### # start main our ($images, $myv, $script, $versionfile, %FORM, $downloadserver); %FORM = Cpanel::Form::parseform(); Whostmgr::ACLS::init_acls(); if (!Whostmgr::ACLS::hasroot()) { print "Content-type: text/html\r\n\r\n"; print "You do not have access to ConfigServer ModSecurity Control.\n"; exit(); } Cpanel::Rlimit::set_rlimit_to_infinity(); $script = "cmc.cgi"; $images = "cmc"; $versionfile = "/usr/local/cpanel/whostmgr/docroot/cgi/configserver/cmc/cmcversion.txt"; local $| = 1; $downloadserver = &getdownloadserver; my $thisapp = "cmc"; my $reregister; my $modalstyle; if ($Cpanel::Version::Tiny::major_version >= 65) { if (-e "/usr/local/cpanel/whostmgr/docroot/cgi/configserver/${thisapp}/${thisapp}.conf") { sysopen (my $CONF, "/usr/local/cpanel/whostmgr/docroot/cgi/configserver/${thisapp}/${thisapp}.conf", O_RDWR | O_CREAT); flock ($CONF, LOCK_EX); my @confdata = <$CONF>; chomp @confdata; for (0..scalar(@confdata)) { if ($confdata[$_] =~ /^target=mainFrame/) { $confdata[$_] = "target=_self"; $reregister = 1; } } if ($reregister) { seek ($CONF, 0, 0); truncate ($CONF, 0); foreach (@confdata) { print $CONF "$_\n"; } &printcmd("/usr/local/cpanel/bin/register_appconfig","/usr/local/cpanel/whostmgr/docroot/cgi/configserver/${thisapp}/${thisapp}.conf"); $reregister = "

Updated application. The next time you login to WHM this will open within the native WHM main window instead of launching a separate window

\n"; } close ($CONF); } } print "Content-type: text/html\r\n\r\n"; #if ($Cpanel::Version::Tiny::major_version < 65) {$modalstyle = "style='top:120px'"} our (@files); open (my $IN, "<", $versionfile) or die $!; flock ($IN, LOCK_SH); $myv = <$IN>; close ($IN); chomp $myv; my $bootstrapcss = ""; my $jqueryjs = ""; my $bootstrapjs = ""; my $templatehtml; my $SCRIPTOUT; unless ($FORM{action} eq "help") { open ($SCRIPTOUT, '>', \$templatehtml); select $SCRIPTOUT; print < $jqueryjs $bootstrapjs EOF } else { print < $bootstrapcss $jqueryjs $bootstrapjs
EOF } print <

ConfigServer ModSecurity Control - cmc v$myv

EOF if ($reregister ne "") {print $reregister} print "

This script creates and rewrites modsec2.whitelist.conf and userdata modsec.conf files

\n"; print "

Do not use cmc if you have made manual modifications to these files as they will be removed by cmc

\n"; my $is_ea4 = 0; my $apachepath = "/usr/local/apache/conf"; my $modsecpath = "/usr/local/apache/conf"; my $apachebin = "/usr/local/apache/bin/httpd"; my $apachectl = "/usr/local/apache/bin/apachectl"; my $apachelogs = "/usr/local/apache/logs"; if (-e "/usr/local/cpanel/version" and -e "/etc/cpanel/ea4/is_ea4" and -e "/etc/cpanel/ea4/paths.conf") { $is_ea4 = 1; $apachepath = "/etc/apache2/conf.d"; $apachebin = "/usr/sbin/httpd"; $apachectl = "/usr/sbin/apachectl"; $apachelogs = "/etc/apache2/logs"; open (my $IN, "<", "/etc/cpanel/ea4/paths.conf"); flock ($IN, LOCK_SH); my @file = <$IN>; close ($IN); chomp @file; foreach my $line (@file) { if ($line =~ /^(\s|\#|$)/) {next} if ($line !~ /=/) {next} my ($name,$value) = split (/=/,$line,2); $value =~ s/^\s+//g; $value =~ s/\s+$//g; if ($name eq "dir_conf") {$apachepath = $value} if ($name eq "bin_httpd") {$apachebin = $value} if ($name eq "bin_apachectl") {$apachectl = $value} if ($name eq "dir_logs") {$apachelogs = $value} } $modsecpath = $apachepath."/modsec"; } my $httpv = "2"; my $mypid; my ($childin, $childout); $mypid = open3($childin, $childout, $childout, "$apachebin","-v"); my @version = <$childout>; waitpid ($mypid, 0); chomp @version; $version[0] =~ /Apache\/(\d+)\.(\d+)\.(\d+)/; my $mas = $1; my $maj = $2; my $min = $3; $httpv = "$mas.$maj"; my $stdpath = "$apachepath/userdata/std/2"; my $sslpath = "$apachepath/userdata/ssl/2"; my $oldstdpath; my $oldsslpath; if ($httpv eq "2.2") { $oldstdpath = $stdpath; $oldsslpath = $sslpath; $stdpath = "$apachepath/userdata/std/2_2"; $sslpath = "$apachepath/userdata/ssl/2_2"; } if ($httpv eq "2.4") { $oldstdpath = $stdpath; $oldsslpath = $sslpath; $stdpath = "$apachepath/userdata/std/2_4"; $sslpath = "$apachepath/userdata/ssl/2_4"; } my $truefile; if ($FORM{template} ne "") { my ($tfile, $tdir) = fileparse("$apachepath/$FORM{template}"); $truefile = "$tdir$tfile"; } if (($FORM{template} ne "") and ($truefile !~ m[^$apachepath/])) { print "[$FORM{template}] is not a valid file"; } elsif (($FORM{domain} ne "") and ($FORM{domain} !~ /^[a-zA-Z0-9\-\_\.]+$/)) { print "[$FORM{domain}] is not a valid domain"; } elsif (($FORM{user} ne "") and ($FORM{user} !~ /^[a-zA-Z0-9\-\_\.\@\%\+]+$/)) { print "[$FORM{user}] is not a valid user"; } elsif ($FORM{action} eq "upgrade") { print "Retrieving new cmc package...\n"; print "
";
	&printcmd("rm -Rfv /usr/src/cmc* ; cd /usr/src ; wget -q https://$downloadserver/cmc.tgz 2>&1");
	print "
"; if (! -z "/usr/src/cmc.tgz") { print "Unpacking new cmc package...\n"; print "
";
		&printcmd("cd /usr/src ; tar -xzf cmc.tgz ; cd cmc ; sh install.sh 2>&1");
		print "
"; print "Tidying up...\n"; print "
";
		&printcmd("rm -Rfv /usr/src/cmc*");
		print "
"; print "...All done.\n"; } open (my $IN, "<",$versionfile) or die $!; flock ($IN, LOCK_SH); $myv = <$IN>; close ($IN); chomp $myv; print "

\n"; } elsif ($FORM{action} eq "ms_list") { &modsec; } elsif ($FORM{action} eq "ms_config") { sysopen (my $IN, "$apachepath/$FORM{template}", O_RDWR | O_CREAT); flock ($IN, LOCK_SH); my @confdata = <$IN>; close ($IN); chomp @confdata; print "
\n"; print "\n"; print "\n"; print "
Edit $FORM{template}\n"; print "\n"; print "
\n"; print "

\n"; print "
\n"; print "

\n"; } elsif ($FORM{action} eq "savems_config") { $FORM{formdata} =~ s/\r//g; sysopen (my $OUT, "$apachepath/$FORM{template}", O_WRONLY | O_CREAT); flock ($OUT, LOCK_EX); seek ($OUT, 0, 0); truncate ($OUT, 0); if ($FORM{formdata} !~ /\n$/) {$FORM{formdata} .= "\n"} print $OUT $FORM{formdata}; close ($OUT); print "\n"; print ""; print "\n"; print "
ModSecurity save $FORM{template}
Rebuilding and restarting Apache:
"; print "
";
	&printcmd("/usr/local/cpanel/bin/build_apache_conf");
	&printcmd("$apachectl","graceful");
	print "\n..Done
"; print "
\n"; print "

\n"; } elsif ($FORM{action} eq "Modify user whitelist") { if ($FORM{user}) { my %ids; my $off = 0; if (-d "$stdpath/$FORM{user}/") { if (-e "$stdpath/$FORM{user}/modsec.conf") { open (my $FH, "<", "$stdpath/$FORM{user}/modsec.conf"); flock ($FH, LOCK_SH); my @data = <$FH>; close ($FH); chomp @data; foreach my $line (@data) { if ($line =~ /SecRuleRemoveById\s+(\d*)/) {$ids{$1} = 1} if ($line =~ /SecRuleEngine\s+Off/) {$off = 1} } } } else { mkpath("$stdpath/$FORM{user}"); } unless (-d "$sslpath/$FORM{user}") {mkpath("$sslpath/$FORM{user}")} my @domains; open (my $IN, "<","/var/cpanel/users/$FORM{user}"); flock ($IN, LOCK_SH); my @userdata = <$IN>; close ($IN); chomp @userdata; foreach my $line (@userdata) { if ($line =~ /^DNS(\d*)=(.*)$/) { my $domain = $2; $domain =~ s/\s//g; push @domains,$domain; unless (-d "$stdpath/$FORM{user}/$domain") { mkdir ("$stdpath/$FORM{user}/$domain"); } unless (-d "$sslpath/$FORM{user}/$domain") { mkdir ("$sslpath/$FORM{user}/$domain"); } } } @domains = sort @domains; if ($off) { print "\n"; print ""; print "\n"; print "
ModSecurity whitelist for $FORM{user}
Off On

You can completely disable ModSecurity for all domains owned by this user by setting this to Off and clicking the Select button:

\n"; } else { print "\n"; print ""; print "\n"; print "\n"; print "\n"; print "
ModSecurity whitelist for $FORM{user}
Off On

You can completely disable ModSecurity for all domains owned by this user by setting this to Off and clicking the Select button:

ModSecurity rule ID list:

You can add ModSecurity rule ID numbers that you want to be disabled for all domains owned by this user.

You should place one ID number per line. When you have clicked the Save whitelist for all $FORM{user} domains button: the relevant lines will be added to:

$stdpath/$FORM{user}/modsec.conf
$sslpath/$FORM{user}/modsec.conf

Then httpd.conf will be rebuilt and apache will be gracefully restarted.

Alternatively, you can disable rules on a per domain basis by selecting a domain and then clicking:

\n"; } } else { print "

No user selected

\n"; } print "


\n"; } elsif ($FORM{action} eq "onoff") { &onoff("$stdpath/$FORM{user}/modsec.conf"); &onoff("$sslpath/$FORM{user}/modsec.conf"); print "\n"; print ""; print "\n"; print "\n"; print "
ModSecurity whitelist for $FORM{user}: "; if ($FORM{choose}) { print "On"; } else { print "Off"; } print "
Rebuilding and restarting Apache:
"; print "
";
	&printcmd("/usr/local/cpanel/bin/build_apache_conf");
	&printcmd("$apachectl","graceful");
	print "\n..Done
"; print "
\n"; print "

\n"; } elsif ($FORM{action} eq "thisuser") { &ids("$stdpath/$FORM{user}/modsec.conf"); &ids("$sslpath/$FORM{user}/modsec.conf"); print "\n"; print ""; print "\n"; print "\n"; print "
ModSecurity whitelist for $FORM{user} saved"; print "
Rebuilding and restarting Apache:
"; print "
";
	&printcmd("/usr/local/cpanel/bin/build_apache_conf");
	&printcmd("$apachectl","graceful");
	print "\n..Done
"; print "
\n"; print "

\n"; } elsif ($FORM{action} eq "domain") { if ($FORM{user} and $FORM{domain}) { my %ids; my $off = 0; if (-d "$stdpath/$FORM{user}/$FORM{domain}/") { if (-e "$stdpath/$FORM{user}/$FORM{domain}/modsec.conf") { open (my $FH, "<", "$stdpath/$FORM{user}/$FORM{domain}/modsec.conf"); flock ($FH, LOCK_SH); my @data = <$FH>; close ($FH); chomp @data; foreach my $line (@data) { if ($line =~ /SecRuleRemoveById\s+(\d*)/) {$ids{$1} = 1} if ($line =~ /SecRuleEngine\s+Off/) {$off = 1} } } } if ($off) { print "\n"; print ""; print "\n"; print "\n"; print "
ModSecurity whitelist for $FORM{domain}
Off On

You can completely disable ModSecurity on this domain by setting this to Off and clicking the Select button:

\n"; } else { print "\n"; print ""; print "\n"; print "\n"; print "\n"; print "
ModSecurity whitelist for $FORM{domain}
Off On

You can completely disable ModSecurity on this domain by setting this to Off and clicking the Select button:

ModSecurity rule ID list:

You can add ModSecurity rule ID numbers that you want to be disabled for this domain.

You should place one ID number per line. When you have clicked the Save whitelist for $FORM{domain} button: the relevant lines will be added to:

$stdpath/$FORM{user}/$FORM{domain}/modsec.conf
$sslpath/$FORM{user}/$FORM{domain}/modsec.conf

Then httpd.conf will be rebuilt and apache will be gracefully restarted.

\n"; } } else { print "

No domain selected

\n"; } print "


\n"; } elsif ($FORM{action} eq "donoff") { &onoff("$stdpath/$FORM{user}/$FORM{domain}/modsec.conf"); &onoff("$sslpath/$FORM{user}/$FORM{domain}/modsec.conf"); print "\n"; print ""; print "\n"; print "\n"; print "
ModSecurity whitelist for $FORM{domain}: "; if ($FORM{choose}) { print "On"; } else { print "Off"; } print "
Rebuilding and restarting Apache:
"; print "
";
	&printcmd("/usr/local/cpanel/bin/build_apache_conf");
	&printcmd("$apachectl","graceful");
	print "\n..Done
"; print "
\n"; print "

\n"; } elsif ($FORM{action} eq "thisdomain") { &ids("$stdpath/$FORM{user}/$FORM{domain}/modsec.conf"); &ids("$sslpath/$FORM{user}/$FORM{domain}/modsec.conf"); print "\n"; print ""; print "\n"; print "\n"; print "
ModSecurity whitelist for $FORM{domain} saved"; print "
Rebuilding and restarting Apache:
"; print "
";
	&printcmd("/usr/local/cpanel/bin/build_apache_conf");
	&printcmd("$apachectl","graceful");
	print "\n..Done
"; print "
\n"; print "

\n"; } elsif ($FORM{action} eq "gonoff") { &onoff("$apachepath/modsec2.whitelist.conf"); print "\n"; print ""; print "\n"; print "
ModSecurity global whitelist: "; if ($FORM{choose}) { print "On"; } else { print "Off"; } print "
Restarting Apache:
"; print "
";
	&printcmd("$apachectl","graceful");
	print "\n..Done
"; print "
\n"; print "

\n"; } elsif ($FORM{action} eq "dironoff") { my $file = "$apachepath/modsec2.whitelist.conf"; open (my $FH, "<", $file); flock ($FH, LOCK_SH); my @data = <$FH>; close ($FH); chomp @data; my $start = 0; my $done = 0; my $directorymatch = quotemeta($FORM{directorymatch}); open (my $OUT, ">", $file); flock ($OUT, LOCK_EX); print $OUT "\n"; foreach my $line (@data) { if ($line =~ /^\s*/) {next} if ($line =~ /^\s*<\/IfModule>/) {next} if ($line =~ //) {$start = 1} if ($start and $line =~ /SecRuleEngine\s/) {next} if ($line =~ /<\/DirectoryMatch>/ and $start) { $start = 0; if ($FORM{choose}) { } else { print $OUT "\tSecRuleEngine Off\n"; } $done = 1; } print $OUT "$line\n"; } unless ($done) { print $OUT "\n"; if ($FORM{choose}) { } else { print $OUT "\tSecRuleEngine Off\n"; } print $OUT "\n"; } print $OUT "\n"; close ($OUT); print "\n"; print ""; print "\n"; print "
ModSecurity DirectoryMatch ($FORM{directorymatch}) whitelist: "; if ($FORM{choose}) { print "On"; } else { print "Off"; } print "
Restarting Apache:
"; print "
";
	&printcmd("$apachectl","graceful");
	print "\n..Done
"; print "
\n"; print "

\n"; } elsif ($FORM{action} eq "global") { &ids("$apachepath/modsec2.whitelist.conf"); print "\n"; print ""; print "\n"; print "
ModSecurity global whitelist saved"; print "
Restarting Apache:
"; print "
";
	&printcmd("$apachectl","graceful");
	print "\n..Done
"; print "
\n"; print "

\n"; } elsif ($FORM{action} eq "thisdirectorymatch") { my $file = "$apachepath/modsec2.whitelist.conf"; my @ids = split(/\n|\r/,$FORM{ids}); chomp @ids; open (my $FH, "<", $file); flock ($FH, LOCK_SH); my @data = <$FH>; close ($FH); chomp @data; my $start = 0; my $done = 0; my $directorymatch = quotemeta($FORM{directorymatch}); open (my $OUT, ">", $file); flock ($OUT, LOCK_EX); print $OUT "\n"; foreach my $line (@data) { if ($line =~ /^\s*/) {next} if ($line =~ /^\s*<\/IfModule>/) {next} if ($line =~ /<\/DirectoryMatch>/ and $start) { $start = 0; foreach my $id (@ids) { if ($id =~ /^\d+$/) {print $OUT "\tSecRuleRemoveById $id\n"} } $done = 1; } if ($start) {next} if ($line =~ //) {$start = 1} print $OUT "$line\n"; } unless ($done) { print $OUT "\n"; foreach my $id (@ids) { if ($id =~ /^\d+$/) {print $OUT "\tSecRuleRemoveById $id\n"} } print $OUT "\n"; } print $OUT "\n"; close ($OUT); print "\n"; print ""; print "\n"; print "
ModSecurity DirectoryMatch ($FORM{directorymatch}) whitelist saved"; print "
Restarting Apache:
"; print "
";
	&printcmd("$apachectl","graceful");
	print "\n..Done
"; print "
\n"; print "

\n"; } elsif ($FORM{action} eq "Remove DirectoryMatch") { my $file = "$apachepath/modsec2.whitelist.conf"; my @ids = split(/\n|\r/,$FORM{ids}); chomp @ids; open (my $FH, "<", $file); flock ($FH, LOCK_SH); my @data = <$FH>; close ($FH); chomp @data; my $start = 0; my $done = 0; my $directorymatch = quotemeta($FORM{directorymatch}); open (my $OUT, ">", $file); flock ($OUT, LOCK_EX); print $OUT "\n"; foreach my $line (@data) { if ($line =~ /^\s*/) {next} if ($line =~ /^\s*<\/IfModule>/) {next} if ($line =~ /<\/DirectoryMatch>/ and $start) {next} if ($start) {next} if ($line =~ //) { $start = 1; next; } print $OUT "$line\n"; } print $OUT "\n"; close ($OUT); print "\n"; print ""; print "\n"; print "
ModSecurity DirectoryMatch ($FORM{directorymatch}) whitelist removed"; print "
Restarting Apache:
"; print "
";
	&printcmd("$apachectl","graceful");
	print "\n..Done
"; print "
\n"; print "

\n"; } elsif ($FORM{action} eq "Modify by DirectoryMatch") { if ($FORM{directorymatch} eq "" or $FORM{directorymatch} eq "New DirectoryMatch") { print "\n"; print ""; print "\n"; print "
ModSecurity DirectoryMatch whitelist

Add a DirectoryMatch Apache directive (do not use quotes). This should be a regular expression. Examples:
^/home/someuser/public_html/ignore/me/index\\.php
^/home/someuser/public_html/ignore/path/
/wp-admin/index\\.php

\n"; } else { my %ids; my $off = 0; if (-e "$apachepath/modsec2.whitelist.conf") { open (my $FH, "<", "$apachepath/modsec2.whitelist.conf"); flock ($FH, LOCK_SH); my @data = <$FH>; close ($FH); chomp @data; my $start = 0; my $directorymatch = quotemeta($FORM{directorymatch}); foreach my $line (@data) { if ($line =~ //) {$start = 1} if ($start and $line =~ /SecRuleRemoveById\s+(\d*)/) {$ids{$1} = 1} if ($start and $line =~ /SecRuleEngine\s+Off/) {$off = 1} if ($line =~ /<\/DirectoryMatch>/) {$start = 0} } } if ($off) { print "\n"; print ""; print "\n"; print "
ModSecurity whitelist for DirectoryMatch: $FORM{directorymatch}
Off On

You can completely disable ModSecurity for this DirectoryMatch by setting this to Off and clicking the Select button:

\n"; } else { print "\n"; print ""; print "\n"; print "\n"; print "\n"; print "
ModSecurity whitelist for DirectoryMatch: $FORM{directorymatch}
Off On

You can completely disable ModSecurity for this DirectoryMatch by setting this to Off and clicking the Select button:

ModSecurity rule ID list:

You can add ModSecurity rule ID numbers that you want to be disabled for this DirectoryMatch.

You should place one ID number per line. When you have clicked the Save Whitelist button:

Then apache will be gracefully restarted.

\n"; } } print "

\n"; print "

\n"; } elsif ($FORM{action} eq "map") { print "\n"; &showmap; print "
\n"; print "

Note: Only users or domain with a modsec.conf containing cmc exceptions will be listed here

\n"; print "

\n"; } elsif ($FORM{action} eq "help") { print "\n"; print ""; print "\n"; print "
ConfigServer ModSecurity Help
"; print <This utility allows you to:
  • Disable ModSecurity rules that have unique ID numbers on a global, per cPanel user or per hosted domain level.
  • Disable ModSecurity entirely, also on a global, per cPanel user or per hosted domain level.
  • Edit files containing ModSecurity configuration settings in $apachepath
  • View the latest ModSecurity log entries

The requirements for this utility are:

  • Apache v2+
  • ModSecurity v2.5+ installed via Easyapache
  • A set of ModSecurity rules each of which uses a unique ID
  • ModSecurity logging that uses "SecAuditLogParts A...Z"

ModSecurity logs will be detected in the following order, the last found being the one that will be used. If the wrong logs are being shown the other logs should be removed:

  • $apachelogs/audit_log
  • $apachelogs/modsec_audit.log
  • $apachelogs/modsec_audit/ (used under mod_ruid2 and mpm_itk)

This utility uses concepts explained in this section of the cPanel documentation.

EOH print "

\n"; } else { my @modsecfiles; my @modsecdirfiles; my %ids; my @alt; my $off = 0; if (-e "$apachepath/modsec2.whitelist.conf") { open (my $FH, "<", "$apachepath/modsec2.whitelist.conf"); flock ($FH, LOCK_SH); my @data = <$FH>; close ($FH); chomp @data; my $start = 0; foreach my $line (@data) { if ($line =~ //) {push @alt,$1; $start = 1} if (!$start and $line =~ /SecRuleRemoveById\s+(\d*)/) {$ids{$1} = 1} if (!$start and $line =~ /SecRuleEngine\s+Off/) {$off = 1} if ($line =~ /^\s*(<\/DirectoryMatch>)/) {$start = 0} } } else { open (my $FH,">","$apachepath/modsec2.whitelist.conf"); flock ($FH, LOCK_SH); print $FH "\# ConfigServer ModSecurity whitelist file\n"; close ($FH); } sysopen (my $FH, "$modsecpath/modsec2.user.conf", O_RDWR | O_CREAT); flock ($FH, LOCK_EX); my @data = <$FH>; chomp @data; if ($is_ea4) { if (grep {$_ =~ /^\s*Include\s+$apachepath\/modsec2\.whitelist\.conf/} @data) { seek ($FH, 0, 0); truncate ($FH, 0); foreach my $line (@data) { if ($line =~ /^\s*Include\s+$apachepath\/modsec2\.whitelist\.conf/) {next} if ($line =~ /^\# ConfigServer ModSecurity whitelist file/) {next} print $FH "$line\n"; } print "

Removing modsec2.whitelist.conf in modsec2.user.conf (not needed in EA4) and gracefully restarting Apache..."; &printcmd("$apachectl","graceful"); print "Done

\n"; } } else { unless ($data[-1] =~ /^\s*Include\s+$apachepath\/modsec2\.whitelist\.conf/) { seek ($FH, 0, 0); truncate ($FH, 0); foreach my $line (@data) { if ($line =~ /^\s*Include\s+$apachepath\/modsec2\.whitelist\.conf/) {next} if ($line =~ /^\# ConfigServer ModSecurity whitelist file/) {next} print $FH "$line\n"; } print $FH "Include $apachepath/modsec2.whitelist.conf\n"; print "

Adding/Relocating modsec2.whitelist.conf in modsec2.user.conf and gracefully restarting Apache..."; &printcmd("$apachectl","graceful"); print "Done

\n"; } } close ($FH); opendir (DIR, "$apachepath/"); while (my $file = readdir (DIR)) { if ($file =~ /^(mod_sec|modsec).*\.conf$/i) { push @modsecfiles, $file; } if (-d "$apachepath/$file" and ($file =~ /^(mod_sec|modsec)/i)) { opendir (MODDIR, "$apachepath/$file"); while (my $modfile = readdir (MODDIR)) { if ($modfile =~ /^\.|\.\.$/) {next} push @modsecdirfiles, "$file/$modfile"; } closedir (MODDIR); } } closedir (DIR); @modsecfiles = sort @modsecfiles; @modsecdirfiles = sort @modsecdirfiles; my @users; my %domains; opendir (DIR, "/var/cpanel/users") or die $!; while (my $user = readdir (DIR)) { if ($user =~ /^\./) {next} my (undef,undef,undef,undef,undef,undef,undef,$homedir,undef,undef) = getpwnam($user); $homedir =~ /(.*)/; $homedir = $1; if ($homedir eq "") {next} if (not -d "$homedir") {next} open (my $IN, "<","/var/cpanel/users/$user"); flock ($IN, LOCK_SH); my @userdata = <$IN>; close ($IN); chomp @userdata; my $domain; foreach my $line (@userdata) { if ($line =~ /^DNS=(.*)/) { $domains{$user} = $1; last; } } push (@users, $user); } closedir (DIR); @users = sort @users; print "\n"; print "\n"; if ($off) { print "\n"; } else { print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; } print "
ConfigServer ModSecurity Control Help
Off On

You can completely disable ModSecurity on the server by setting this to Off and clicking the Select button:

Off On

You can completely disable ModSecurity on the server by setting this to Off and clicking the Select button:

ModSecurity rule ID list:

You can add ModSecurity rule ID numbers that you want to be globally disabled.

\n"; print "

You should place one ID number per line. When you have clicked the Save global whitelist button: the relevant lines will be added to $apachepath/modsec2.whitelist.conf which has already been added to the top of $modsecpath/modsec2.user.conf. Then httpd.conf will be rebuilt and apache will be gracefully restarted.

Alternatively, you can disable rules on a per cPanel account or per domain basis by selecting a user and then clicking:

You can disable rules by DirectoryMatch (e.g. ^/home/someuser/public_html/ignore/some/path/)

Display cmc user/domain configuration map

\n"; print "\n"; print ""; print "\n"; print "\n"; print "
ConfigServer ModSecurity Tools
View the last entries in the ModSecurity log file and auto-refresh the log view\n"; print "

Note: If your audit_log file is very large it may take some time to process it.

Edit files containing ModSecurity configuration settings in $apachepath/. After a file has been edited httpd.conf will be rebuilt and apache gracefully restarted.

Note: Files or directories must be prefixed modsec* or mod_sec* to be detected.


\n"; print "\n"; my ($status, $text) = &urlget("https://$downloadserver/cmc/cmcversion.txt"); my $actv = $text; my $up = 0; print ""; if ($actv ne "") { if ($actv =~ /^[\d\.]*$/) { if ($actv > $myv) { print "\n"; } else { print "\n"; } $up = 1; } } unless ($up) { print "\n"; } print "
Upgrade
A new version of cmc (v$actv) is available. Upgrading will retain your settings
View ChangeLog
You appear to be running the latest version of cmc. An Upgrade button will appear here if a new version becomes available
Failed to determine the latest version of cmc. An Upgrade button will appear here if new version is detected

\n"; print "\n"; print "\n"; } print "
cmc: v$myv
"; print "

©2009-2019, ConfigServer Services (Jonathan Michaelson)

\n"; print < \$("#loader").hide(); \$("#docs-link").hide(); EOF unless ($FORM{action} eq "help") { close $SCRIPTOUT; select STDOUT; Cpanel::Template::process_template( 'whostmgr', { "template_file" => "${thisapp}.tmpl", "${thisapp}_output" => $templatehtml, "print" => 1, } ); } else { print "\n"; print "\n"; print "\n"; } # end main ############################################################################### # start showmap sub showmap { if (-e "$apachepath/modsec2.whitelist.conf") { my %ids; open (my $FH, "<", "$apachepath/modsec2.whitelist.conf"); flock ($FH, LOCK_SH); my @data = <$FH>; close ($FH); chomp @data; my $start = 0; foreach my $line (@data) { if ($line =~ /^\s*()|(# Start cmc block)/) {$start = 1} if ($start and $line =~ /SecRuleRemoveById\s+(\d*)/) {$ids{$1} = 1} if ($line =~ /^\s*(<\/LocationMatch>)|(# End cmc block)/) {$start = 0} } if (%ids) { print "Global Disabled ID:"; foreach my $id (sort keys %ids) {print " $id"} print "\n"; } } foreach my $userdir (glob "$stdpath/*") { if (-d $userdir) { my ($user, $filedir) = fileparse($userdir); unless (-f "/var/cpanel/users/$user") {next} my $off = 0; if (-f "$userdir/modsec.conf") { my $start = 0; my %ids; open (my $FH, "<", "$userdir/modsec.conf"); flock ($FH, LOCK_SH); my @data = <$FH>; close ($FH); chomp @data; foreach my $line (@data) { if ($line =~ /^\s*()|(# Start cmc block)/) {$start = 1} if ($start and $line =~ /SecRuleRemoveById\s+(\d*)/) {$ids{$1} = 1} if ($start and $line =~ /SecRuleEngine\s+Off/) {$off = 1} if ($line =~ /^\s*(<\/LocationMatch>)|(# End cmc block)/) {$start = 0} } if ($off) { print "$userModSecurity disabled \n"; } elsif (%ids) { print "$userModSecurity enabled \n"; print " User Disabled ID:"; foreach my $id (keys %ids) {print " $id"} print " \n"; } } unless ($off) { foreach my $domaindir (glob "$userdir/*") { if (-d $domaindir) { my ($domain, $filedir) = fileparse($domaindir); if (-f "$domaindir/modsec.conf") { my $start = 0; my $off = 0; my %ids; open (my $FH, "<", "$domaindir/modsec.conf"); flock ($FH, LOCK_SH); my @data = <$FH>; close ($FH); chomp @data; foreach my $line (@data) { if ($line =~ /^\s*()|(# Start cmc block)/) {$start = 1} if ($start and $line =~ /SecRuleRemoveById\s+(\d*)/) {$ids{$1} = 1} if ($start and $line =~ /SecRuleEngine\s+Off/) {$off = 1} if ($line =~ /^\s*(<\/LocationMatch>)|(# End cmc block)/) {$start = 0} } if ($off) { print " $domainModSecurity disabled\n"; } elsif (%ids) { print " $domainDomain Disabled ID:"; foreach my $id (sort keys %ids) {print " $id"} print "\n"; } } } } } } } return; } # end showmap ############################################################################### sub wanted { if (-f $File::Find::name) {push @files,$File::Find::name} return; } ############################################################################### sub modsec { my $start = 0; my $entry; my @requests; my $log = "$apachelogs/modsec_audit.log"; my $ruid2_itk = 0; my ($childin, $childout); my $mypid = open3($childin, $childout, $childout, $apachebin,"-M"); my @modules = <$childout>; waitpid ($mypid, 0); chomp @modules; if (my @ls = grep {$_ =~ /ruid2_module|mpm_itk_module/} @modules) { $ruid2_itk = 1; $log = "$apachelogs/modsec_audit/*"; } if ($ruid2_itk) { print "

Displaying logs from $apachelogs/modsec_audit/

\n"; find(\&wanted, "$apachelogs/modsec_audit"); @files = sort { -M $a <=> -M $b } @files; @files = reverse @files; foreach my $log (@files) { sysopen (my $IN, $log, O_RDWR | O_CREAT); flock ($IN, LOCK_SH); while (my $line = <$IN>) { chomp $line; if ($line =~ /^\=\=(\w*)\=*$/) { $start = $1; $entry = ""; } elsif ($line =~ /^\-\-(\w*)\-A\-\-$/) { $start = $1; $entry = ""; } elsif ($line =~ /^\-\-$start\-\-$/ and $start) { push @requests, $entry; $start = 0; $entry = ""; } elsif ($line =~ /^\-\-$start-Z\-\-$/ and $start) { push @requests, $entry; $start = 0; $entry = ""; } elsif ($start) { $entry .= "$line\n"; } } close ($IN); } } else { print "

Displaying logs from $log

\n"; sysopen (my $IN, $log, O_RDWR | O_CREAT); flock ($IN, LOCK_SH); while (my $line = <$IN>) { chomp $line; if ($line =~ /^\=\=(\w*)\=*$/) { $start = $1; $entry = ""; } elsif ($line =~ /^\-\-(\w*)\-A\-\-$/) { $start = $1; $entry = ""; } elsif ($line =~ /^\-\-$start\-\-$/ and $start) { push @requests, $entry; $start = 0; $entry = ""; } elsif ($line =~ /^\-\-$start-Z\-\-$/ and $start) { push @requests, $entry; $start = 0; $entry = ""; } elsif ($start) { $entry .= "$line\n"; } } close ($IN); } if ($FORM{refresh}) { print < //Refresh page script- By Brett Taylor (glutnix\@yahoo.com.au) //Modified by Dynamic Drive for NS4, NS6+ //Visit http://www.dynamicdrive.com for this script //configure refresh interval (in seconds) var countDownInterval=10; //configure width of displayed text, in px (applicable only in NS4) var c_reloadwidth=200 var page_url = "$script?action=ms_list&lines=$FORM{lines}&refresh=$FORM{refresh}"; EOF } if (@requests > 0) { my $start = 0; if ($FORM{lines} < @requests) {$start = @requests - $FORM{lines}} my $divcnt = 0; my $expcnt = @requests - $start; print "\n"; print "\n"; print "\n"; print "\n"; for (my $x = @requests -1; $x > $start - 1; $x--) { $divcnt++; $requests[$x] =~ s/\&/\&\;/g; $requests[$x] =~ s/>/\>\;/g; $requests[$x] =~ s/"; my $host; my $id; if (my @ls = grep {$_ =~ /^Host: /} @lines) { if ($ls[0] =~ /^Host: (.*)$/) {$host = $1} } if ($host eq "") {$host = $data[5]} if (my @ls = grep {$_ =~ /\s\[id \"\d+\"\]\s/} @lines) { if ($ls[0] =~ /\s\[id \"(\d+)\"\]\s/) {$id = $1} } if ($id eq "") {$id = "unknown"} print "\n"; my $entry = "\n"; if ($modsec =~ /\w*\.\s(.*)$/) {$modsec = $1} $modsec = &splitlines($modsec); print "\n"; } print "
ConfigServer ModSecurity Log Entries\n"; print "\n"; print "
DomainSource IPRule IDDate Stamp
$host$data[3]$id$data[0] $data[1] $span
$modsec$entry
\n"; } else { print "

No entries found in $log

\n"; } print "

\n"; return; } ############################################################################### # start printcmd sub printcmd { my @command = @_; my ($childin, $childout); my $pid = open3($childin, $childout, $childout, @command); while (<$childout>) {print $_} waitpid ($pid, 0); return; } # end printcmd ############################################################################### # start onoff sub onoff { my $file = shift; open (my $FH, "<", $file); flock ($FH, LOCK_SH); my @data = <$FH>; close ($FH); chomp @data; my $start = 0; my $dmstart = 0; open (my $OUT, ">", $file); flock ($OUT, LOCK_EX); print $OUT "# Do not modify this file directly as it will be overwritten by cmc\n"; print $OUT "\n"; unless ($FORM{choose}) {print $OUT "SecRuleEngine Off\n"} foreach my $line (@data) { if ($line =~ /^\#/) {next} if ($line =~ /^\s*\n"; close ($OUT); return; } # end onoff ############################################################################### # start ids sub ids { my $file = shift; my @ids = split(/\n|\r/,$FORM{ids}); chomp @ids; open (my $FH, "<", $file); flock ($FH, LOCK_SH); my @data = <$FH>; close ($FH); chomp @data; my $start = 0; open (my $OUT, ">", $file); flock ($OUT, LOCK_EX); print $OUT "# Do not modify this file directly as it will be overwritten by cmc\n"; print $OUT "\n"; foreach my $line (@data) { if ($line =~ /^\s*\n"; foreach my $id (@ids) { if ($id =~ /^\d+$/) {print $OUT "\tSecRuleRemoveById $id\n"} } print $OUT "\n"; print $OUT "\n"; close ($OUT); return; } # end ids ############################################################################### # start splitlines sub splitlines { my $line = shift; my $cnt = 0; my $newline; for (my $x = 0;$x < length($line) ;$x++) { if ($cnt > 120) { $cnt = 0; $newline .= ""; } my $letter = substr($line,$x,1); if ($letter =~ /\s/) { $cnt = 0; } else { $cnt++; } $newline .= $letter; } return $newline; } # end splitlines ############################################################################### ############################################################################### # start urlget (v1.3) # # Examples: #my ($status, $text) = &urlget("http://prdownloads.sourceforge.net/clamav/clamav-0.92.tar.gz","/tmp/clam.tgz"); #if ($status) {print "Oops: $text\n"} # #my ($status, $text) = &urlget("http://www.configserver.com/free/msfeversion.txt"); #if ($status) {print "Oops: $text\n"} else {print "Version: $text\n"} # sub urlget { my $url = shift; my $file = shift; my $status = 0; my $timeout = 1200; local $SIG{PIPE} = 'IGNORE'; use LWP::UserAgent; my $ua = LWP::UserAgent->new; $ua->timeout(30); my $req = HTTP::Request->new(GET => $url); my $res; my $text; ($status, $text) = eval { local $SIG{__DIE__} = undef; local $SIG{'ALRM'} = sub {die "Download timeout after $timeout seconds"}; alarm($timeout); if ($file) { local $|=1; my $expected_length; my $bytes_received = 0; my $per = 0; my $oldper = 0; open (my $OUT, ">", "$file\.tmp") or return (1, "Unable to open $file\.tmp: $!"); flock ($OUT, LOCK_EX); binmode ($OUT); print "...0\%\n"; $res = $ua->request($req, sub { my($chunk, $res) = @_; $bytes_received += length($chunk); unless (defined $expected_length) {$expected_length = $res->content_length || 0} if ($expected_length) { my $per = int(100 * $bytes_received / $expected_length); if ((int($per / 5) == $per / 5) and ($per != $oldper)) { print "...$per\%\n"; $oldper = $per; } } else { print "."; } print $OUT $chunk; }); close ($OUT); print "\n"; } else { $res = $ua->request($req); } alarm(0); if ($res->is_success) { if ($file) { rename ("$file\.tmp","$file") or return (1, "Unable to rename $file\.tmp to $file: $!"); return (0, $file); } else { return (0, $res->content); } } else { return (1, "Unable to download: ".$res->message); } }; alarm(0); if ($@) { return (1, $@); } if ($text) { return ($status,$text); } else { return (1, "Download timeout after $timeout seconds"); } } # end urlget ############################################################################### ## start getdownloadserver sub getdownloadserver { my @servers; my $downloadservers = "/usr/local/cpanel/whostmgr/docroot/cgi/configserver/cmc/downloadservers"; my $chosen; if (-e $downloadservers) { open (my $DOWNLOAD, "<", $downloadservers); flock ($DOWNLOAD, LOCK_SH); my @data = <$DOWNLOAD>; close ($DOWNLOAD); chomp @data; foreach my $line (@data) { if ($line =~ /^download/) {push @servers, $line} } ## foreach my $line (slurp($downloadservers)) { ## $line =~ s/$cleanreg//g; ## if ($line =~ /^download/) {push @servers, $line} ## } $chosen = $servers[rand @servers]; } if ($chosen eq "") {$chosen = "download.configserver.com"} return $chosen; } ## end getdownloadserver ############################################################################### 1;