Editing the content in WordPress by using its customizer can lead to an error. The changes won’t persist and the edited content won’t appear in the live view as it supposed to do.
What could be the reason and how can it be solved?
When you are editing the content of your WordPress site using the customizer and click on the save button, nothing happens. All your hard work just not getting saved.
By looking at the the web browser’s “Inspect” function (eg. Google Chrome) we can discover that the web server is sending 403 (Forbidden) HTTP statuses as response from admin-ajax.php.
This error occurs when the access to admin-ajax.php is declined, not permitted. It could be down to the permission settings of the file/folder, which by the way should be 0644 for admin-ajax.php and 0755 for its parent directory wp-admin. However, having the right file/folder permissions configured the problem is still there. Let’s look into the web server’s log files.
This scenario happened on an Apache server with ModSecurity running under CentOS (CWP) operating system. First examine the Apache’s error log file.$ cat /usr/local/apache/logs/error_log
1 |
$ cat /usr/local/apache/logs/error_log |
After going through the log file the following can be found (I’ve masked the IP, payload and host name).
1 |
[Thu Mar 09 07:37:02 2017] [error] [client xxx.xxx.196.210] ModSecurity: Access denied with code 403 (phase 2). Pattern match "\\\\b(?i:having)\\\\b\\\\s+(\\\\d{1,10}|'[^=]{1,10}')\\\\s*?[=<>]|(?i:\\\\bexecute(\\\\s{1,5}[\\\\w\\\\.$]{1,5}\\\\s{0,3})?\\\\()|\\\\bhaving\\\\b ?(?:\\\\d{1,10}|[\\\\'\\"][^=]{1,10}[\\\\'\\"]) ?[=<>]+|(?i:\\\\bcreate\\\\s+?table.{0,20}?\\\\()|(?i:\\\\blike\\\\W*?char\\\\W*?\\\\()|(?i:(?:(select(.* ..." at ARGS:customize_changeset_data. [file "/usr/local/apache/modsecurity-crs/base_rules/modsecurity_crs_41_sql_injection_attacks.conf"] [line "130"] [id "959070"] [rev "2"] [msg "SQL Injection Attack"] [data "Matched Data: This is the text which I wanted to change to something else! within ARGS:customize_changeset_data: {\\x22parallax_one_our_story_text\\x22:{\\x22value\\x22:\\x22<p>There were more text which were needed to be changed [hostname "www.domain.com"] [uri "/wordpress/wp-admin/admin-ajax.php"] [unique_id "WMEGHn8AAAEAACWLdyQAAAAE"] |
The “Access denied with code 403 (phase 2)” is related to the 403 (Forbidden) HTTP status. The log file nicely reviled the cause of the problem. The data or payload was sent to URI “/wordpress/wp-admin/admin-ajax.php” but it got blocked by ModSecurity’s security rule. It was recognized as “SQL Injection Attack” even though the request was sent from the back end with genuine login credentials.
What to do now?
At this point, let’s focus on ModSecurity’s rules and try to figure out which rule is responsible for the 403 problem. Take this part of Apache’s error log and examine it:
1 |
[file "/usr/local/apache/modsecurity-crs/base_rules/modsecurity_crs_41_sql_injection_attacks.conf"] [line "130"] [id "959070"] [rev "2"] [msg "SQL Injection Attack"] |
The file modsecurity_crs_41_sql_injection_attack.conf at line 130 contains the blocking rule with ID 959070. The ID is our main focus. This tells which rule causing the problem. If there are more than one IDs with different numbers then we have to deal with them individually. By knowing the ID number of the rule it is possible to go around it. There are multiple ways to do it, good ones and bad ones.
Dynamic IP
The host, which is used to login to WordPress back end, has dynamic public IP address. The easiest and maybe the most unsecured way is to disable the rule itself, it will help but in case of a real attack… So to do it, add a new line to /usr/local/apache/conf/mod_sec_disable_rules.conf with the following command and restart Apache server.
1 2 |
$ echo 'SecRuleRemoveById 959070' >> /usr/local/apache/conf/mod_sec_disabled_rules.conf $ service httpd restart |
As I mentioned, maybe this is not the best way to do it but if you use dynamic IP address when you login to WordPress then it’ll do it. The rule is completely disabled now. The following command will enable the rule again:
1 2 3 |
$ cd /usr/local/apache/conf/ $ cp mod_sec_disabled_rules.conf mod_sec_disabled_rules.conf.tmp $ sed '/959070/d' mod_sec_disabled_rules.conf.tmp > mod_sec_disabled_rules.conf |
Static IP
With static IP address or IP range, there is a better way to bypass the rule by white listing the host’s public address.
1 2 3 |
$ cd /usr/local/apache/modsecurity-crs/ $ echo 'SecRule REMOTE_ADDR "@ipMatch xxx.xxx.196.208/28"phase:1,nolog,allow,ctl:ruleEngine=Off,id:40' >> modsecurity_crs_10_config.conf $ service httpd restart |
Replace xxx.xxx.196.208/28 with your CIDR address.
This can prevent any further conflicts with ModSecurity in the future and the rule will still protect against any external intrusion.