Skip to content

Instantly share code, notes, and snippets.

@picodotdev
Last active March 14, 2023 00:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save picodotdev/7044756 to your computer and use it in GitHub Desktop.
Save picodotdev/7044756 to your computer and use it in GitHub Desktop.
Solución al problema de seguridad CSRF en Apache Tapestry http://elblogdepicodev.blogspot.com/2013/10/solucion-al-problema-de-seguridad-csrf.html
package es.com.blogspot.elblogdepicodev.plugintapestry.services.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Csrf {
}
@Contribute(ComponentClassTransformWorker2.class)
public static void contributeWorkers(OrderedConfiguration<ComponentClassTransformWorker2> configuration) {
configuration.addInstance("CSRF", CsrfWorker.class);
}
package es.com.blogspot.elblogdepicodev.plugintapestry.mixins;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.InjectContainer;
import org.apache.tapestry5.annotations.MixinAfter;
import org.apache.tapestry5.annotations.SessionState;
import org.apache.tapestry5.corelib.components.ActionLink;
import org.apache.tapestry5.corelib.components.BeanEditForm;
import org.apache.tapestry5.corelib.components.EventLink;
import org.apache.tapestry5.corelib.components.Form;
import org.apache.tapestry5.dom.Element;
import org.apache.tapestry5.dom.Node;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.runtime.Component;
import org.apache.tapestry5.services.Request;
import es.com.blogspot.elblogdepicodev.plugintapestry.services.sso.Sid;
@MixinAfter
public class Csrf {
@SessionState(create = false)
private Sid sid;
@Inject
private Request request;
@Inject
private ComponentResources resources;
@InjectContainer
private Component container;
void beginRender(MarkupWriter writer) {
if (container instanceof EventLink || container instanceof ActionLink) {
buildSid();
Element element = writer.getElement();
String href = element.getAttribute("href");
String character = (href.indexOf('?') == -1) ? "?" : "&";
element.forceAttributes("href", String.format("%s%st:sid=%s", href, character, sid.getSid()));
}
}
void afterRenderTemplate(MarkupWriter writer) {
if (container instanceof BeanEditForm) {
Element form = null;
for (Node node : writer.getElement().getChildren()) {
if (node instanceof Element) {
Element element = (Element) node;
if (element.getName().equals("form")) {
form = element;
break;
}
}
}
if (form != null) {
buildSid();
Element e = form.element("input", "type", "hidden", "name", "t:sid", "value", sid.getSid());
e.moveToTop(form);
}
}
}
void beforeRenderBody(MarkupWriter writer) {
if (container instanceof Form) {
buildSid();
Element form = (Element) writer.getElement();
form.element("input", "type", "hidden", "name", "t:sid", "value", sid.getSid());
} else if (container instanceof BeanEditForm) {
buildSid();
Element form = (Element) writer.getElement();
form.element("input", "type", "hidden", "name", "t:sid", "value", sid.getSid());
}
}
private void buildSid() {
if (sid == null) {
sid = Sid.newInstance();
}
}
}
package es.com.blogspot.elblogdepicodev.plugintapestry.services.workers;
import org.apache.tapestry5.model.MutableComponentModel;
import org.apache.tapestry5.plastic.MethodAdvice;
import org.apache.tapestry5.plastic.MethodInvocation;
import org.apache.tapestry5.plastic.PlasticClass;
import org.apache.tapestry5.plastic.PlasticMethod;
import org.apache.tapestry5.services.ApplicationStateManager;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
import org.apache.tapestry5.services.transform.TransformationSupport;
import es.com.blogspot.elblogdepicodev.plugintapestry.services.annotation.Csrf;
import es.com.blogspot.elblogdepicodev.plugintapestry.services.exceptions.CSRFException;
import es.com.blogspot.elblogdepicodev.plugintapestry.services.sso.Sid;
public class CsrfWorker implements ComponentClassTransformWorker2 {
private Request request;
private ApplicationStateManager manager;
public CsrfWorker(Request request, ApplicationStateManager manager) {
this.request = request;
this.manager = manager;
}
public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model) {
MethodAdvice advice = new MethodAdvice() {
public void advise(MethodInvocation invocation) {
String rsid = request.getParameter("t:sid");
Sid sid = manager.getIfExists(Sid.class);
if (sid != null && sid.isValid(rsid)) {
invocation.proceed();
} else {
invocation.setCheckedException(new CSRFException("El parámetro sid de la petición no se corresponde con el sid de la sesión. Esta petición no es válida (Posible ataque CSRF)."));
invocation.rethrow();
}
}
};
for (PlasticMethod method : plasticClass.getMethodsWithAnnotation(Csrf.class)) {
method.addAdvice(advice);
}
}
}
$ git clone git://github.com/picodotdev/elblogdepicodev.git
$ cd elblogdepicodev/PlugInTapestry
$ ./gradlew tomcatRun
# Abrir en el navegador http://localhost:8080/PlugInTapestry/
@Csrf
void onSuccessFromCsrfForm() {
cuenta += 1;
renderer.addRender("zone", zone).addRender("submitOneZone", submitOneZone).addRender("csrfZone", csrfZone);
}
@Csrf
void onSumar1CuentaCsrf() {
cuenta += 1;
renderer.addRender("zone", zone).addRender("submitOneZone", submitOneZone).addRender("csrfZone", csrfZone);
}
<h4>Solución al CSRF</h4>
<p>
Cuenta: <t:zone t:id="csrfZone" id="csrfZone" elementName="span">${cuenta}</t:zone>
<div class="row">
<div class="col-md-4">
<h5>En formulario</h5>
<form t:id="csrfForm" t:type="form" t:zone="csrfZone" t:mixins="csrf">
<input t:type="submit" value="Sumar 1"/>
</form>
</div>
<div class="col-md-4">
<h5>En enlace</h5>
<a t:type="eventlink" t:event="sumar1CuentaCsrf" t:zone="csrfZone" t:mixins="csrf">Sumar 1</a>
</div>
<div class="col-md-4">
<h5>Fallo seguridad</h5>
<a t:type="eventlink" t:event="sumar1CuentaCsrf" t:zone="csrfZone" t:parameters="prop:{'t:sid':'dummy-attack'}">Sumar 1</a>
</div>
</div>
</p>
package es.com.blogspot.elblogdepicodev.plugintapestry.services.sso;
import java.io.Serializable;
import java.util.UUID;
public class Sid implements Serializable {
private static final long serialVersionUID = -4552333438930728660L;
private String sid;
protected Sid(String sid) {
this.sid = sid;
}
public static Sid newInstance() {
return new Sid(UUID.randomUUID().toString());
}
public String getSid() {
return sid;
}
public boolean isValid(String sid) {
return this.sid.equals(sid);
}
}
@enriqueism
Copy link

Hi! I cant pass from here "Failure creating embedded component 'inputs' of foo.bar.pages.inicio.ModEMail: Unable to resolve 'csrf' to a mixin class name."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment