Apache - deny non-local requests that match a specific URL pattern - apache

I need to configure my Apache (2.4) in a way, that it prevents answering specific requests
I have an application that runs on a server myapplication.com.
Browsing the application triggers some further ajax requests on the server, having the pattern myapplication.com/abc. These kind of "abc"-requests must be prohibited, when being called from a different context then this one. Thus, is should not be possible to call this request in a different tab. Neither should it be possible to call these requests outside a browser context, for example via curl or wget or any non-browser http client.
The directive must look something like this
<If "Request has not been triggered as ajax from within my application" && %{REQUEST_URL} contains 'abc' ">
Require all denied
</If>
This directive causes an error and I dont know why, changing =~ to !~ is semantically false but does not cause an error.
<If "%{HTTP_REFERER} !~ /myapplication.com/ && %{QUERY_STRING} =~ /abc/ ">
Require all denied
</If>
What can be wrong with that?
Any help debugging the apache config would also be appreciated - I don't get any information why this directive causes errors.

Any XMLHttpRequest or fetch call made from myapplication.com in browser will contain myapplication.com as referer header.
The following apache conf snippet will block requests which referrer isn't myapplication.com.
<Location "/abc">
RewriteEngine on
RewriteCond "%{HTTP_REFERER}" "!myapplication.com"
RewriteRule . - [R=403,L]
</Location>
This configuration requires mod_rewrite which can be enabled with a2enmod rewrite if not already done.

Related

Using Apache REDIRECT_STATUS to allow custom ErrorDocument

I have an Apache config that uses legacy access rules and CGI error documents.
ErrorDocument 403 /perl/dispay.pl?page=error403
SetEnvIf User-Agent "SomeOldUserAgent" badUA
Deny from env=badUA
The blocking of the user agent works fine, but unfortunately the Deny rule also covers the generating of the error page.
I would like to exclude the error page from the blocking to provide a dynamic error message.
I have tried to allow by REDIRECT_STATUS, but this doesn't work.
SetEnvIf REDIRECT_STATUS 403 errorPage
Allow from env=errorPage
Any ideas ?

Block access to PHP file using .htaccess

