Donnerstag, 28. Juli 2011

How to build and modfiy Tapestry 5.3.0

Currently I think the fastest and easy way to build your custom Tapestry version is to do a checkout from the subversion repository. Use gradle idea to generate IntelliJ IDEA project description files. The community version of this IDE is free. Just open the project and everything should compile. Since the switch to a gradle build process, the eclipse integration is a little bit broken (at least for me). The gradle generated eclipse project files didn't work for me. Compared to that the IntelliJ IDEA integration is painless. Using gradle install deploys your build in your local maven repository which makes it available to your Tapestry projects.

So the required commands in a brief are:

  • svn checkout https://svn.apache.org/repos/asf/tapestry/tapestry5/trunk tapestry-project
  • cd tapestry-project
  • gradle idea
  • gradle install

Donnerstag, 30. Juni 2011

Tapestry 5.3.0 + Chenillekit + Jumpstart

In order to test my CSRF protection I want to use Jumpstart as testcase. This requires an update of the Chenillekit and Jumpstart to work with Apache Tapestry 5.3.0.

Chenillekit update:
The required modifications in the source code are quite easy. I was able to make a compatible build and  deployed it to my Google Summer of Code maven repository. This is just an interim solution until an official Chenillekit update has happend.

Jumpstart update:
  • Libraries: The Tapestry and Chenillekit libraries in the web/src/main/lib-compile and web/src/main/lib-runtime have to be updated.
  • Code: The AppModule contributeValidateMessagesSource method has to be renamed to contributeComponentMessagesSource
That's it.

Update:
[TAP5-1440] - Remove "validateForm" event triggered by Form component (replaced with "validate" event) --> onValidateForm method needs to be renamed!






Sonntag, 5. Juni 2011

Protecting AJAX requests

So far the insert of an Anti-CSRF token works for every type of component. The token check can either be inserted by using the @Protected annotation at a page or on an event handling method. A problem with the redirect-after-post approach is that the following GET request after a form POST cannot be enhanced with an Anti-CSRF token. I will try to solve this problem later, next task is to protect Ajax based requests.








Samstag, 4. Juni 2011

Fine grained protection mechanism

I changed the design of the fine grained protection mechanism. The insertCSRFToken method and logic is moved to the components, by using AOP techniques. A class transformation inserts component specific methods for the internal Tapestry components, which rely on the specific components. Additionally a generic XPath insertCSRFToken can be used for any kind of component. The mixin is then quite easy and calls the insertCSRFToken method. To make this possible all modified components implement the CSRFProtectable interface.


The demonstration app contains now also a BeanEditForm, that introduces some new problems. The @Protected annotation needs to work also on page level, because on components used in templates it is not possible to add an annotation in the event handler.

Samstag, 14. Mai 2011

Apache Tapestry - CSRF Protection - First Prototype

To get in touch with tapestry internals I implemented a first draft of a CSRF protection mechanism based on a mixin and an annotation named Protected.

The mixin adds a hidden formtoken input field into a Form component - currently in a really ugly way. Additionally the formtoken is also stored in the session at the server side.

Protected - Mixin

package org.apache.tapestry5.csrfprotection.victimapp.mixins;

import java.util.List;
import java.util.UUID;

import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.dom.Element;
import org.apache.tapestry5.dom.Node;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.Request;

public class Protected {
 @Inject
 private Request request;

 void afterRender(MarkupWriter writer) {
  List<Node> nodes = writer.getElement().getChildren();
  for(Node node:nodes){   
   if(node instanceof Element && ((Element)node).getName().equals("form")){
    Element form = (Element) node;
    String token = UUID.randomUUID().toString();
    form.element("input", "type","hidden","name","formtoken","value",token);
    request.getSession(true).setAttribute("formtoken", token);
   }
  }
 } 
}

The value of the formtoken stored in the session is compared against the HTTP request parameter, if the event handler is annotated with the Protected annotation.

Protected - Annotation


