How to protect your Cloud pages

How to protect your Cloud pages

Imagine, if you will, that you have a CloudPage with a form that needs protection from any bot submissions and such. In this case, the solution is pretty easy, just put a reCAPTCHA and let it work its magic!

Not a fan of reading? Jump to the code snippet.

But what happens if you use the Form Handler technique where a customer sends data to your page from a web form?

In this case we first need to make sure no web crawler finds our page:

<meta name="ROBOTS" content="NOINDEX, NOFOLLOW">

But is it enough? Of course not! It doesn’t give a guarantee that a robot will not ignore it.

There are 2 possible solutions when it comes to securing a Cloud page in SFMC.

AMPscript + SSJS solution

At first, let’s make sure that the page only accepts data that comes from our form page URL (ex: https://myawesomeform.com) by adding some CORS rules:

%%[
    SET @origin = HTTPRequestHeader("Origin")
    SET @pattern = "^(https:\/\/(.*\.)?((myawesomeform)\.com))($|\/)"
    SET @match = RegExMatch(@origin, @pattern, 1)
]%%
<script runat=server>
    Platform.Load("core","1");
    var MATCH = Variable.GetValue("@match");
    if (!MATCH) { MATCH = null }
    HTTPHeader.SetValue("Access-Control-Allow-Methods","POST");
    HTTPHeader.SetValue("Access-Control-Allow-Origin",MATCH);
</script>

Basically, when someone will try to send data to our page from another URL, this page will display Error 500, which is exactly what we want except… this will not work in Firefox!!!

In fact, Firefox refuses to send the Origin header since the last update and therefore we will need to use the Referer header:

SET @origin = HTTPRequestHeader("Referer")

Let’s put some SSJS response headers as well, like it is advised in Landing Pages Security Best Practices

<script runat=server>
    Platform.Load("core","1");
    var MATCH = Variable.GetValue("@match");
    if (!MATCH) { MATCH = null }
    HTTPHeader.SetValue("Access-Control-Allow-Methods","POST");
    HTTPHeader.SetValue("Access-Control-Allow-Origin",MATCH);
    Platform.Response.SetResponseHeader("Strict-Transport-Security","max-age=200"); 
	Platform.Response.SetResponseHeader("X-XSS-Protection","1; mode=block"); 
	Platform.Response.SetResponseHeader("X-Frame-Options","Deny"); 
	Platform.Response.SetResponseHeader("X-Content-Type-Options","nosniff"); 
	Platform.Response.SetResponseHeader("Referrer-Policy","strict-origin-when-cross-origin"); 
	Platform.Response.SetResponseHeader("Content-Security-Policy","default-src 'self'");
</script>

And finally, let’s add a simple IF condition in our AMPscript, just for good measure:

%%[

IF LENGTH(@match) > 0 THEN

/* my ampscript code */

ENDIF 

]%%

Let’s have a look at the full code.

<!DOCTYPE html>
<html>
<head>
  <meta name="ROBOTS" content="NOINDEX, NOFOLLOW">
</head>
<body>
%%[
    SET @origin = HTTPRequestHeader("Referer")
    SET @pattern = "^(https:\/\/(.*\.)?((myawesomeform)\.com))($|\/)"
    SET @match = RegExMatch(@origin, @pattern, 1)
]%%
<script runat=server>
    Platform.Load("core","1");
    var MATCH = Variable.GetValue("@match");
    if (!MATCH) { MATCH = null }
    HTTPHeader.SetValue("Access-Control-Allow-Methods","POST");
    HTTPHeader.SetValue("Access-Control-Allow-Origin",MATCH);
    Platform.Response.SetResponseHeader("Strict-Transport-Security","max-age=200"); 
	Platform.Response.SetResponseHeader("X-XSS-Protection","1; mode=block"); 
	Platform.Response.SetResponseHeader("X-Frame-Options","Deny"); 
	Platform.Response.SetResponseHeader("X-Content-Type-Options","nosniff"); 
	Platform.Response.SetResponseHeader("Referrer-Policy","strict-origin-when-cross-origin"); 
	Platform.Response.SetResponseHeader("Content-Security-Policy","default-src 'self'"); 
</script>
%%[

IF LENGTH(@match) > 0 THEN

/* DO SOMETHING */

ENDIF 

]%%
</body>
</html>	

Server-side JavaScript solution

The server-side JavaScript solution is more elegant. It doesn’t contain any AMPscript and, thanks to the Try…Catch function, allows us to easily handle errors and exceptions.

<!DOCTYPE html>
<html>
<head>
  <meta name="ROBOTS" content="NOINDEX, NOFOLLOW">
</head>
<body>
<script runat='server'>

    Platform.Load('core', '1.1.1');
    
    try {
		
        var referer = Platform.Request.ReferrerURL;
        var regex = /^(https:\/\/(.*\.)?((myawesomeform)\.com))($|\/)/g;
        var match = referer.match(regex);
        var origin = (match.length > 0) ? match[1] : null;
    
        if (origin != null) {
    
            HTTPHeader.SetValue('Access-Control-Allow-Methods', 'POST');
            HTTPHeader.SetValue('Access-Control-Allow-Origin', origin);
            Platform.Response.SetResponseHeader('Strict-Transport-Security', 'max-age=200');
            Platform.Response.SetResponseHeader('X-XSS-Protection', '1; mode=block');
            Platform.Response.SetResponseHeader('X-Frame-Options', 'Deny');
            Platform.Response.SetResponseHeader('X-Content-Type-Options', 'nosniff');
            Platform.Response.SetResponseHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
            Platform.Response.SetResponseHeader('Content-Security-Policy', "default-src 'self'");
    
        } else {
            throw "Wrong origin";
        }
		
		/// DO SOMETHING
		
    } catch(error) {
    
        Write(Stringify({ status: "Error", message: error }));
    
    } 		
</script>
</body>
</html>	

Conclusion

In my practice, these security measures have proven to be very effective against bots that were using my Form Handler to send various fake data to Salesforce.

I hope this will help you as well!

Have I missed anything?

Please poke me with a sharp comment below or use the contact form.

  1. Of course, there is more than one way to skin a cat. Wouldn’t advise it if you have multiple domains to manage though, will make your code longer.

Leave a Reply

Your email address will not be published. Required fields are marked *