Use PHP/Apache to restrict access to images - php

Use PHP/Apache to restrict access to images
First, I know this is NOT a new topic, or even a duplicate that was discussed, but I want to propose a new aproach, that I don't know how to implement:
My idea is the following:
APACHE should deliver the file not PHP. This is highly critical
PHP should just do the authentication and re-send/forward the request with some trick/variable set)
the same-request is being re-sent and this second time the Rewrite Conditions should fail(due to the trick) and the file should be directly delivered.
Steps:
request is coming in(URL = http://somedomain.com/files/image1.png), apache forwards it to access.php
access.php checks that the user has access to the file
IF check is successful, then PHP sets an environment variable AUTHORIZED = true and makes an internal re-direct to the original URL.
Same request is coming in (SECOND time - this time with AUTHORIZED set) -> RewriteCond will NOT match(RewriteCond %{ENV:AUTHORIZED} !^$), and the file will be delivered.
I am not sure how to make a proper link between step 3 and 4. Not sure how that would be possible.
I am not sure what kind of mechanisms are available from PHP to set an apache environment variable, or something similar.
Any input would be appreciated.
Code samples:
.htaccess
RewriteEngine on
RewriteCond %{ENV:AUTHORIZED} !^$
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ access.php [L]
access.php
if ( 1 == 1 /* check if user is authorized*/ ) {
// set an environment variable for apache!!!
apache_setenv( 'AUTHORIZED', "true" ); //not working!
//forward the request
header( "Location:" . $_SERVER[ 'REQUEST_URI' ] );
exit;
} else {
header("HTTP/1.1 401 Unauthorized");
exit;
}
Some references:
https://serverfault.com/questions/374955/protecting-images-from-direct-access-by-checking-current-php-session-with-mod-re
Using PHP/Apache to restrict access to static files (html, css, img, etc)

I am not sure how to make a proper link between step 3 and 4. Not sure how that would be possible.
It isn't, at least not in practical terms. HTTP is stateless.
I'm not too familiar with the Apache life cycle, but any environment variable would last either:
Only for the request, in which case it wouldn't exist when the client made another request in response to the redirect
Globally, in which case every request (from every client) would be given access
In theory you could authorise an IP address (but IP addresses are shared so you'd be giving access to anybody who shared the IP … such as everyone in the same building as the client in many cases). So, that wouldn't be secure.
You could set a cookie and then have another mechanism check for that cookie … but then you'd either need to repeat the authentication or have a shared cookie that anyone could copy. So, that wouldn't be secure.
If you were working in C or Perl (mod_perl) then you could write an Apache module to hook in to the authentication/authorization part of the request cycle. But you're working in PHP and, as far as I know, nobody has ever written anything to let PHP play with Apache's internals (well, unless you count PHP.pm, but I've no idea how neatly you could tie that in with mod_perl).

Related

Intercept Angular URL with PHP first before loading webpage

We currently have a full Angular project running in a sub-dir on our server and a physical "device" using a hardcoded URL to send a user to that page.
I'm looking for some kind of way to "intercept" the request via a PHP script first to (for example, not the real purpose) see if the requested "ID" param for that browser page has enabled the option to view the browser page or if it has been configured by the user to return a 406 HTTP response (for example).
Currently:
- ..com/app/routing-view?id=1234 => Angular view -> fetch info
Idea:
- ..com/app/routing-view?id=1234 => PHP-script -> isValid => forward to angular and do a normal 'webview' -> fetch info
- ..com/app/routing-view?id=2889 => PHP-script -> notValid => HTTP code
I thought about having a .htaccess "intercept" the url and forward it to a .php file. Do the magic and checks there, and then forward the browser to the original url, but to "bypass" the previous interceptor.
It's about that last part that I'm currently having issues. Because it's Angular and it is using paths, I can't just say "okay, redirect to index.html instead of index.php" because it would need to be something like ..com/app/routing-view?id=1234 (and the index.html is located in the /app directory.
I don't want to add PHP code to the original Angular-index file if that could be avoided.
Am I looking at this right or would there be a different, more efficient way to tackle this?
The reason for all this is that I want to (for example) return a different HTTP code or different headers to the device instead of a 200 html header response even if the ID turned out to be disabled or something like that.
Got it to work via a simple .htaccess, a parameter and an "interceptor" script. Quite simple and basic really, not sure why my brain didn't go this route before posting my question.
Added the .htaccess to the root of the Angular application with the following code.
RewriteEngine On
# Check if the request has been intercepted before (via URL parameter)
RewriteCond %{QUERY_STRING} !(^|&)intercepted=true
# .. If not, rewrite the page to use the interceptor script (only if it matches the route).
RewriteRule ^my-route dir-of-script/extra/interceptor.php?url=https://%{HTTP_HOST}%{REQUEST_URI}?%{QUERY_STRING} [L,QSA]
# Other stuff for rewriting the Angular routes etc
...
Added a /dir-of-script/extra/interceptor.php like-script that parses the URL from the GET parameter, fetches the info, does the checks and depending on the result, returns the output or redirects the page and let it through.
This adds a ..&intercepted=true parameter to the original URL so that the .htaccess doesn't intercept it again.
This seems to be working like a charm (for my situation). The only "downside" is that this counts as a redirect and not just a rewrite when it was allowed to go through. Will look further into it, to maybe let the PHP script "serve" the Angular content instead of redirecting to it.

$_GET value from HTACCESS url

Goes without saying, I know completely nothing about HTACCESS. I'm coding a referral system in PHP, and it all works, although it stops working when I use HTACCESS to make the URLs pretty. Example:
From
http://localhost/ref.php?referral=1
To
http://localhost/ref/1
Now the code works and all, it just stops working when I use HTACCESS, I remember reading from somewhere, if I'm not wrong, that you can't use the _GET function from a URL which has passed through HTACCESS? None the less, I've tried this HTACCESS rule, but it doesn't seem to work.. It redirects, as per the code it told to do when it drops the cookie, but it's not actually dropping the cookie when I use the shortened URL.
RewriteRule ^ref/([^/]+)(|/)$ /ref.php?referral=$1
The code of the script, if it helps understand the issue..
if (isset($_GET['referral']))
{
$value = $_GET['referral']; // Let's set the cookie value
// Drop the cookie, let it expire in an hour.
setcookie("HHRefCookie", $value, time()+3600);
// Redir them to register page
header("Location: " . WWW . "/register");
}
From my understanding, this should be working? Should it not. Feedback would be appreciated. And guidance towards the right direction would be much appreciated!
Did you look at the status code returned by the server? I can't see how the rewrite rule you've provided would match the URL schema you are using (i.e. I'd expect the server to return a 404).
Try:
RewriteRule ^/ref/(.*)$ /ref.php?referral=$1 [L]
(and if this is Apache 2.4 then use [END] rather than [L])

How do I allow only certain IP's to view webpages?

I'm trying to get a PHP script to work where it compares the IP of the person against a text file(one IP per line). If the person's IP is not in the text file, then he gets redirected to declined.html. I know I could use .htaccess for this but the IP list could get really, really long.
This is the code that I have so far:
<?php
$ipArray = file('ip.txt');
unset($allowed);
foreach ($ipArray as $ipTest) if (substr_count($_SERVER['REMOTE_ADDR'],trim($ipTest)) != "0") $allowed = true;
if ($allowed != true) {
header('location: /declined.html'); // the banned display page
die();
}
?>
I want to be able to call this script in every page that I only want certain IP's to see, this is what I'm trying to call it with:
<? include('ip_allow.php'); ?>
When I call the PHP script in the HTML page when my IP is NOT in ip.txt, it does not redirect me to /declined.html! How can I fix this? Again, my question is not how to use .htaccess, but how to fix my script! Thanks.
For IP filtering, it's best to do it as early as possible in the processing chain. In order of preference:
router
firewall
webserver
script
Most likely you don't have access to the router or firewall levels, but you CAN use Apache's mod_rewrite to have a dynamic block using an external file. Set up your "enabled IPs" file as follows:
a.b.c.d ALLOWED
b.c.d.e ALLOWED
c.d.e.f ALLOWED
etc...
It's basically "key value", where the key is the IP address
Then your mod_rewrite rules (I've numbered them for reference)
1. RerwriteMap ipfiltermap txt:/path/to/your/ips/list
2. RewriteCond %{REMOTE_ADDR} (.*)
3. RewriteCond %{ipfiltermap:%1} !ALLOWED
4. RewriteCond %{REQUEST_URI} !^/declined.html
5. RewriteRule .* /forbidden.html [L]
They work as follows:
Defines the allowed IPs as a "key value" pair, where the keys are the allowed IPs, and the value is the word "ALLOWED", as a mapping named "ipfiltermap"
Captures the remote address of the current request (the client IP)
Using the IP captured in step 2, look it up in the ipfiltermap, and see if the the IP does NOT have the word 'ALLOWED'
Specifically exempt your 'declined' page from the IP enforcement - if this wasn't here, disabllowed IPs would enter an infinite redirect loop
If all 3 of the RewriteConds match, then the user is from a forbidden IP, and should be redirected to the fobidden page.
Relevant Apache docs for all this are here.
If you place your configuration in your Apache configuration directly, it won't incur the speed penalties of .htaccess lookups -- but that would mean you'd need to reload the Apache configuration whenever the list is modified. (Though Marc's answer avoids this very nicely.)
The mod_authz_host.c is optimized for moderately fast searching. If you're reading a text file in your script every execution, you're already several times slower than Apache. Apache reads the configuration once, converts the IP addresses to a binary format once, and can then use very fast integer arithmetic to determine if hosts are allowed or not.
Furthermore, it's already debugged and working. It'd take you less time to deploy it than it would to find the bug in your current code -- and even then, your current code would -- on every access-controlled request -- re-read (and re-parse into an array) the textual description of IP addresses, convert the IP address from the remote peer into a text version, and then perform a very slow text-based comparison over the entire array.
If speed is of real importance, then you should investigate doing the access control via your system firewall. iptables has optimized routines to find matching IP addresses among a list of allowed or denied hosts and won't waste any time performing any protocol analysis. Of course, this is a much heavier all-or-nothing approach that would require an annoying separation of content among listening ports if some content is available for all.
I would guess you have files in the wrong directory, or PHP not working or some other more basic configuration issue, since I tested your code and it works fine for me.
The file() command includes the \n character at the end of each line, so each line is actually something like 0.0.0.0\n which is returning false every time.
Use this:
$ipArray = file('ip.txt', FILE_IGNORE_NEW_LINES); // Try specifying an ABSOLUTE path to this file.
$allowed = false; // Rather than unset($allowed), this will prevent notice errors too!
foreach ($ipArray as $ipTest) if ($_SERVER['REMOTE_ADDR'] == $ipTest) $allowed = true;
Also just to point out in your header line, Location should start with a capital letter and you should specify a full URI to the file:
header('Location: http://example.com/declined.html');

URL handling – PHP vs Apache Rewrite

Currently I let a single PHP script handle all incoming URLs. This PHP script then parses the URL and loads the specific handler for that URL. Like this:
if(URI === "/")
{
require_once("root.php");
}
else if(URI === "/shop")
{
require_once("shop.php");
}
else if(URI === "/contact")
{
require_once("contact.php");
}
...
else
{
require_once("404.php");
}
Now, I keep thinking that this is actually highly inefficient and is going to need a lot of unnecessary processing power once my site is being visited more often. So I thought, why not do it within Apache with mod_rewrite and let Apache directly load the PHP script:
RewriteRule ^$ root.php [L]
RewriteRule ^shop$ shop.php [L]
...
However, because I have a lot of those URLs, I only want to make the change if it really is worth it.
So, here's my question: What option is better (efficiency-wise and otherwise) and why?
Btw, I absolutely want to keep the URL scheme and not simply let the scripts accessible via their actual file name (something.php).
So, here's my question: What option is better (efficiency-wise and otherwise) and why?
If every resource has to run through a PHP based check, as you say in your comment:
some resources are only available to logged in users, so I get to check cookies and login state first, then I serve them with readfile().
then you can indeed use PHP-side logic to handle things: A PHP instance is going to be started anyway, which renders the performance improvement of parsing URLs in Apache largely moot.
If you have static resources that do not need any session or other PHP-side check, you should absolutely handle the routing in the .htaccess file if possible, because you avoid starting a separate PHP process for every resource. But in your case, that won't apply.
Some ideas to increase performance:
consider whether really every resource needs to be protected through PHP-based authentication. Can style sheets or some images not be public, saving the performance-intensive PHP process?
try minifying resources into as few files as possible, e.g. by minifying all style sheets into one, and using CSS sprites to reduce the number of images.
I've heard that nginx is better prepared to handle this specific kind of scenario - at least I'm told it can very efficiently handle the delivery of a file after the authentication check has been done, instead of having to rely on PHP's readfile().
The PHP approach is correct but it could use a bit of improvement.
$file = $uri.".php";
if (!is_file($file)) { header("Status: 404 Not Found"); require_once(404.php); die(); }
require_once($uri.".php");
OK, as for efficiency - htaccess version with regexp and php version with single regexp and loading of matching file would be faster than many htaccess rules or many php if - else
Apart from that, htaccess and php way should be similar in efficiency in that case, probably with little gain with htaccess (eliminating one require in php)
RewriteRule ^([a-z]+)$ $1.php [L]
and rename root.php to index.php.

Is there a way to exclude domain from link generation in wordpress

I have a website that responds on *.domain.com.
Going to x.domain.com or y.domain.com should produce the same web page.
What * is I do not know, but it is and important piece of info since we track things based on it.
When moving to wordpress we ran into a pretty severe problem. It seems to generate links (using get_page_link) with the domain which is set in the admin.
This will not work for us because we can't find a way to tell wordpress to generate links without the domain (why does it do this anyway?!) and every time a link is clicked the browser goes from: x.domain.com to domain.com (since domain.com is what we have in the admin).
Unfortunately WordPress is architected such that it is really hard to get rid of the domain component of URLs. But all is not lost! Read on as the answer to your question requires a bit of background.
The WordPress team made the decision to require the user of a site to hardcode the site domain either in the database via an admin console, which you can see in the following screen shot, of via PHP which we'll discuss below:
You might ask what the difference is between the the two URLs? Even I find it confusing because I almost never need anything other them to have them both set to the root URL and as it's not important for your question I'll just gloss over that detail. If you are interested though you can learn more here:
Changing The Site URL
Editing wp-config.php: WordPress Address (URL)
Giving WordPress Its Own Directory
Moving along, the other option is to hardcode the two PHP constants WP_SITEURL and WP_HOME in the /wp-config.php file which can be found in the root of a WordPress installation. Those two lines might look like this in your /wp-config.php file:
define('WP_HOST','http://domain.com');
define('WP_SITEURL','http://domain.com');
The good news is that you can define both of them dynamically based on the current domain your site is serving from (I'm going to assume you have both your DNS server and your Apache web server configured for wildcard DNS.) You can use the following code to match any subdomain name consisting of letters and numbers:
$root_domain = 'domain.com'; // Be sure to set this to your 2nd level domain!!!
$this_domain = $_SERVER['SERVER_NAME'];
if (!preg_match("#^([a-zA-Z0-9]+\.)?{$root_domain}$#",$this_domain)) {
echo "ERROR: The domain [$this_domain] is not a valid domain for this website.";
die();
} else {
define('WP_HOME',"http://{$this_domain}");
define('WP_SITEURL',"http://{$this_domain}");
}
The bad news is you may have some "artifacts" to deal with after you get it working such as how URLs are handled for image URLs stored in the database content (which may or may not end up being a problem) or for Google Maps API keys, etc. If you have trouble with them let me suggest you post another question here or even better at the new WordPress Answers Exchange also run by the same people as StackOverflow.
As for telling WordPress how to generate links, there are filters you can "hook" but in my quick testing I don't think you need it because WordPress will generate the links for whatever domain happens to be your current domain. Still if you do find you need them you can do it although be prepared to be overwhelmed by all the add_filter() statements required! Each one controls one of the different ways links can be generated in WordPress.
Here is the hook filter function and the 40+ add_filter() calls; you might not need them all but if you do here they are:
function multi_subdomain_permalink($permalink){
$root_domain = 'domain.com';
$this_domain = $_SERVER['SERVER_NAME'];
if (preg_match("#^([a-zA-Z0-9]+)\.?{$root_domain}$#",$this_domain,$match)) {
$permalink = str_replace("http://{$match[1]}.",'http://',$permalink);
}
return $permalink;
}
add_filter('page_link','multi_subdomain_permalink');
add_filter('post_link','multi_subdomain_permalink');
add_filter('term_link','multi_subdomain_permalink');
add_filter('tag_link','multi_subdomain_permalink');
add_filter('category_link','multi_subdomain_permalink');
add_filter('post_type_link','multi_subdomain_permalink');
add_filter('attachment_link','multi_subdomain_permalink');
add_filter('year_link','multi_subdomain_permalink');
add_filter('month_link','multi_subdomain_permalink');
add_filter('day_link','multi_subdomain_permalink');
add_filter('search_link','multi_subdomain_permalink');
add_filter('feed_link','multi_subdomain_permalink');
add_filter('post_comments_feed_link','multi_subdomain_permalink');
add_filter('author_feed_link','multi_subdomain_permalink');
add_filter('category_feed_link','multi_subdomain_permalink');
add_filter('taxonomy_feed_link','multi_subdomain_permalink');
add_filter('search_feed_link','multi_subdomain_permalink');
add_filter('get_edit_tag_link','multi_subdomain_permalink');
add_filter('get_edit_post_link','multi_subdomain_permalink');
add_filter('get_delete_post_link','multi_subdomain_permalink');
add_filter('get_edit_comment_link','multi_subdomain_permalink');
add_filter('get_edit_bookmark_link','multi_subdomain_permalink');
add_filter('index_rel_link','multi_subdomain_permalink');
add_filter('parent_post_rel_link','multi_subdomain_permalink');
add_filter('previous_post_rel_link','multi_subdomain_permalink');
add_filter('next_post_rel_link','multi_subdomain_permalink');
add_filter('start_post_rel_link','multi_subdomain_permalink');
add_filter('end_post_rel_link','multi_subdomain_permalink');
add_filter('previous_post_link','multi_subdomain_permalink');
add_filter('next_post_link','multi_subdomain_permalink');
add_filter('get_pagenum_link','multi_subdomain_permalink');
add_filter('get_comments_pagenum_link','multi_subdomain_permalink');
add_filter('shortcut_link','multi_subdomain_permalink');
add_filter('get_shortlink','multi_subdomain_permalink');
add_filter('home_url','multi_subdomain_permalink');
add_filter('site_url','multi_subdomain_permalink');
add_filter('admin_url','multi_subdomain_permalink');
add_filter('includes_url','multi_subdomain_permalink');
add_filter('content_url','multi_subdomain_permalink');
add_filter('plugins_url','multi_subdomain_permalink');
add_filter('network_site_url','multi_subdomain_permalink');
add_filter('network_home_url','multi_subdomain_permalink');
add_filter('network_admin_url','multi_subdomain_permalink');
While brings us to the final point. There is functionality in WordPress that attempts to ensure every URL that is loaded is served via its canonical URL which in general is a web best practice, especially if you are concerned with optimizing search engine results on Google and other search engines. In your case, however, if you really do not want WordPress to redirect to your canonical URL then you need to add a redirect_canonical filter hook and tell WordPress not to do it.
What follows is the code to make sure any page that serves as "x.domain.com" stays on "x.domain.com" even if all the URLs are filtered to be "domain.com". That may not be the exact logic you need but I'm just showing you the building blocks of WordPress so you'll be able to figure out the logic that you require.
A few final details about this function call; parameters #3 and #4 refer respectively to the priority (10 is standard priority so this hook will not be handled special) and the number of function arguments (the 2 arguments are $redirect_url and $requested_url.) The other thing to note is that returning false instead of a valid URL cancels the canonical redirect:
add_filter('redirect_canonical','multi_subdomain_redirect_canonical',10,2);
function multi_subdomain_redirect_canonical($redirect_url,$requested_url){
$redirect = parse_url($redirect_url);
$requested = parse_url($requested_url);
// If the path+query is the same for both URLs, Requested and Redirect, and
if ($redirect['path']+$redirect['query']==$requested['path']+$requested['query']) {
// If Requested URL is a subdomain of the Redirect URL
if (preg_match("#^([a-zA-Z0-9]+).{$redirect['host']}$#",$requested['host'])) {
$redirect_url = false; // Then cancel the redirect
}
}
return $redirect_url;
}
That's about it. Hope this helps.
-Mike
do you have any control over your hosting? Maybe you can use the rewrite module in apache, if you are using apache.
In httpd.conf add:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^x\.domain\.com
RewriteRule ^(.*)$ http://www.domain.com/x/$1
RewriteCond %{HTTP_HOST} ^y\.domain\.com
RewriteRule ^(.*)$ http://www.domain.com/y/$1
You can change the way you pass the variable "v" too
RewriteRule ^(.*)$ http://www.domain.com/$1&var=v
I didn't try the code, but i'm pretty sure that at least it will open your mind to a new way to deal with this issue --one that doesn't involve so much coding.
Cheers.
A.

Categories