I love WordPress. I like Gravity Forms. The only trouble with Gravity forms is if you add a file upload field to your form it is stored within your /wp-content/uploads directory and as such is publicly accessible.
Now the link that Gravity Forms generates is fairly long (mywebsite.com/wp-content/uploads/gravity_forms/reallylongstringofnumbers10239812039802193/year/month/filename) however that just isn’t secure enough for me. The worst part os they don’t seem to care.
After a week or two of searching I have found a way of securing it! The following method secures the folder in question so that only Logged in users can access them. It is also probably possible to modify this method to account for different user roles etc, but for now a little security is better than none!
Ok so we need to do 2 things.
- Tell our website that all files in a certain directory need to be passed to a special file for processing before downloading.
- Use our special file to check if the user is logged in!
<?php
require_once('wp-load.php');
is_user_logged_in() || auth_redirect();
$upload_dir = wp_upload_dir();
//Set your path below I am using /gravity_forms/
$basedir = $upload_dir[ 'basedir' ] . '/gravity_forms/';
$file = rtrim( $basedir, '/' ) . '/' . str_replace( '..', '', isset( $_GET[ 'file' ] ) ? $_GET[ 'file' ] : '' );
if ( ! $basedir || ! is_file( $file ) ) {
status_header( 404 );
die( '404 — File not found.' );
}
$mime = wp_check_filetype( $file );
if( false === $mime[ 'type' ] && function_exists( 'mime_content_type' ) )
$mime[ 'type' ] = mime_content_type( $file );
if( $mime[ 'type' ] )
$mimetype = $mime[ 'type' ];
else
$mimetype = 'image/' . substr( $file, strrpos( $file, '.' ) + 1 );
header( 'Content-Type: ' . $mimetype ); // always send this
if ( false === strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS' ) )
header( 'Content-Length: ' . filesize( $file ) );
$last_modified = gmdate( 'D, d M Y H:i:s', filemtime( $file ) );
$etag = '"' . md5( $last_modified ) . '"';
header( "Last-Modified: $last_modified GMT" );
header( 'ETag: ' . $etag );
header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 100000000 ) . ' GMT' );
// Support for Conditional GET
$client_etag = isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ? stripslashes( $_SERVER['HTTP_IF_NONE_MATCH'] ) : false;
if( ! isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) )
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = false;
$client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
// If string is empty, return 0. If not, attempt to parse into a timestamp
$client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0;
// Make a timestamp for our most recent modification...
$modified_timestamp = strtotime($last_modified);
if ( ( $client_last_modified && $client_etag )
? ( ( $client_modified_timestamp >= $modified_timestamp) && ( $client_etag == $etag ) )
: ( ( $client_modified_timestamp >= $modified_timestamp) || ( $client_etag == $etag ) )
) {
status_header( 304 );
exit;
}
// If we made it this far, just serve the file
readfile( $file );
Looks kinda scary doesn’t it! Don’t worry about it! The only thing you need to pay attention to is line 8. This is where you set which folder you are protecting, for this I am using /gravity_forms/ the path is relative to your uploads directory.
Second in your .htaccess file you need to add the following:
#Gravity Forms Upload Protection
RewriteCond %{REQUEST_FILENAME} -s
RewriteRule ^wp-content/uploads/gravity_forms/(.*)$ dl-file.php?file=$1 [QSA,L]
The RewriteRule path needs to match the path you set in your dl-file.php so here you can see gravity_forms is mentioned again.
So what happens.
When a file is requested from within the Gravity Forms folder the request is passed via the .htaccess file to our dl-file.php. This file then checks to see if the user is logged in. If not logged in they are forwarded to the login page, after logging in the file is downloaded. If they don’t login. No file. Simple!
There are probably loads of improvements that can be made to this but it is a great start.
The credit goes entirely to http://wordpress.stackexchange.com/a/37743/12438 I simply took the code and made it work for me! Let me know what improvements you make to it!
20 thoughts on “Secure your Gravity Forms uploads”
I just tried this and it did not work. I was still able to access uploaded documents even when not logged in. No idea why. Is there any specific place within .htaccess where the 3 lines of text should go?
No but you do need to ensure that mod_rewrite is enabled and that the path to your uploads is set correctly in both files.
Not sure I understand… the path in line 6 of dl-file.php? You have it as ‘/gravity_forms/’
Do I need the full path, ralative to the wordpress root?
Sorry, I meant relative, not ralative.
The path is relative to your WordPress uploads folder so in my example wp-content/uploads/gravity_forms.
You only need to change it if you are using a directory named something different to gravity_forms.
I appreciate the help, thanks. It still did not work for me, but I have a feeling I have some deeper issues that need to be fixed. If your method works for 90% of users, you have a great idea here!
This is a great post and has been very useful to me. Thanks. One thing worth pointing out though is that if you use the Really Simple Captcha integration with Gravity Forms then it stores the generated png in the gravity_forms directory, as this script stands it will not be displayed on the front end.
I modified it slightly so there is a check up front for the filetype to see if it’s a .png and then just serving the file if it is. ie;
require_once('wp-load.php'); $upload_dir = wp_upload_dir(); //Set your path below I am using /gravity_forms/ $basedir = $upload_dir[ 'basedir' ] . '/gravity_forms/'; $file = rtrim( $basedir, '/' ) . '/' . str_replace( '..', '', isset( $_GET[ 'file' ] ) ? $_GET[ 'file' ] : '' ); $mime = wp_check_filetype( $file ); if ($mime[ 'ext' ] == 'png') { // Its the captcha , we just serve the file with a basic header header('Content-type: image/jpeg'); readfile($file); die(); } is_user_logged_in() || auth_redirect();Hope this helps, cheers.
Thanks for your comment, I’ve also fixed your typo as you mentioned in your other comment.
Thanks for putting this together! Saved me lots of time searching the web! Noticed that the copy on github is giving an error because it is missing:
require_once('wp-load.php');I also modified it to only allow access to the gravity_forms folder if the user is an admin by replacing:
is_user_logged_in() || auth_redirect();to include the user role:
( (is_user_logged_in() ) && (current_user_can('administrator')) ) || auth_redirect();Thanks for letting me know I’ve updated the Gist.
I have uploaded the dl-file.php to the root of the wp installation, and made the following alterations (see below) to my .htaccess file – but when I navigate to the uploaded file in the gravity forms directory I don’t get redirected. Instead, my browsers just show the text from the the dl-file.php in a paragraph. I have messed around with this for hours, please tell me I’m overlooking something really simple here? Thanks, Owain
#Gravity Forms Upload Protection RewriteCond %{REQUEST_FILENAME} -s RewriteRule ^wp-content/uploads/gravity_forms/(.*)$ dl-file.php?file=$1 [QSA,L] # Use PHP5 Single php.ini as default AddHandler application/x-httpd-php5s .php # BEGIN WordPress RewriteEngine On RewriteBase / RewriteRule ^index.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] # END WordPress AuthName "topsete1" AuthUserFile "/home2/topsete1/.htpasswds/public_html/passwd"Nothing I can see there although I did notice that in my code example there was a closing peer tag at the start for some reason (probably from when I changed syntax highlighters). Do you have that in your code if so remove it. And make sure you have a opening php tag at the start of the file as well!
Please excuse how silly this is going to sound, but what does a closing peer tag look like? Do you mean ‘?>’?
In the line below, is the closing tag the ‘;’ ?
Thanks for your help!
Sorry for the confusion I made my reply from my iPad which auto-corrected everything I said.
Ok there were 2 issues with my code example.
1) On line 1 there was a
tag which shouldn’t have been there.
2) I didn’t including the opening
tags as I hoped everyone would insert that themselves.
I’ve fixed the code example above and have also put the code on GitHub so you can simply get the whole thing. Let me know if you get this working!
GitHub Gist
Works great for me! Do you think this could made into a Gravity Forms add-on?
Possibly but as it needs to write to the .htaccess file to make it work I’m not sure how easy it is do do this from a plugin.
Hey man,
I’ve used Super Cache in the past and it def. does write to the .htaccess file. I’m way more of a theme author than plugin developer, so I have no idea how it is actually accomplished. But I have seen it happen.
Also, the Retina @2x plugin writes to the .htaccess file as well.
And of course, thanks for the code!
Brilliant solution, I’m trying it out at this very moment. Thanks for sharing it!
Thanks, I will give this a try.