Description
Describe the bug
When response code is changed during phase 4 and the original response code is other than 200, Apache httpd does not execute ErrorDocument directive and bad response is generated.
Logs and dumps
Output of:
- DebugLogs (level 9)
- AuditLogs
- Error logs
To Reproduce
- Add following configuration:
ProxyPass /helloworld http://localhost:9090/unavaible
ErrorDocument 403 /custompage.html
SecRule RESPONSE_PROTOCOL "@contains HTTP" "id:'4',phase:4,auditlog,log,deny,status:403,msg:'yay!'"
- Create and place custompage.html page to server root.
- execute
$ curl -v localhost/helloworld
Actual behavior
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<p>Additionally, a 503 Service Unavailable
error was encountered while trying to use an ErrorDocument to handle the request.</p>
</body></html>
Expected behavior
Proper rewritten response with custom error document:
Server (please complete the following information):
- ModSecurity version (and connector): mod_security-2.9.6
- WebServer: httpd-2.4.37
- OS (and distro): Linux Fedora
Rule Set (please complete the following information):
- Running any public or commercial rule set? No
Additional context
In my opinion, this is caused by apache httpds assumption that changed status of request which got through filter is a recursive error which occured during handling of previous errors.
See ap_die_r
function in http module of apache, particularly this part of code:
/*
* The following takes care of Apache redirects to custom response URLs
* Note that if we are already dealing with the response to some other
* error condition, we just report on the original error, and give up on
* any attempt to handle the other thing "intelligently"...
*/
if (recursive_error != HTTP_OK) {
while (r_1st_err->prev && (r_1st_err->prev->status != HTTP_OK))
r_1st_err = r_1st_err->prev; /* Get back to original error */
if (r_1st_err != r) {
/* The recursive error was caused by an ErrorDocument specifying
* an internal redirect to a bad URI. ap_internal_redirect has
* changed the filter chains to point to the ErrorDocument's
* request_rec. Back out those changes so we can safely use the
* original failing request_rec to send the canned error message.
*
* ap_send_error_response gets rid of existing resource filters
* on the output side, so we can skip those.
*/
update_r_in_filters(r_1st_err->proto_output_filters, r, r_1st_err);
update_r_in_filters(r_1st_err->input_filters, r, r_1st_err);
}
custom_response = NULL; /* Do NOT retry the custom thing! */
}
else {
int error_index = ap_index_of_response(type);
custom_response = ap_response_code_string(r, error_index);
recursive_error = 0;
}
Clearing the original response code with f->r->status = 200;
in send_error_bucket
function appears to fix this issue.
This issue is a continuation of #533 and extends findings of Marc Stern in #533 (comment)