Protecting uploaded files with cbsecurity rules

I’m posting quite often, so I apologize for taking up so much airspace, but I think this is a solid question others can benefit from.

On my app, users have the ability to upload files. These files are then shown to other users in the app that have the appropriate authorization contexts and permissions to view them. I’ve made sure that accessing the pages containing these files is secured by checking the permissions of the current user within the event handler.

However, I understand that people can just type the literal path to a file they want to access on my app and then it pops up right in front of them, no resistance at all. This is a problem for when we want to secure important files from public viewing!

A prime example:

I understood that cbsecurity has the ability to protect URLs, so I read the documentation and tried it out. The following is the cbsecurity key in my coldbox.cfc’s module settings:

cbSecurity : {
				// The global invalid authentication event or URI or URL to go if an invalid authentication occurs
				"invalidAuthenticationEvent"    : "login/new?msg=invalidAuthentication",
				// Default Authentication Action: override or redirect when a user has not logged in
				"defaultAuthenticationAction"    : "redirect",
				// The global invalid authorization event or URI or URL to go if an invalid authorization occurs
				"invalidAuthorizationEvent"        : "/?msg=invalidAuthorization",//"#event.buildLink(to="main/index",queryString="msg=invalidAuth")#",
				// Default Authorization Action: override or redirect when a user does not have enough permissions to access something
				"defaultAuthorizationAction"    : "redirect",
				// You can define your security rules here or externally via a source
				"rules"                            : [
					{
						"whitelist": "",
						"securelist": "includes/fileUploads/canvasSubmissions,includes/fileUploads/canvasSubmissions.*,includes/fileUploads/submissions,includes/fileUploads/submissions.*",
						"match": "url",
						"roles": "user",
						"permissions": "",
						"redirect": "login.new",
						"useSSL": false
					}
				],
				// The validator is an object that will validate rules and annotations and provide feedback on either authentication or authorization issues.
				"validator"                        : "CBAuthValidator@cbsecurity",
				// The WireBox ID of the authentication service to use in cbSecurity which must adhere to the cbsecurity.interfaces.IAuthService interface.
				"authenticationService"          : "authenticationService@cbauth",
				// WireBox ID of the user service to use
				"userService"                     : "UserService",
				// The name of the variable to use to store an authenticated user in prc scope if using a validator that supports it.
				"prcUserVariable"                 : "oCurrentUser",
				// If source is model, the wirebox Id to use for retrieving the rules
				"rulesModel"                    : "",
				// If source is model, then the name of the method to get the rules, we default to `getSecurityRules`
				"rulesModelMethod"                : "getSecurityRules",
				// If source is db then the datasource name to use
				"rulesDSN"                        : "",
				// If source is db then the table to get the rules from
				"rulesTable"                    : "",
				// If source is db then the ordering of the select
				"rulesOrderBy"                    : "",
				// If source is db then you can have your custom select SQL
				"rulesSql"                         : "",
				// Use regular expression matching on the rule match types
				"useRegex"                         : true,
				// Force SSL for all relocations
				"useSSL"                        : false,
				// Auto load the global security firewall
				"autoLoadFirewall"                : true,
				// Activate handler/action based annotation security
				"handlerAnnotationSecurity"        : true,
				// Activate security rule visualizer, defaults to false by default
				"enableSecurityVisualizer"        : true,
				// JWT Settings
				"jwt" : {
					// The issuer authority for the tokens, placed in the `iss` claim
					"issuer"                     : "",
					// The jwt secret encoding key to use. This key is only effective within the `config/Coldbox.cfc`. Specifying within a module does nothing.
					"secretKey"                  : getSystemSetting( "JWT_SECRET", "" ),
					// by default it uses the authorization bearer header, but you can also pass a custom one as well or as an rc variable.
					"customAuthHeader"           : "x-auth-token",
					// The expiration in minutes for the jwt tokens
					"expiration"                 : 60,
					// If true, enables refresh tokens, token creation methods will return a struct instead
					// of just the access token. e.g. { access_token: "", refresh_token : "" }
					"enableRefreshTokens"        : false,
					// The default expiration for refresh tokens, defaults to 30 days
					"refreshExpiration"          : 10080,
					// The Custom header to inspect for refresh tokens
					"customRefreshHeader"        : "x-refresh-token",
					// If enabled, the JWT validator will inspect the request for refresh tokens and expired access tokens
					// It will then automatically refresh them for you and return them back as
					// response headers in the same request according to the customRefreshHeader and customAuthHeader
					"enableAutoRefreshValidator" : false,
					// Enable the POST > /cbsecurity/refreshtoken API endpoint
					"enableRefreshEndpoint"      : true,
					// encryption algorithm to use, valid algorithms are: HS256, HS384, and HS512
					"algorithm"                  : "HS512",
					// Which claims neds to be present on the jwt token or `TokenInvalidException` upon verification and decoding
					"requiredClaims"             : [],
					// The token storage settings
					"tokenStorage"               : {
							// enable or not, default is true
							"enabled"    : true,
							// A cache key prefix to use when storing the tokens
							"keyPrefix"  : "cbjwt_",
							// The driver to use: db, cachebox or a WireBox ID
							"driver"     : "cachebox",
							// Driver specific properties
							"properties" : { "cacheName" : "default" }
					}
				}
			},

