Active content filtering (ACF) examples
The active content filtering (ACF) component removes potentially malicious active content from application content that is displayed in a browser. This article explores situations in which active content can be leveraged to prevent malicious content from running on the client.
Simple ACF: Hello world
A Hello world program is a common starting point for users who are familiarizing themselves with
a new programming language or application server. Using the Hello world program as a starting point, this article demonstrates how active content can be injected into even the simplest of applications.
Hello world example
The Hello world script used for this demonstration is written in Groovy. The code has two components, each
rendering different content based upon the HTTP method of the request. When the client sends a
POST request to the server, the /public/helloworld.gt script gets the request parameter name and prints out
Hello <name>. For all other HTTP methods, the form is presented to the user.
The user can then type in their name and click on the submit button. The following sample shows the coding for
the Hello world script:
<html>
<head>
<title>ACF Hello World!</title>
</head>
<body bgcolor='lightyellow'>
<% if(request.method[] == "POST" ){ %>
Hello <b><%= request.params.name[] %></b>
<%} else { %>
<form action='' method='POST'>
Name : <input type='text'name='name'><br/>
<input type='submit' value='submit'>
</form>
</body>
<% } %>
</html>
Hello world: Good user
When the script has been written, you can see what happens in the typical usage scenario for this application. To see this, open the browser and request /helloworld.gt. This results in the rendering of the
form as shown in the following example:
When the user enters in their name and clicks enter, the Hello <name> greeting displays, as shown
in the following example:
Hello world: Bad user
In this example, active content is introduced to this scenario. In the previous good
user scenario, the user entered their name and clicked submit. In the active content scenario, instead of typing a name,
the user types the following string:
<script<alert('ACF attack');</script>
This is shown in the following example:
When the user types in the active content text and clicks enter, instead of the greeting shown in the first example, the active content is run as shown in the following example:
Preventing the bad user scenario
You can prevent the scenario in which a user enters active content by including the zero.acf dependency as part of your application. By default,
ACF filters active content from request parameters (such as the name field in the form) prior to the application gaining access to
the request parameters.
Simple ACF: Blog
The blog component is a common requirement when building Web 2.0 applications. Using a simple blog application as
a starting point, you can see how active content can be injected into a blogger application in the following blog example.
Blog example
The blog scripts used for this demonstration are written in Groovy. The code has the following components:
- A blog (and a form that allows users to comment on the blog posting)
- JavaScript® that leverages Dojo
-
/app/resources/comment.groovy
The first two components
, the blog and JavaScript, are part of the /public/blog.gt script.
The following sample shows the coding for
the /public/blog.gt script:
<html>
<head><title></title>
<script type="text/javascript"
src='<%= getRelativeUri("/dojo/dojo.js")%>'
djConfig="isDebug:false, usePlainJson:true, parseOnLoad: true"></script>
<SCRIPT LANGUAGE="JavaScript">
function addUserComment(form){
var inputData = {comment: form['comment'].value, user: form['username'].value };
var jsonString = dojo.toJson(inputData);
var request = {
url: '<%= getRelativeUri("/resources/comment")%>',
postData: jsonString,
sync: false,
handleAs: "text",
contentType: "text/json-omment-filtered",
handle: function (response, ioArgs){
form.reset();
},load: function (response, ioArgs){
listComments();
return response;
},error: function(data, ioArgs){
return data;
}
}
var deferred = dojo.rawXhrPost(request);
}
function listComments(){
var request = {
url: '<%= getRelativeUri("/resources/comment")%>',
sync: false,
preventCache: true,
handleAs: "json-comment-optional",
contentType: "text/json-comment-filtered",
load: function (response, ioArgs){
var comments = (response['comments'] );
if(comments !=null){
var commentDiv = dojo.byId("comments");
var text='';
for(var i=0; i < comments.length; i++){
text +="User <b><code>" +
comments[i]['user'] +
"</code></b> commented:<br/>";
text += comments[i]['comment']+"<br/>";
}
commentDiv.innerHTML = text;
}
return response;
},error: function(data, ioArgs){
return data;
}
}
var deferred = dojo.xhrGet(request);
}
</SCRIPT>
</head>
<body bgcolor="lightyellow" onLoad="listComments()">
<blockquote width="75%">
<div id="blogPost">
</div>
<div id="comments">
</div>
<div id="addComment">
<h3>Leave a reply:</h3>
<form action='' method='POST'>
<table>
<tr>
<th>Name:</th>
<td><input type="text" name="username"></td>
</tr>
<tr>
<th>Comment:</th>
<td> <textarea cols='40' rows='6' name='comment'></textarea>
</td></tr>
<tr>
<td colspan="2" align="left">
<input type='button' value='Submit comment'
onclick="addUserComment(this.form); return false;">
</td>
</tr>
</table>
</form>
</div>
</blockquote>
</body>
</html>
The /app/resources/comment.groovy component of the application contains the services that are called from the
/public/blog.gt. These services allow users to post comments and view previous comments posted by other users.
The following sample shows the coding for the /app/resources/comment.groovy script:
def onCreate(){
def postedComment = zero.json.Json.decode(request.input[])
def user = postedComment["user"]
def comment = postedComment["comment"]
synchronized(this){
// just for demo purposes. Should use database to persist
// comments.
def commentsExist = zcontains("/app/comments")
if(!commentsExist){
comments = []
}else{
comments = zget("/app/comments")
}
comments.add(["user": user, "comment":comment] );
zput("/app/comments", comments);
request.status = java.net.HttpURLConnection.HTTP_NO_CONTENT
}
}
def onList(){
request.view='JSON'
request.json.output = ["comments" : zget("/app/comments", [])];
render()
}
Blog: Good user
The blog application has been written, so you can see what happens in the typical usage scenario for this application. To begin, you would open the browser and request the /blog.gt script, which renders the
blog, as shown in the following example:
When the user enters their name and a comment and clicks enter, the blog comment is displayed as shown in the following example:
Blog: Bad user
In this example, active content is introduced to this scenario. In the previous good
user scenario, the user entered in their name and a comment and clicked submit. In the active content scenario, instead of typing a comment, the user types the following string:
<iframe src="" width="0%" height="0%" align="right" onload="alert('iframe ACF attack')"></iframe>
The following example shows this:
When the user enters the active content text and clicks enter, instead of the comment being shown in the previous gooduser example, the active content is run as shown in the following example:
Preventing the BadUser blog scenario
You can prevent the previous active content scenario by including the zero.acf dependency as part of your application. By default,
ACF filters active content from inbound JSON data (such as the comment field in the form) prior to the application accesses
the JSO data.
Advanced ACF: Blog
After completing those demonstrations of the default features of ACF, you can start to investigate some of the more advanced features of ACF. Consider the blog application that was reviewed in the previous section. It would not be unrealistic to have all or part of that application existing as an application outside of the scope of the application the client is using. For example, the application could be a portal application written as a server side mashup of services with one of the services being the complete blog application or just the comment service that is part of the original blog application. Using the blog application, the following blog example explores one possible way that the application could be distributed and how your original application can leverage ACF to prevent active content.
Advanced blog example
To demonstrate a more advanced feature of ACF, this example separates the blog application into the following two separate applications:
-
/public/blog.gt - The page that the client uses to access to the blog application.
-
/app/resources/comment.groovy - Contains the code that deals with the listing and adding of comments.
The comment component is separated into its own application because there are many other applications that want to have this ability to post comments about various things for a
variety of applications. There are two downsides to this solution. The first is that the server which runs the comment application does not
provide ACF support. The second is that, due to restrictions with browser same application origin security policies, a server side proxy
(/app/resources/comment_proxy.groovy) is required to make the service call from the original application to the new comment
application running on a different server hostname and port.
The following sample shows the coding for the /app/resources/comment_proxy.groovy script:
import zero.core.connection.Connection.Response
import zero.core.connection.Connection
def url="http://www.someserver.com/resources/comment"
def onCreate(){
def postedComment = zero.json.Json.decode(request.input[])
Connection.Response response = Connection.doPOST(url,
["Content-Type" :["text/json"]],
zero.json.Json.encode(postedComment) )
def status = response.getResponseStatus() // block for result status code
request.status = status
}
def onList(){
Connection.Response response = Connection.doGET(url)
def jsonBody = response.getResponseBodyAsString()
if(jsonBody != null){
def comments = zero.json.Json.decode(jsonBody)
request.view='JSON'
request.json.output = comments;
render()
}else{
request.status = response.status
}
}
Because the good user and bad user scenarios are the same as those discussed in the simple blog example, those details are not repeated in this section.
Preventing the advanced BadUser blog scenario
You can prevent the BadUser advanced blog scenario by including the zero.acf dependency as part of your application. By default,
ACF filters active content from outbound JSON data (such as the comment field in the JSON response) during the render call in the
onList method of /app/resources/comment_proxy.groovy.
Default enablement of ACF
The preceding scenarios demonstrated the functionality that is available when an application includes the zero.acf dependency. For most
applications, the default enablement covers most of the possibile vulnerabilties that exist when dealing with user generated content. Having worked through these
scenarios, you can now evaluate whether ACF is applicable for their application. For further details on ACF and its more
advanced features, see the Actice content filtering section of the Developer's Guide.