1) return false;
}
return true;
}
/**
* This function checks if .htaccess file exists and is readable
*
* @param string $htaccess - The path to .htaccess file
* @return boolean
*/
public static function htaccess_exist_and_readable($htaccess) {
global $aio_wp_security;
if (!file_exists($htaccess)) {
$aio_wp_security->debug_logger->log_debug("The .htaccess file is missing", 4);
return false;
} elseif (!is_readable($htaccess)) {
$aio_wp_security->debug_logger->log_debug("The .htaccess file exists, but it is not readable.", 4);
return false;
}
return true;
}
/**
* Write all active rules to .htaccess file.
*
* @param boolean $show_error - if the error should be shown
*
* @return boolean True on success, false on failure.
*/
public static function write_to_htaccess($show_error = true) {
global $aio_wp_security;
if (!class_exists('AIOWPSecurity_Admin_Menu')) {
include_once AIO_WP_SECURITY_PATH . '/admin/wp-security-admin-menu.php';
}
// figure out what server is being used
$serverType = AIOWPSecurity_Utility::get_server_type();
if (in_array($serverType, array('-1', 'nginx', 'iis')) && !defined('WP_CLI')) {
if ($show_error) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('The .htaccess file is not supported by your web server.', 'all-in-one-wp-security-and-firewall'), !$show_error);
}
$aio_wp_security->debug_logger->log_debug("Unable to write to .htaccess - server type not supported.", 4);
return false; // unable to write to the file
}
$home_path = AIOWPSecurity_Utility_File::get_home_path();
$htaccess = $home_path . '.htaccess';
if (!self::htaccess_exist_and_readable($htaccess)) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('The .htaccess file either does not exist or is unreadable', 'all-in-one-wp-security-and-firewall'), !$show_error);
$aio_wp_security->debug_logger->log_debug("The .htaccess file either does not exist or is unreadable", 4);
return false;
} // check the existence of the file and if its readable
// confirm the hataccess has valid markers
if (!self::htaccess_has_valid_markers($htaccess)) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('The .htaccess file contains invalid content, please manually verify the file contents', 'all-in-one-wp-security-and-firewall'), !$show_error);
$aio_wp_security->debug_logger->log_debug("Unable to edit the .htaccess file as it contains invalid content, please manually verify the file contents", 4);
return false;
}
AIOWPSecurity_Utility_File::backup_and_rename_htaccess($htaccess);
// creating a copy of htaccess file to work on
$temp_htaccess = $home_path.'.htaccess_temp';
if (!copy($htaccess, $temp_htaccess)) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('A copy of the .htaccess file could not be created', 'all-in-one-wp-security-and-firewall'), !$show_error);
$aio_wp_security->debug_logger->log_debug("Write operation on .htaccess file failed, unable to create a copy of the file", 4);
return false;
}
// clean up old rules first
if (-1 == AIOWPSecurity_Utility_Htaccess::delete_from_htaccess($temp_htaccess)) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__("Unable to delete plugin's content from .htaccess file.", 'all-in-one-wp-security-and-firewall'), !$show_error);
$aio_wp_security->debug_logger->log_debug("Unable to delete plugin's content from .htaccess file.", 4);
return false; //unable to write to the file
}
$ht = explode("\n", implode('', file($temp_htaccess))); // parse each line of file into array
$rules = AIOWPSecurity_Utility_Htaccess::getrules();
$rulesarray = explode("\n", $rules);
$rulesarray = apply_filters('aiowps_htaccess_rules_before_writing', $rulesarray);
$contents = array_merge($rulesarray, $ht);
$f = @fopen($temp_htaccess, 'w+'); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore warning as we try to handle it below
if (!$f) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('Write operation on .htaccess failed.', 'all-in-one-wp-security-and-firewall'), !$show_error);
$aio_wp_security->debug_logger->log_debug("Write operation on .htaccess failed.", 4);
return false; //we can't write to the file
}
$blank = false;
// write each line to file
foreach ($contents as $insertline) {
if (trim($insertline) == '') {
if (false == $blank) {
fwrite($f, "\n" . trim($insertline));
}
$blank = true;
} else {
$blank = false;
fwrite($f, "\n" . trim($insertline));
}
}
if (is_resource($f)) @fclose($f);
// before writing into the live htaccess confirm the markers still valid
if (!self::htaccess_has_valid_markers($temp_htaccess)) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('The .htaccess file has invalid content, please manually verify that the file is properly formatted', 'all-in-one-wp-security-and-firewall'), !$show_error);
$aio_wp_security->debug_logger->log_debug("The .htaccess file has invalid content, please manually verify that the file is properly formatted", 4);
return false;
}
// copy the changes from the temp htaccess into the live htaccess from here
if (!copy($temp_htaccess, $htaccess)) {
AIOWPSecurity_Admin_Menu::show_msg_error_st(__('An error has occurred while writing to the .htaccess file.', 'all-in-one-wp-security-and-firewall'), !$show_error);
$aio_wp_security->debug_logger->log_debug("Failed to write to the .htaccess file", 4);
return false;
}
// Remove the temp htaccess file created
unlink($temp_htaccess);
return true; //success
}
/**
* This function will delete the code which has been added to the .htaccess file by this plugin
* It will try to find the comment markers "# BEGIN All In One WP Security" and "# END All In One WP Security" and delete contents in between
*
* @param string $htaccess - The htaccess file path to manipulate
* @param string $section - All in One Security
* @return Integer {-1,1} -1 for failure, 1 for success.
*/
public static function delete_from_htaccess($htaccess = '', $section = 'All In One WP Security') {
if (empty($htaccess)) {
$home_path = AIOWPSecurity_Utility_File::get_home_path();
$htaccess = $home_path . '.htaccess';
}
if (!file_exists($htaccess)) {
$ht = @fopen($htaccess, 'a+');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore warning as we try to handle it below
if (false === $ht) {
global $aio_wp_security;
$aio_wp_security->debug_logger->log_debug('Failed to create .htaccess file', 4);
return -1;
}
if (is_resource($ht)) @fclose($ht);
}
// Bug Fix: On some environments such as windows (xampp) this function was clobbering the non-aiowps-related .htaccess contents for certain cases.
// In some cases when WordPress saves the .htaccess file (eg, when saving permalink settings),
// the line endings differ from the expected "\n" endings. (WordPress saves with "\n" (UNIX style) but "\n" may be set as "\r\n" (WIN/DOS))
// In this case exploding via "\n" may not yield the result we expect.
// Therefore we need to do the following extra checks.
$ht_contents_imploded = implode('', file($htaccess));
if (empty($ht_contents_imploded)) {
return 1;
} elseif (strstr($ht_contents_imploded, "\n")) {
$ht_contents = explode("\n", $ht_contents_imploded); //parse each line of file into array
} elseif (strstr($ht_contents_imploded, "\r")) {
$ht_contents = explode("\r", $ht_contents_imploded); //parse each line of file into array
} elseif (strstr($ht_contents_imploded, "\r\n")) {
$ht_contents = explode("\r\n", $ht_contents_imploded); //parse each line of file into array
}
if ($ht_contents) { //as long as there are lines in the file
$state = true;
$f = @fopen($htaccess, 'w+'); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore warning as we try to handle it below
if (!$f) {
@chmod($htaccess, 0644);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore warning as we try to handle it below
$f = @fopen($htaccess, 'w+'); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- ignore warning as we try to handle it below
if (!$f) return -1;
}
foreach ($ht_contents as $markerline) { //for each line in the file
if (strpos($markerline, '# BEGIN ' . $section) !== false) { //if we're at the beginning of the section
$state = false;
}
if (true == $state) { //as long as we're not in the section keep writing
fwrite($f, trim($markerline) . "\n");
}
if (strpos($markerline, '# END ' . $section) !== false) { //see if we're at the end of the section
$state = true;
}
}
if (is_resource($f)) @fclose($f);
return 1;
}
return 1;
}
public static function getrules() {
global $aio_wp_security;
$rules = "";
$rules .= AIOWPSecurity_Utility_Htaccess::getrules_basic_htaccess();
$rules .= AIOWPSecurity_Utility_Htaccess::getrules_block_debug_log_access_htaccess();
$rules .= AIOWPSecurity_Utility_Htaccess::getrules_disable_index_views();
$rules .= AIOWPSecurity_Utility_Htaccess::getrules_disable_trace_and_track();
$rules .= AIOWPSecurity_Utility_Htaccess::getrules_5g_blacklist();
$rules .= AIOWPSecurity_Utility_Htaccess::prevent_image_hotlinks();
$custom_rules = AIOWPSecurity_Utility_Htaccess::getrules_custom_rules();
if ($aio_wp_security->configs->get_value('aiowps_place_custom_rules_at_top')=='1') {
$rules = $custom_rules . $rules;
} else {
$rules .= $custom_rules;
}
//TODO: The following utility functions are ready to use when we write the menu pages for these features
//Add more functions for features as needed
//$rules .= AIOWPSecurity_Utility_Htaccess::getrules_somefeature();
//Add outer markers if we have rules
if ('' != $rules) {
$rules = "# BEGIN All In One WP Security" . "\n" . $rules . "# END All In One WP Security" . "\n";
}
return $rules;
}
/**
* TODO - info
*/
public static function getrules_basic_htaccess() {
global $aio_wp_security;
$rules = '';
if ($aio_wp_security->configs->get_value('aiowps_enable_basic_firewall') == '1') {
$rules .= AIOWPSecurity_Utility_Htaccess::$basic_htaccess_rules_marker_start . "\n"; //Add feature marker start
//protect the htaccess file - this is done by default with apache config file but we are including it here for good measure
$rules .= self::create_apache2_access_denied_rule('.htaccess');
//disable the server signature
$rules .= 'ServerSignature Off' . "\n";
//limit file upload size
$upload_limit = $aio_wp_security->configs->get_value('aiowps_max_file_upload_size');
//Shouldn't be empty but just in case
$upload_limit = empty($upload_limit) ? AIOS_FIREWALL_MAX_FILE_UPLOAD_LIMIT_MB : $upload_limit;
$upload_limit = $upload_limit * 1024 * 1024; // Convert from MB to Bytes - approx but close enough
$rules .= 'LimitRequestBody '.$upload_limit . "\n";
// protect wpconfig.php.
$rules .= self::create_apache2_access_denied_rule('wp-config.php');
$rules .= AIOWPSecurity_Utility_Htaccess::$basic_htaccess_rules_marker_end . "\n"; //Add feature marker end
}
return $rules;
}
public static function getrules_block_debug_log_access_htaccess() {
global $aio_wp_security;
$rules = '';
if ($aio_wp_security->configs->get_value('aiowps_block_debug_log_file_access') == '1') {
$rules .= AIOWPSecurity_Utility_Htaccess::$debug_log_block_htaccess_rules_marker_start . "\n"; //Add feature marker start
$rules .= self::create_apache2_access_denied_rule('debug.log');
$rules .= AIOWPSecurity_Utility_Htaccess::$debug_log_block_htaccess_rules_marker_end . "\n"; //Add feature marker end
}
return $rules;
}
/**
* This function will disable directory listings for all directories, add this line to the
* site’s root .htaccess file.
* NOTE: AllowOverride must be enabled in the httpd.conf file for this to work!
*/
public static function getrules_disable_index_views() {
global $aio_wp_security;
$rules = '';
if ($aio_wp_security->configs->get_value('aiowps_disable_index_views') == '1') {
$rules .= AIOWPSecurity_Utility_Htaccess::$disable_index_views_marker_start . "\n"; //Add feature marker start
$rules .= 'Options -Indexes' . "\n";
$rules .= AIOWPSecurity_Utility_Htaccess::$disable_index_views_marker_end . "\n"; //Add feature marker end
}
return $rules;
}
/**
* This function will write rules to disable trace and track.
* HTTP Trace attack (XST) can be used to return header requests
* and grab cookies and other information and is used along with
* a cross site scripting attacks (XSS)
*/
public static function getrules_disable_trace_and_track() {
global $aio_wp_security;
$rules = '';
if ($aio_wp_security->configs->get_value('aiowps_disable_trace_and_track') == '1') {
$rules .= AIOWPSecurity_Utility_Htaccess::$disable_trace_track_marker_start . "\n"; //Add feature marker start
$rules .= '' . "\n";
$rules .= 'RewriteEngine On' . "\n";
$rules .= 'RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)' . "\n";
$rules .= 'RewriteRule .* - [F]' . "\n";
$rules .= '' . "\n";
$rules .= AIOWPSecurity_Utility_Htaccess::$disable_trace_track_marker_end . "\n"; //Add feature marker end
}
return $rules;
}
/**
* This function contains the rules for the 5G blacklist produced by Jeff Starr from perishablepress.com
* NOTE: Since Jeff regularly updates and evolves his blacklist rules, ie, 5G->6G->7G.... we will update this function to reflect the latest blacklist release
*/
public static function getrules_5g_blacklist() {
global $aio_wp_security;
$rules = '';
if ('1' == $aio_wp_security->configs->get_value('aiowps_enable_5g_firewall')) {
$rules .= AIOWPSecurity_Utility_Htaccess::$five_g_blacklist_marker_start . "\n"; //Add feature marker start
$rules .= '# 5G BLACKLIST/FIREWALL (2013)
# @ http://perishablepress.com/5g-blacklist-2013/
# 5G:[QUERY STRINGS]
RewriteEngine On
RewriteBase /
RewriteCond %{QUERY_STRING} (\"|%22).*(<|>|%3) [NC,OR]
RewriteCond %{QUERY_STRING} (javascript:).*(\;) [NC,OR]
RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3) [NC,OR]
RewriteCond %{QUERY_STRING} (\\\|\.\./|`|=\'$|=%27$) [NC,OR]
RewriteCond %{QUERY_STRING} (\;|\'|\"|%22).*(union|select|insert|drop|update|md5|benchmark|or|and|if) [NC,OR]
RewriteCond %{QUERY_STRING} (base64_encode|localhost|mosconfig) [NC,OR]
RewriteCond %{QUERY_STRING} (boot\.ini|echo.*kae|etc/passwd) [NC,OR]
RewriteCond %{QUERY_STRING} (GLOBALS|REQUEST)(=|\[|%) [NC]
RewriteRule .* - [F]
# 5G:[USER AGENTS]
# SetEnvIfNoCase User-Agent ^$ keep_out
SetEnvIfNoCase User-Agent (binlar|casper|cmsworldmap|comodo|diavol|dotbot|feedfinder|flicky|ia_archiver|jakarta|kmccrew|nutch|planetwork|purebot|pycurl|skygrid|sucker|turnit|vikspider|zmeu) keep_out
Order Allow,Deny
Allow from all
Deny from env=keep_out
# 5G:[REQUEST STRINGS]
RedirectMatch 403 (https?|ftp|php)\://
RedirectMatch 403 /(https?|ima|ucp)/
RedirectMatch 403 /(Permanent|Better)$
RedirectMatch 403 (\=\\\\\\\'|\=\\\%27|/\\\\\\\'/?|\)\.css\()$
RedirectMatch 403 (\,|\)\+|/\,/|\{0\}|\(/\(|\.\.\.|\+\+\+|\||\\\\\"\\\\\")
RedirectMatch 403 \.(cgi|asp|aspx|cfg|dll|exe|jsp|mdb|sql|ini|rar)$
RedirectMatch 403 /(contac|fpw|install|pingserver|register)\.php$
RedirectMatch 403 (base64|crossdomain|localhost|wwwroot|e107\_)
RedirectMatch 403 (eval\(|\_vti\_|\(null\)|echo.*kae|config\.xml)
RedirectMatch 403 \.well\-known/host\-meta
RedirectMatch 403 /function\.array\-rand
RedirectMatch 403 \)\;\$\(this\)\.html\(
RedirectMatch 403 proc/self/environ
RedirectMatch 403 msnbot\.htm\)\.\_
RedirectMatch 403 /ref\.outcontrol
RedirectMatch 403 com\_cropimage
RedirectMatch 403 indonesia\.htm
RedirectMatch 403 \{\$itemURL\}
RedirectMatch 403 function\(\)
RedirectMatch 403 labels\.rdf
RedirectMatch 403 /playing.php
RedirectMatch 403 muieblackcat
# 5G:[REQUEST METHOD]
RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
RewriteRule .* - [F]
' . "\n";
$rules .= AIOWPSecurity_Utility_Htaccess::$five_g_blacklist_marker_end . "\n"; //Add feature marker end
}
return $rules;
}
/**
* This function will write some directives to prevent image hotlinking
*/
public static function prevent_image_hotlinks() {
global $aio_wp_security;
$rules = '';
if ($aio_wp_security->configs->get_value('aiowps_prevent_hotlinking') == '1') {
$url_string = AIOWPSecurity_Utility_Htaccess::return_regularized_url(AIOWPSEC_WP_HOME_URL);
if (false == $url_string) {
$url_string = AIOWPSEC_WP_HOME_URL;
}
$rules .= AIOWPSecurity_Utility_Htaccess::$prevent_image_hotlinks_marker_start . "\n"; //Add feature marker start
$rules .= '' . "\n";
$rules .= 'RewriteEngine On' . "\n";
$rules .= 'RewriteCond %{HTTP_REFERER} !^$' . "\n";
$rules .= 'RewriteCond %{REQUEST_FILENAME} -f' . "\n";
$rules .= 'RewriteCond %{REQUEST_FILENAME} \.(gif|jpe?g?|png)$ [NC]' . "\n";
$rules .= 'RewriteCond %{HTTP_REFERER} !^' . $url_string . ' [NC]' . "\n";
$rules .= 'RewriteRule \.(gif|jpe?g?|png)$ - [F,NC,L]' . "\n";
$rules .= '' . "\n";
$rules .= AIOWPSecurity_Utility_Htaccess::$prevent_image_hotlinks_marker_end . "\n"; //Add feature marker end
}
return $rules;
}
/**
* This function will write any custom htaccess rules into the server's .htaccess file
*
* @return string
*/
public static function getrules_custom_rules() {
global $aio_wp_security;
$rules = '';
if ($aio_wp_security->configs->get_value('aiowps_enable_custom_rules') == '1') {
$custom_rules = $aio_wp_security->configs->get_value('aiowps_custom_rules');
$rules .= AIOWPSecurity_Utility_Htaccess::$custom_rules_marker_start . "\n"; //Add feature marker start
$rules .= $custom_rules . "\n";
$rules .= AIOWPSecurity_Utility_Htaccess::$custom_rules_marker_end . "\n"; //Add feature marker end
}
return $rules;
}
/**
* This function will do a quick check to see if a file's contents are actually .htaccess specific.
* At the moment it will look for the following tag somewhere in the file - "# BEGIN WordPress"
* If it finds the tag it will deem the file as being .htaccess specific.
* This was written to supplement the .htaccess restore functionality
*
* @param string $file_contents - the contents of the .htaccess file
*
* @return boolean
*/
public static function check_if_htaccess_contents($file_contents) {
$is_htaccess = false;
if (false === $file_contents || strlen($file_contents) == 0) {
return -1;
}
if ((strpos($file_contents, '# BEGIN WordPress') !== false) || (strpos($file_contents, '# BEGIN') !== false)) {
$is_htaccess = true; // It appears that we have some sort of .htaccess file
} else {
//see if we're at the end of the section
$is_htaccess = false;
}
if ($is_htaccess) {
return 1;
} else {
return -1;
}
}
/**
* This function will take a URL string and convert it to a form useful for using in htaccess rules.
* Example: If URL passed to function = "http://www.mysite.com"
* Result = "http(s)?://(.*)?mysite\.com"
*
* @param string $url
* @return string
*/
public static function return_regularized_url($url) {
if (filter_var($url, FILTER_VALIDATE_URL)) {
$xyz = explode('.', $url);
$y = '';
if (count($xyz) > 1) {
$j = 1;
foreach ($xyz as $x) {
if (strpos($x, 'www') !== false) {
$y .= str_replace('www', '(.*)?', $x);
} elseif (1 == $j) {
$y .= $x;
} elseif ($j > 1) {
$y .= '\.' . $x;
}
$j++;
}
//Now replace the "http" with "http(s)?" to cover both secure and non-secure
if (strpos($y, 'https') !== false) {
$y = str_replace('https', 'http(s)?', $y);
} elseif (strpos($y, 'http') !== false) {
$y = str_replace('http', 'http(s)?', $y);
}
return $y;
} else {
return $url;
}
} else {
return false;
}
}
/**
* Returns a string with directive that contains rules
* to effectively block access to any file that has basename matching
* $filename under Apache webserver.
*
* @link http://httpd.apache.org/docs/current/mod/core.html#files
*
* @param string $filename
* @return string
*/
protected static function create_apache2_access_denied_rule($filename) {
return <<
Require all denied
Order deny,allow
Deny from all
END;
// Keep the empty line at the end of heredoc string,
// otherwise the string will not end with end-of-line character!
}
/**
* Convert an array of optionally asterisk-masked or partial IPv4 addresses
* into network/netmask notation. Netmask value for a "full" IP is not
* added (see example below)
*
* Example:
* In: array('1.2.3.4', '5.6', '7.8.9.*')
* Out: array('1.2.3.4', '5.6.0.0/16', '7.8.9.0/24')
*
* Simple validation is performed:
* In: array('1.2.3.4.5', 'abc', '1.2.xyz.4')
* Out: array()
*
* Simple sanitization is performed:
* In: array('6.7.*.9')
* Out: array('6.7.0.0/16')
*
* @param array $ips
* @return array
*/
protected static function add_netmask($ips = array()) {
$output = array();
if (empty($ips)) return array();
foreach ($ips as $ip) {
//Check if ipv6
if (strpos($ip, ':') !== false) {
//for now support whole ipv6 and CIDR range.
$checked_ip = AIOWPSecurity_Utility_IP::is_ipv6_address_or_ipv6_range($ip);
if (false != $checked_ip) {
$output[] = $ip;
}
}
$parts = explode('.', $ip);
// Skip any IP that is empty, has more parts than expected or has
// a non-numeric first part.
if (empty($parts) || (count($parts) > 4) || !is_numeric($parts[0])) {
continue;
}
$ip_out = array($parts[0]);
$netmask = 8;
for ($i = 1, $force_zero = false; ($i < 4) && $ip_out; $i++) {
if ($force_zero || !isset($parts[$i]) || ('' === $parts[$i]) || ('*' === $parts[$i])) {
$ip_out[$i] = '0';
$force_zero = true; // Forces all subsequent parts to be a zero
} elseif (is_numeric($parts[$i])) {
$ip_out[$i] = $parts[$i];
$netmask += 8;
} else {
// Invalid IP part detected, invalidate entire IP
$ip_out = false;
}
}
if ($ip_out) {
// Glue IP back together, add netmask if IP denotes a subnet, store for output.
$output[] = implode('.', $ip_out) . (($netmask < 32) ? ('/' . $netmask) : '');
}
}
return $output;
}
public static function get_htaccess_path() {
$home_path = AIOWPSecurity_Utility_File::get_home_path();
return $home_path . '.htaccess';
}
}