package org.apache.tapestry5.csrfprotection.victimapp.annotations;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target(METHOD)
@Retention(RUNTIME)
@Documented
public @interface Protected {

}


The magic happens in the ProtectedWorker class, where the class transformation is done with the help of Plastic. The methods that have the @Protected annotation are enhanced with the comparison of the session parameter and the request parameter. In the case of an CSRF attack an exception is thrown.

ProtectedWorker

package org.apache.tapestry5.csrfprotection.victimapp.protection;

import org.apache.tapestry5.csrfprotection.victimapp.annotations.Protected;
import org.apache.tapestry5.model.MutableComponentModel;
import org.apache.tapestry5.plastic.MethodAdvice;
import org.apache.tapestry5.plastic.PlasticClass;
import org.apache.tapestry5.plastic.PlasticMethod;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.Session;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
import org.apache.tapestry5.services.transform.TransformationSupport;
import org.apache.tapestry5.plastic.MethodInvocation;

public class ProtectedWorker implements ComponentClassTransformWorker2 {
 private Request request;
 
 public ProtectedWorker(Request request){
  this.request = request;
 }
 public void transform(PlasticClass plasticClass,
   TransformationSupport support, MutableComponentModel model) {

  final MethodAdvice myLoggingAdvice = new MethodAdvice() {
   public void advise(MethodInvocation invocation) {
    
    String requestParam = request.getParameter("formtoken");
    String sessionParam = ""; // avoid null pointer exceptions
    Session session = request.getSession(false);
    if(session != null){
     sessionParam = session.getAttribute("formtoken").toString();
    }
    
    if(sessionParam.equals(requestParam)){
     invocation.proceed();
    }
    else{
     invocation.setCheckedException(new ProtectionException("CSRF Attack detected."));
     invocation.rethrow();
    }
   }
  };
  for (final PlasticMethod method : plasticClass
    .getMethodsWithAnnotation(Protected.class)) {
   method.addAdvice(myLoggingAdvice);
  }
 }
}

The exception class is currently trivial.

ProtectedException

package org.apache.tapestry5.csrfprotection.victimapp.protection;

public class ProtectedException extends Exception {
 public ProtectedException(String msg){
  super(msg);
 }

}


To make it run, the class transform configuratoin in the AppModule is required.

AppModule - Fragment

@Contribute(ComponentClassTransformWorker2.class)
    public static void provideTransformWorkers(
            OrderedConfiguration configuration,

            MetaWorker metaWorker,

            ComponentClassResolver resolver)
    {
     configuration.addInstance("Protected",ProtectedWorker.class);
    }

I just added the mixin in the html template of my sample applications and protected the onSuccess event handler.

Usage

<form t:type="form" t:id="statusForm" t:mixins="Protected">
@Protected
 private Object onSuccess(){
  history.getHistory().add(statusMessage);
  return this;
 }

CSRF - Attack

The sample attack that targets the form submit fails now with an exception:



Mittwoch, 11. Mai 2011

m2eclipse import does not recognize Java projects

m2eclipse seem not to work so nice as thought after the first usage. Some projects are not recognized as Java projects.

Tapestry tests hanging with Firefox4

The automated selinum tests that are part of the test goal of Tapestry5 hang if Firefox 4 is used. By switching back to Firefox 3 the build completes.

Dienstag, 10. Mai 2011

m2eclipse

As suggested by Thiago H. de Paula Figueiredo I used the m2eclipse plugin which integrates maven multi module  projects better in Eclipse.

One problem still remain - the source directory setting of the plastic module/project.

It points to the root directory of the module.

With these settings it works.

The error seems to come from the pom.xml setting for sourceDirectory of the plastic module. 


Using Eclipse to compile Apache Tapestry source

Today I faced some problems when I created eclipse project files for Tapestry. There are several modules that result in separate Eclipse project files. Since there is a Maven pom.xml for the whole Tapestry project and for each project I used

