Apache 2.2 - "Allow from" only if QUERY_STRING matches - apache

I'm trying to limit one of my servers to only one specific request, but after 2 hours of trying couldn't come up with a working solution. Basically I'm looking for something similar to the <If ...> directive, but I only have Apache 2.2 (this is a fact, and I cannot update to 2.4).
I have 4 Servers: frontend[1-3] and backend1. frontend[1-2] are allowed to do anything on backend1, but frontend3 should only be allowed to make 1 specific request. In Apache 2.4 it would look something like this:
<Location />
Order allow,deny
Allow from frontend1
Allow from frontend2
<If "%{QUERY_STRING} =~ /foobar/myfunc/[^/]*$">
Allow from frontend3
</If>
</Location>
How can I do the same in Apache 2.2? I tried using SetEnvIf, but since it hasn't got logical AND it was a mess and didn't work (I have to match the host and the URL, since frontend3 is only allowed to do "myfunc").

Found a way with the help of mod_rewrite:
RewriteEngine On
# Matching the host "frontend3"
RewriteCond %{REMOTE_ADDR} ^1\.2\.3\.4$
# Matching the request "/foobar/myfunc?do=something"
RewriteCond %{QUERY_STRING} ^do=something$
# Setting an environment variable if host & request match
RewriteRule ^/foobar/myfunc$ - [E=allowthis:1,L]
<Location />
Order allow,deny
Allow from frontend1
Allow from frontend2
Allow from env=allowthis
</Location>

Related

Denying access to URLs then IP blocks in htaccess

I'm trying to block bad bots from clicking certain links to one site running Apache 2.4. Here is what I am trying in htaccess:
RewriteEngine On
# Check for the suspect querystring first
RewriteCond %{QUERY_STRING} gclid=(.*)
RewriteRule .* - [E=IsAdClick:1]
# Filter on those requests with an ad string
<IfDefine IsAdClick>
# BAN USER BY IP
Order Deny,Allow
Deny from 172.64.0.0/13
Deny from 173.245.48.0/20
...
</IfDefine>
The deny rules work if they are by themselves, but for the life of me I cannot get the conditional to work. I've tried other things like
<If "%{QUERY_STRING} =~ /gclid=.*?/">
# BAN USER BY IP
Order Deny,Allow
Deny from 172.64.0.0/13
Deny from 173.245.48.0/20
...
</If>
but there is no effect. Traffic still comes through. What am I missing? I don't want to write a whole bunch of RewriteCond for each IP, nor change the .config files. Thanks.
Update: According to this SO post it seems that IfDefine only responds to command line parameters. Ref:
The IfDefine directive in Apache, Only , ONLY and when I say only i
mean ONLY, responds to parameters passed at the command line. Let me
emphasize that a little. ONLY COMMAND LINE!
How to achieve the effect I'm looking for though?
This took a lot of trial and error, but this seems to be working on THE_REQUEST which include any querystring data:
# Filter on those requests with an adwords string
<If "%{THE_REQUEST} =~ /gclid=/i">
# BAN USER BY IP
Order Deny,Allow
Deny from 172.64.0.0/13
Deny from 173.245.48.0/20
...
</If>
Still, I'd like to know why my second attempt in my question failed.

Apache ACL based on environment variable value