I want to block direct access to PHP file, for example when someone enters it manually in the address bar (https://example.com/php/submit.php). I used this code:
<Files "submit.php">
Order Allow,Deny
Deny from all
</Files>
But if I block it this way, the form can't be submitted (it doesn't send mails).
Is there another way to block direct access but to be able to submit form?
<form id="form" action="php/submit.php" method="post">
Your form is making a POST request, whereas "when someone enters it manually in the address bar" they are making a GET request. So, you could block anything but POST requests..
Using <LimitExcept>
For example, surround your existing directives in a <LimitExcept> container:
<LimitExcept POST>
<Files "submit.php">
Require all denied
</Files>
</LimitExcept>
Note that this blocks non-POST requests to any submit.php file on your system.
NB: Order, Allow and Deny are Apache 2.2 directives and formerly deprecated on Apache 2.4 (which you are more likely to be using, unless you are on LiteSpeed). Require all denied is the Apache 2.4 equivalent. However, you should not mix authentication directives from both modules.
Using mod_rewrite
Alternatively, using mod_rewrite near the top of the root .htaccess file you can target the /php/submit.php URL-path directly. For example:
RewriteEngine On
RewriteCond %{REQUEST_METHOD} !=POST [NC]
RewriteRule ^php/submit\.php$ - [F]
The above will serve a 403 Forbidden for any request to /php/submit.php that is not a POST request (eg. when a user types the URL directly in the browser's address bar).
Alternatively, check that the request is a GET request. ie. =GET
HOWEVER, you should already be performing this check as part of your standard form validation in your PHP code, so this additional check in .htaccess should be redundant. (After all, how are you checking that the form has been (successfully) submitted?)

How to set status code for certain URLs with IF in htaccess

I have URLs like https://example.com/page/1234/ and https://example.com/page/9876/. For these URLs i want set certain status code via htaccess (Apache 2.4).
I try to do it with
<If "%{REQUEST_URI} =~ (1234|9876)\/$">
Header set Status "HTTP/1.1 410 Gone"
</If>
but i seem to have an error in the code, because i don't see the new status code as response in developer tools. How should it be done on the correct way?
PS: i can't use 'rewriteRule' - this kind of setting 410 triggers ErrorDocument, what i don't want. For these two URLs i only want to set the status code. For other URLs, which get 410 on the native way, ErrorDocument shold be triggered.
You may use this block with <If> expression that uses default ErrorDocument 410 for a specific URL pattern:
RewriteEngine On
<If "%{REQUEST_URI} =~ m#/(1234|9876)/?$#">
# only for above URL disable ErrorDocument
ErrorDocument 410 default
# set status=410
RewriteRule . - [L,G]
</If>
Are you wanting the "normal page response" (as generated by your application), but with a 410 HTTP status?
100% correct: usual page, but with response status 410 Gone
Triggering the 410 response in Apache will always serve the appropriate Apache ErrorDocument. What you could do is set the 410 ErrorDocument itself to the same URL and trigger the 410 response in the usual way. However, we need to be careful not to create a "rewrite-loop".
For example:
<If "%{REQUEST_URI} =~ m#^/page/(1234|9876)/$# && %{ENV:REDIRECT_STATUS} == ''">
# Capture the URL-path after the slash prefix
SetEnvIf Request_URI "^/(.+)" REL_URL_PATH=$1
# Dynamic ErrorDocument to the same as the requested URL
# The slash prefix in the directive is necessary to be seen as a local URL-path
ErrorDocument 410 /%{reqenv:REL_URL_PATH}
# Trigger 410 Gone
RewriteRule ^ - [G]
</If>
This requires Apache 2.4.13+ due to the expression syntax in the ErrorDocument directive.
The check against the REDIRECT_STATUS in the <If> expression is necessary to avoid a rewrite loop (500 response) when serving the error document itself.
The alternative is to set the 410 response code in your application itself, which would be my preference.
Aside: Setting the Status HTTP response header, which appears to be what you are trying to do in the question simply sets a Status HTTP response header, it does not change the HTTP response status itself. (The Status response header is a non-standard header used by CGI scripts to indicate to the webserver what response code should be set.)
UPDATE: in my tests if i add to htaccess only the rule Header set Status "HTTP/1.1 410 Gone", without any condition, it works like i expect: all URLs from this directory get the header 410 Gone but are still available (ErrorDocument isn't triggered).
Maybe your server is configured differently and is perhaps behind a proxy that sets the HTTP response? But as mentioned above, that simply sets a Status HTTP response header, it doesn't change the HTTP response code on the request. It doesn't "work" on my test server. (If used at all, the Status header doesn't normally contain the protocol, it would simply be Header set Status "410 Gone".)
So, if this does work for you then you just need to "correct" the syntax in your Apache expression.
For example:
<If "%{REQUEST_URI} =~ m#/(1234|9876)/$#">
Header set Status "HTTP/1.1 410 Gone"
</If>
The above matches any URL that simply ends with /1234/ or /9876/. Or, to be more specific and match the entire URL use m#^/page/(1234|9876)/$#. This uses the alternative syntax for delimiting regex (ie. m#<regex>#), instead of using slashes (ie. /<regex>/), in order to avoid having to escape the slashes in the pattern.
Alternatively, you don't need the Apache expression, you could use SetEnvIf and set the header conditionally based on the environment variable.
For example:
SetEnvIf Request_URI "/(1234|9876)/$" GONE=1
Header set Status "HTTP/1.1 410 Gone" env=GONE
With your shown samples please try following. Please do clear your browser cache before testing your URLs.
RewriteEngine ON
RewriteRule ^page/(1234|9876)/?$ - [NC,R=410,L]

Apache redirect location from new server to old

www.old-server.com/ws/ is the base URL for a web service on old-server. This is accomplished using a Location directive for "/ws" with a setHandler enclosed within the directive.
when a request for www.new-server.com/ws/ is received new-server needs to redirect the request to old-server/ws/. Any trailing parts of request URL need to be passed to old-server as well.
Running Apache2, with mod_rewrite.so loaded, on CentOS 7.
on new-server, the following does not work.
<Virtual Hosts>
...
<Location /ws>
Redirect "https://www.old-server/ $1"
</Location>
...
</Virtual Hosts>
I think the doc's say Redirect is not support inside of Location.
What is the correct way to do redirect the request URL to old-server?
The answer for me was to stop over thinking the problem. The Location directive is fine, but my redirect statement was wrong. The following is working at the moment, pending further testing with more complex scenarios.
<Virtual Hosts>
...
<Location /ws>
Redirect "/ws" "https://www.old-server/ws"
</Location>
...
</Virtual Hosts>

How to disable HTTP 1.0 protocol in Apache?

HTTP 1.0 has security weakness related to session hijacking.
I want to disable it on my web server.
You can check against the SERVER_PROTOCOL variable in a mod-rewrite clause. Be sure to put this rule as the first one.
RewriteEngine On
RewriteCond %{SERVER_PROTOCOL} ^HTTP/1\.0$
RewriteCond %{REQUEST_URI} !^/path/to/403/document.html$
RewriteRule ^ - [F]
The additional negative check for !^/path/to/403/document.html$ is so that the forbidden page can be shown to the users. It would otherwise lead to a recursion.
If you are on a name-based virtual host (and each virtual server does not have its own separate IP address), then it is technically impossible to connect to your virtual host using HTTP/1.0; Only the default server --the first virtual server defined-- will be accessible. This is because HTTP/1.0 does not support the HTTP "Host" request header, and the Host header is required on name-based virtual hosts in order to "pick" which virtual host the request is being addressed to. In most cases, the response to a true HTTP/1.0 request will be a 400-Bad Request.If you did manage to get that code working, but you later tried to use custom error documents (see Apache core ErrorDocument directive), then the result of blocking a request would be an 'infinite' loop: The server would try to respond with a 403-Forbidden response code, and to serve the custom 403 error document. But this would result in another 403 error because access to all resources --including the custom 403 page-- is denied. So the server would generate another 403 error and then try to respond to it, creating another 403, and another, and another... This would continue until either the client or the server gave up.
I'd suggest something like:
SetEnvIf Request_Protocol HTTP/1\.0$ Bad_Req
SetEnvIf Request_URI ^/path-to-your-custom-403-error-page\.html$
Allow_Bad_Req
#Order Deny,Allow
Deny from env=BadReq
Allow from env=Allow_Bad_Req
In mod_rewrite, something like:
RewriteCond %{THE_REQUEST} HTTP/1\.0$
RewriteCond %{REQUEST_URI} !^/path-to-your-custom-403-error-page\.html$
This will (note the FUTURE tense - as of October 2018) be possible with Apache 2.5, using the PolicyVersion directive in mod_policy. The PolicyVersion directive sets the lowest level of the HTTP protocol that is accepted by the server, virtual host, or directory structure - depending on where the directive is placed.
First enable the policy module:
a2enmod mod_policy
Then in the server config, vhost, or directory (will not work in .htaccess), add:
PolicyVersion enforce HTTP/1.1
Finally restart the server:
systemctl restart apache2