This having been setup, my app is still allowing signed-out users to directly access files contained within my includes/fileUploads directory. Interestingly enough though, the desired behavior occurs (redirecting to the login screen) when I try to access a file in one of the secured directories (includes/fileUploads/canvasSubmissions, includes/fileUploads/submissions) that does not exist!

I suppose I am part of the way there with getting my security behavior to work correctly. My question is, how do I extend the security to the existent files and not just the non-existent ones?

I believe you’re confusing CF-land with server (or servlet)-land.

CF (Lucee or ACF) is only responsible for executing .cfm and .cfc files, by default. Your server (like Apache, Nginx, IIS, etc.) serves static (and other) files in some other manner. Since .png is not a CFML-ish extension, and you don’t have any rewrites to pass that URL or filepath to a CFML page for rendering, there’s nothing CFML can do here.

So this isn’t a CBSecurity problem, or a ColdBox one, or even a CFML one. You need to look into how your server serves those files… and decide on a path to either 1) securing them via the server, or 2) passing the file path to a CFML page and using CFML to serve the files after running cbSecurity’s access rules.

Note that serving images through CFML should not be done lightly. I’m thinking you’d see a dramatic decrease in image download speed, an increase in cfml/java memory usage, and an overall decrease in other page performance. (Because page rendering now has to compete with image serving for the same amount of RAM allocated to the JVM.)

With that said… I’m not sure how you’d lock down static assets without serving them through CFML. My dumb-enough-it-might-work approach may be to upload the files to S3, and use a unique-enough URL for “security through obscurity”. Depends on your needs and how sensitive these cat pics are.

For further reading, I would work through CommandBox’s “Server Rules” documentation. That should be a good start:

1 Like

Also: obligatory I-Don’t-Actually-Know-What-I’m-Talking-About, so Take-This-With-A-Grain-Of-Salt. :smile:

If you want legit advice from a legit developer who’s worked with CommandBox security rules and/or serving static files from S3, I’m sure Brad will chime in at some point. :brain: :rocket:

1 Like

Michael is on the money- you can’t use CF code to protect a static file because your web server will serve that up directly.

Two options I recommend:

  • Store the files outside of the web root use a CF route to serve the file with <cfcontent>, but not until after checking the logged in user. This easy and portable, but also taxes your CF server to serve potentially large files when it could be doing something better.
  • Put your files in an S3 bucket which is private and generate pre-signed URLs for authenticated users. This is a bit more work, but moves all the heavy lifting of storage and bandwidth to a CDN and is still 100% secure.

We servers like CommandBox, or IIS also have the ability to add stuff like basic auth to a directory, but that probably doesn’t work for most circumstances since you want more complex logic inside your application to decide who has access.

2 Likes

Thanks, Brad.

As my employer doesn’t actively use Amazon, I’ve decided to go the route to using <cfcontent> to serve my files from a directory outside of the webroot.

On my page I need to display files, where there’s a potential that more than one file needs to be displayed at a time, I tried using a <cfcontent> tag. It did what the documentation described and made the contents of the file display as the only thing on the page.

So now that I know that I can access secured files outside of the webroot on the web app, my last requirement may touch on part of what you said:

How would I be able to generate URLs for these files as to use them inside of <img> tags and <a> tags?

site.com/secretFileServer.cfm?fileToServe=path/to/secretFile.jpg

That .cfm becomes a proxy to check the logged in user and serve up whatever is requested. Just be VERY cautious of RFI exploits by validating the incoming path and not allowing stuff like ../

1 Like

Hi guys,

Just finished up solving this issue this morning and I wanted to recount how I went about securing my files for any readers that may be solving the same problem.

So I did as Brad said and I made a handler secretFileServer.cfc that on being hit, checks to see if a user is logged in and checks to see if the file that is requested to be served is defined within the query string.

Once these things are verified, we remove all instances of “../”, “..\”, “/”, “\”, “..” from the file name requested in the query string. This is to protect against RFI and directory traversal attacks.

Next, we append the filename to the directory from which the files are served and we set the view to a small .cfm file which looks like this:

<cfoutput>

    <!--- Secret file server --->
    <cfcontent type="application/octet-stream" file="#rc.file#">

</cfoutput>

Then whenever we want to use an image or file URL in any of our HTML pages, we just use event.buildLink(to="secretFileServer.index", queryString="file=fileNameHere.jpg")

Works just fine! Thanks for the help guys.

3 Likes