I have a vendor Apache module (PingFederate) that sets environment variables based on a token it receives. I would like to control access to directories based on the value of an environment variable.
For example the module sets variables like this:
[PF_AUTH_SUBJECT] => aaron
[PF_AUTH_GROUPS] => CN=Application.E18.Users,OU=Internal,DC=local,CN=Application.E17.Users,OU=Internal,DC=local
I want to secure a directory so that only users in group CN=Application.E18... can access it. My location direction conf looks like this:
<Location /example_app>
SetEnvIf %{PF_AUTH_GROUPS} ^.*CN=Application.E18.Users.*$ ALLOWED
AuthName "ACL PingFederate restricted"
AuthType PFApacheAgent
Order Deny,Allow
Deny from all
Allow from all
</Location>
This doesn't seem to work. I've tried:
SetEnvIf %{PF_AUTH_GROUPS} ^.*CN=Application.E18.Users.*$ ALLOWED
SetEnvIf %{PF_AUTH_GROUPS} ^.*Application.*$ ALLOWED
SetEnvIf %{PF_AUTH_GROUPS} ^.*A.*$ ALLOWED
The only thing that works is:
SetEnvIf %{PF_AUTH_GROUPS} ^.*$ ALLOWED
That obviously won't work.
https://issues.apache.org/bugzilla/show_bug.cgi?id=25725 somewhat intimates that SetEnvIf won't test environment variables but the docs at http://httpd.apache.org/docs/2.2/mod/mod_setenvif.html mentions "an environment variable in the list of those associated with the request", which this should be.
I've also tried mod_rewrite using this:
RewriteEngine On
<Location /example_app>
RewriteCond %{PF_AUTH_GROUPS} ^.*Application.E18.Users.*$
RewriteRule - [E=ALLOWED:1]
AuthName "ACL PingFederate restricted"
AuthType PFApacheAgent
Order Deny,Allow
Deny from all
Allow from all
</Location>
In all of these instances the ALLOWED environment variable is not set.
You'll need to make mod_rewrite execute 2 times to be able to leverage the headers produced by mod_pf, since the latter executes after the former:
RewriteEngine On
RewriteCond %{ENV:REDIRECT_PASS} !1
RewriteRule .* $1 [L,E=PASS:1]
RewriteCond %{HTTP:PF_AUTH_GROUPS} !^.*ECN.*$
RewriteRule .* $1 [L,R=401]
This is also documented here: https://ping.force.com/Support/PingIdentityArticle?id=kA340000000Gs7bCAC

mod_rewrite: redirect requests from localhost to remote server

I have a following scenario:
Remote server with some webapp running at http://remote/webapp
Local machine inside corporate network
Corporate proxy between them
Apache with mod_rewrite running on my local machine
I would like to have the mod_proxy rewrite every request like http://localhost/webapp?someparams into http://remote/webapp?someparams.
Currently I have the following httpd.conf:
DocumentRoot "C:/Apache2.2/htdocs"
<Directory />
RewriteEngine On
RewriteRule ^(.+) http://remote/$1
Options FollowSymLinks
AllowOverride All
Order deny,allow
Deny from all
</Directory>
Which results in mod_rewrite transforming http://localhost/webapp?someparams into http://remote/C:/Apache2.2/htdocs/webapp?someparams
How to configure mod_rewrite to handle it correctly?
Since it looks like you have access to vhost/server config, you should ditch mod_rewrite and just use mod_proxy:
ProxyPass /webapp http://remote/webapp
ProxyPassReverse /webapp http://remote/webapp
and get rid of the 2 mod_rewrite lines (which is redirecting, not proxying):
RewriteEngine On
RewriteRule ^(.+) http://remote/$1
Note that if you have cookies, you may need to reverse map their domains.using ProxyPassReverseCookieDomain.
Also:
The fact that windows absolute path appears in the URL is due to misconfiguration of the mod_rewrite and this is what I'm trying to avoid
This is not a misconfiguration with mod_rewrite. When you put rewrite rules inside a <Directory>, the filepath is used in the match instead of the URI-path. According to the mod_rewrite documentation
What is matched?
In VirtualHost context, The Pattern will initially be matched against the part of the URL after the hostname and port, and before the query string (e.g. "/app1/index.html").
In Directory and htaccess context, the Pattern will initially be matched against the filesystem path, after removing the prefix that lead the server to the current RewriteRule (e.g. "app1/index.html" or "index.html" depending on where the directives are defined).
Thank you Jon for inspiration, finally mod_proxy + mod_rewrite worked:
# Global context
RewriteEngine On
RewriteRule ^(.+) http://remote/$1 [P]
ProxyPassReverse / http://remote/
I know that this is a simplified and coarse solution, but works for my purpose.

Can htaccess read a server environment variable set in Apache?