mvn eclipse:eclipse -DdownloadSource=true

to generate initial project files. I used a separate workspace where I imported all projects of Tapestry. At first Eclipse was not able to compile them.

I identified two problems:

  1. ArtefactID of tapestry-annotations is tapestry5-annotations. This resulted in an invalid classpath project reference to tapestry-5annotations. After manually modifying the build path settings for the tapestry-ioc and the tapestry-component-report projects that error was resolved. Seems that the pom.xml needs to be fixed to solve that problem, but this would probably cause many side effects.

  2. The source directory configuration in the plastic project pom.xml just points to the project folder. This results in an invalid project configuration in Eclipse. By manually changing the project layout to use the src/test/java, src/main/java and src/external/java and target/maven-shared-archive-resources and removing the project root folder fixed the problem and the projects compile in Eclipse without any problem.

Samstag, 7. Mai 2011

To demonstrate the CSRF vulnerability I have created a simple app called LittleChirp that allows to post a small status message for a logged in user.



The tapestry app behind is simple:


The Index.java and Index.tml files contain the Java class and the page template for the home page. 

public class Index 
{
@SessionState
@Property
private AuthenticationState auth;
@Property
private String username = "TestUser";

@Property
private String password = "Secret";
@InjectPage
private Status status;
@Log
private Object onSuccess(){
auth.setLoggedIn(true);
return status;
}
}


The status page puts the posted statusMessage into a History objects that acts as a wrapper for the status history and is stored in the session.


public class Status{

@Property
private String statusMessage;

@SessionState(create=true)
@Property
private History history;

@Log
private Object onSuccess(){
history.getHistory().add(statusMessage);
return this;
}

@SessionState
private AuthenticationState auth;

@InjectPage
private Index home;

@Log
private Object onActivate(){
if(!auth.isLoggedIn()){
return home;
}
return null;
}
}

If the user is not logged in a redirect to the home page is done. The Layout component contains the functionality to logout a user, which is included at each page that uses the layout.


 @InjectPage
private Index home;
 
    @Inject
    private Request request;
 
    @Log
public Object onActionFromLogout(){
auth.setLoggedIn(false);
request.getSession(false).invalidate();
return home;
}
}


The attack site, which demonstrates the CSRF vulnerability contains two attacks. 

Post request:

<form action="http://localhost:8080/victimapp/status.statusform" method="post">
<input type="hidden" name="statusMessage" value="My evil message, that maybe contains also some bad javascript code."/>
<input type="hidden" name="t:formdata" value="H4sIAAAAAAAAAFvzloG1XIxBJLgksaS02KoYTOWmFhcnpqcWFzGY5hel6yUWJCZnpOqVJBakFpcUVZrqJecXpeZkJuklJRan6jkmAQUTk0vcMlNzUlSCU0tKC1RDD3M/FD3+h4mB0YeBOzk/r6QoP8cvMTe1hEHIJyuxLFE/JzEvXT+4pCgzL926oqCEgRdisS/EYjwOciTVQQFF+clA3cGlSbmZxcWZ+XmH16WYpH2bd46JgaGiAAAF4Kgw/wAAAA==">
<input type="submit" value="Go"/>
</form>

If the "Go" button is pressed a status message update is sent to LittleChirp. This works if the user is currently logged in in the same browser. The t:formdata paramater is a token that is the same for all users and session and will only change if the component structure of the page is changed, it is no protection against CSRF.

GET request:
<a href="http://localhost:8080/victimapp/index.layout.logout"><img src="res/money.jpg" style="width:200px"/></a>

If the user can be tricked to click on the second image, the logout action of LittleChirp is executed. In this case it is not a big problem but on other sites some bigger functionality can behind such action links.




Google Summer of Code 2011!

I start this blog to document my progress for my Google Summer of Code 2011 project Cross-site request forgery protection for Apache Tapestry.

http://www.google-melange.com/gsoc/project/google/gsoc2011/mjung85/8001