When you set a server environment variable in your Apache settings, it's possible to read that variable from PHP using built in functions.
But can you read the variable from an htaccess file?
What I'm trying to accomplish in my htaccess is something along the lines of:
<If var=="stuff">
do things here
</if>
<if var=="different stuff">
do different things here
</if>
Yes it is.
You will probably want to use mod_setenvif functionality, so that module will need to be turned on.
Then you simply do something like:
SetEnvIf Request_URI "\.gif$" image_type=gif
Which would set the environmental variable image_type with a value of gif if the requested file name ends with .gif.
You could then access this (like in RewriteCond) like this:
RewriteCond %{ENV:image_type} gif
RewriteRule ^(.*) $.gif
You can read the full documentation here: http://httpd.apache.org/docs/2.2/env.html
As I had a lot of problems setting up a similar thing to recognise my home localhost as distinct to my work set up, here's what I ended up doing.
First and foremost, The <IF ..> directive does not recognise SetEnv. From http://httpd.apache.org/docs/current/env.html : "The SetEnv directive runs late during request processing meaning that directives such as SetEnvIf and RewriteCond will not see the variables set with it."
So, try to use SetEnvIf if you can. I was lazy and just put this in my httpd.conf file. It checks that it's running on localhost - which is obviously is - then sets the variable:
SetEnvIf Server_Addr ^127\.0\.0\.1$ home_variable
Then, elsewhere, in my htaccess file I had this:
# IfVersion needs mod_version - you could do an ifmodule before all this i guess
# we use ifversion because If was introduced in apache 2.4
<IfVersion >= 2.4>
# check if the home variable exists/isn't: empty, "0", "off", "false", or "no"
<If "-T reqenv('home_variable')">
... do home-y stuff
</If>
<Else>
... do work-y stuff
</Else>
</IfVersion>
# fallback
<IfVersion < 2.4>
.. fallback?
</IfVersion>
Alternatively, you could just have one variable for each environment and give it a specific value:
SetEnvIf Server_Addr ^127\.0\.0\.1$ check_location=home
Then the rest would be the same, except that first<If ..> statement:
<If "%{ENV:check_location} == 'home'">....</If>
I've tested both of those between a home environment with 2.4 and a work environment with 2.2, both of which had mod_version enabled (hence not bothering with the ifmod).
(I also haven't tested for more than two environments but Apache 2.4 does give an <ElseIf> so that's also an option).
Doop di do.
(More about the -T operator et al: http://httpd.apache.org/docs/current/expr.html)
Apache 2.4 introduced <If>, <Else>,<ElseIf> blocks in conjunction with a new expression syntax for more powerful programmatic control in .htaccess files, etc.
This is untested but ostensibly works:
<If "%{MY_VAR} == 'my result'">
…
</If>
Source: https://blogs.apache.org/httpd/entry/new_in_httpd_2_4
I gave description and .htaccess and PHP examples here: Set an environment variable in .htaccess and retrieve it in PHP
For just some examples in .htaccess, taken from my modifying the Perishable Press 5G Blacklist/Firewall http://perishablepress.com/5g-blacklist-2013/ to use environment variable reporting:
SetEnv myServerName %{SERVER_NAME}
RewriteCond %{QUERY_STRING} (base64_encode|localhost|mosconfig|open_basedir) [NC,OR]
RewriteCond %{QUERY_STRING} (boot\.ini|echo.*kae|etc/passwd) [NC,OR]
RewriteCond %{QUERY_STRING} (GLOBALS|REQUEST)(=|\[|%) [NC]
RewriteRule .* - [E=badQueryString:%0--%1--%2,F,L]
SetEnvIfNoCase User-Agent ^$ noUserAgent
SetEnvIfNoCase User-Agent (binlar|casper|cmsworldmap|comodo|diavol|dotbot|feedfinder|flicky|ia_archiver|jakarta|kmccrew|nutch|planetwork|purebot|pycurl|skygrid|sucker|turnit|vikspider|zmeu) badUserAgent=$1
<limit GET POST PUT>
Order Allow,Deny
Allow from all
Deny from env=badUserAgent
</limit>
Notice the use of paramters. %0 gives the full string, %1 gives the match from the 1st parenthesized statement, %2 the 2nd. The hyphens are literal display characters, to visually separate (don't think is any way to put spaces in there).

Apache 2.2 redirect to SSL *then* do auth (with solution < but is it crap?)

Seems to be the place for apache so here goes :)
Age old problem: how so I redirect HTTP->HTTPS, then and only if HTTPS, do an auth?
Oh - and I'd like most of it in a single snippet that can be Include-ed in multiple <directory> or <location> blocks, so no virtual host level random path based rewrites...
Well, here's what I have that does seem to work:
In the top of a VirtualHost block
# Set ssl_off environment variable
RewriteEngine on
RewriteCond %{HTTPS} =on
RewriteRule ^ - [E=ssl]
In the location or directory block
RewriteEngine on
# Case 1 redirect port 80 SSL
RewriteCond %{HTTPS} !=on
RewriteCond %{SERVER_PORT} =80
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [R=301]
AuthType Basic
AuthBasicProvider external
AuthExternal auth_pam
AuthName "My Underpants"
AuthzUnixgroup on
Order Deny,Allow
Deny from all
Allow from env=!ssl
Satisfy any
Require group nice-users
Pluses
All of that bar the Require's can be abstracted out to a snippet file to Include in one line on each location
It fixes forcing SSL and authentication together for each location, so less chance of mistakes
Minuses
Bloody hell, it is hardly intuitive! Might be fragile for all I know...
Is there a better way (not that I've found...)?
Comments would be very welcome on whether that has any serious flaws :)
Aside
Life would be so much easier if Apache had a sensible config syntax with a generic
<If expression> </If>
block that could be used anywhere. It has certain special case blocks such as IfModule, and then you have special case conditionals like RewriteCond (which is very hard to grok if you're not used to it).
Cheers,
Tim
If you're wanting to force the entire site to https, you can use the VirtualHost directives, and then it's quite simple:
<VirtualHost *:80>
ServerName example.com
RedirectMatch (.*) https://example.com$1
</VirtualHost>
<VirtualHost *:443>
ServerName example.com
...
...
...
</VirtualHost>
Tim Watts' solution seems to work best for me but needed a bit of tweaking. Also my situation is slightly different in that I wish to allow certain IP addresses without HTTP auth but this just adds an extra line.
mod_rewrite won't inherit config from the VirtualHost by default.
See: http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html#rewriteoptions
I was going to make use of "RewriteOptions inherit" but it seems that this applies the parent rules AFTER the child ones. In any case, I thought of a different solution.
Within my SSL <VirtualHost> I have the line:
SetEnvIf Request_URI "/" using_ssl=yes
This sets the using_ssl environment variable if the request URI contains a forward slash (i.e. all the time.) It's a bit of hack as I'd prefer to use the unconditional SetEnv but apparently:
The internal environment variables set by this directive are set after most early request processing directives are run, such as access control and URI-to-filename mapping. If the environment variable you're setting is meant as input into this early phase of processing such as the RewriteRule directive, you should instead set the environment variable with SetEnvIf.
(source: http://httpd.apache.org/docs/2.2/mod/mod_env.html#setenv)
My config within my container looks like this:
# Require a basic HTTP auth user
AuthName "realm-name-goes-here"
AuthType Basic
AuthUserFile /var/www/etc/htpasswd
Require valid-user
# OR allow from non-SSL (which will be redirected due to mod_rewrite below!)
Order Allow,Deny
Allow from env=!using_ssl
# OR allow from a trusted IP range
# NB: This allows certain IPs without a username & password
Allow from 192.168.0.0/16
Satisfy Any
# Force a redirect to HTTPS
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [L,R=permanent]
You probably want to try with just 'R' instead of 'R=permanent' first for testing purposes.
Hope this is useful for others :)
I've tested your solution and it didn't work ...
After a loooong time searching the solution, googling too much and found always the same things which didn't work, I finally did this : I use SSLRequireSSL and an ErrorDocument 403 configured with a static page containing a JavaScript code (thanks to this blog page).
in /etc/apache2/conf.d.opt/redirect_if_not_https.conf :
SSLRequireSSL
ErrorDocument 403 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\
<html><head>\
<title>403 Forbidden</title>\
<script language=\"JavaScript\">\
window.location='https://'+window.location.hostname+window.location.pathname;\
</script>\
</head><body>\
<h1>Forbidden</h1>\
<p>You don't have permission to access that resource using simple HTTP. Please use HTTPS instead.</p>\
</body></html>"
(note that I created /etc/apache2/conf.d.opt/)
And in an app conf, include that file (for example in /etc/apache2/conf.d/trac.conf) :
<LocationMatch "/trac">
# All the classical configurations here
# ...
Include conf.d.opt/redirect_if_not_https.conf
</LocationMatch>