ציטוט Patrick Casey:
*The Problem:*

Tapestry links, like most everybody’s generated links (this isn’t something specific to Howard’s code), are vulnerable to attack by somebody who decides to change the url.

Let’s say, for example, I have a DirectLink that takes as its parameter a user id e.g.

            app?service=direct/1/Page/Component/<UserId>

Let say you generate a link pointing to the user’s personal user record (id #55) and send it out on a form labeled “My User Record” so the user can update their password.

            app?service=direct/1/Page/Component/sp=55


No, You save the user's ID in the session (ASO). just an Integer, does'nt take much place. Then you link to showProfile - which takes the id of the user from the session and *never* from the GET/POST. When editting any resource which has an owner, owner verification should rely on server/session state only, and not on GET/POST variables. What you do here is duplicating the session integration of the servlet specification. It already has an implementation (cookie or URL based) for just what you do - its called session - so why not use it.


Then lets say evil eve decides he wants to see the **administrators** user record (#1). Assuming he can get onto your app with **some** form of rights (so that he has a session) he can CTRL-C your link to record 55, replace the 55 with a 1, and CTRL-V it back into your browser.

            app?service=direct/1/Page/Component/sp=1

All he has to do is take your generated URL and replace the 55 with a 1 and voila he’s staring at the administrator record and changing the password, thus hijacking your web app.

*The Solution:*

Include a cryptographically strong digital signature as the final parameter on each URL so that a malicious user who attempts to manipulate the URL content will be rejected by the system. Thus, instead of:

app?service=direct/1/Page/Component/<UserId>

We instead generate:

app?service=direct/1/Page/Component/<UserId>/<signature>

Then any maniupluation of the URL will result in a failed signature verification and the URL will be rejected.

*How to Use it:*

1) Register the SecureDirectService in your .application file with something like:

<service name="SecureDirectService" class="services.SecureDirectService" />

2) Everywhere you need a secure link, replace @Direct with @SecureDirect.

3) Sit back and bask in the glory of a (somewhat) more secure application.

*Caveots:*

1) I’m making no legal guarantee this is secure at all or even that it works. Use at your own risk.

2) Secure Links take more processing horsepower to render than regular direct links (because of the signatures). There will also be a noticeable pause at application startup when the initializer generates a strong key pair (1-2 seconds on my laptop.

3) Secure Links **will not** survive a JVM cycle or a reload. Right now I generate a transient public/private key pair in a static initializer block so when you restart your app, you’ll get a new keypair and hence all your existing rendered links are considered stale. If you want to change the system to use a static keypair, feel free to change the initializer block, but this was the simplest approach.

4) The attached code is in my packaging convention and probably references my logging code here and there, so you might need to tweak it a bit to get it working.

5) There are a couple of JVM 1.5 isms in there, but nothing critical so if you need to run it on 1.4.2 just get rid of my for (Object o : args) loops and replace them with for (int x=0; x< args.size(); x++).

6) Secure Links are **longer** than regular Direct Links because they include a 48 character Base64 encoded signature at the end of every URL.

7) If anyone wants to take the time to seriously productize this thing, package it into a .jar or whatever, be my guest. Right now it’s sloppy and probably doesn’t handle all the boundry cases well, but it’s as far as I got this evening.

8) This is Tapestry 3.0.3 code. I haven’t a clue how it’ll behave in the brave new world of 4.0.

Best of Luck,

--- Pat


------------------------------------------------------------------------

package components;

import java.util.ArrayList;
import java.util.List;

import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.engine.ILink;
import org.apache.tapestry.link.DirectLink;

public abstract class SecureDirectLink extends DirectLink {     

        public ILink getLink(IRequestCycle cycle) {
                Object[] params = constructServiceParameters(getParameters(), 
cycle);
                return getLink(cycle, "SecureDirectService", params);
        }

        public static Object[] constructServiceParameters(Object 
parameterValue, IRequestCycle cycle) {
                ArrayList args = new ArrayList();
                if (parameterValue != null) {
                        if (parameterValue instanceof Object[]) {
                                Object[] oa = (Object[]) parameterValue;
                                for (Object o : oa)
                                        args.add(o);
                        } else if (parameterValue instanceof List) {
                                List list = (List) parameterValue;
                                args.addAll(list);
                        } else
                                args.add(parameterValue);
                }               
                return args.toArray();
        }
        
}


------------------------------------------------------------------------

package services;

import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.ArrayList;

import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;

import org.apache.tapestry.ApplicationRuntimeException;
import org.apache.tapestry.IComponent;
import org.apache.tapestry.IDirect;
import org.apache.tapestry.IEngine;
import org.apache.tapestry.IPage;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.StaleLinkException;
import org.apache.tapestry.StaleSessionException;
import org.apache.tapestry.Tapestry;
import org.apache.tapestry.engine.DirectService;
import org.apache.tapestry.engine.IEngineServiceView;
import org.apache.tapestry.engine.ILink;
import org.apache.tapestry.request.RequestContext;
import org.apache.tapestry.request.ResponseOutputStream;
import org.apache.tapestry.util.io.DataSqueezer;

import sun.misc.BASE64Decoder;
import utils.Log;

public class SecureDirectService extends DirectService {
        public static final String SECUREDIRECTSERVICE = "SecureDirectService";
        private static PublicKey fPublicKey = null;
        private static PrivateKey fPrivateKey = null;

        static {
                try {
                        // Generate a 1024-bit Digital Signature Algorithm 
(DSA) key pair
                        KeyPairGenerator keyGen = 
KeyPairGenerator.getInstance("DSA");
                        keyGen.initialize(1024);
                        KeyPair keypair = keyGen.genKeyPair();
                        fPrivateKey = keypair.getPrivate();
                        fPublicKey = keypair.getPublic();
                } catch (NoSuchAlgorithmException e) {
                        Log.error(e);
                }
        }

        public String getName() {
                return SECUREDIRECTSERVICE;
        }

        public ILink getLink(IRequestCycle cycle, IComponent component,
                        Object[] parameters) {

                // New since 1.0.1, we use the component to determine
                // the page, not the cycle. Through the use of tricky
                // things such as Block/InsertBlock, it is possible
                // that a component from a page different than
                // the response page will render.
                // In 1.0.6, we start to record *both* the render page
                // and the component page (if different), as the extended
                // context.

                IPage renderPage = cycle.getPage();
                IPage componentPage = component.getPage();

                boolean complex = renderPage != componentPage;

                String[] context = complex ? new String[4] : new String[3];

                int i = 0;

                String stateful = cycle.getEngine().isStateful() ? "1" : "0";

                context[i++] = stateful;

                if (complex)
                        context[i++] = renderPage.getPageName();

                context[i++] = componentPage.getPageName();
                context[i++] = component.getIdPath();
                IEngine engine = cycle.getEngine();
                ArrayList args = new ArrayList();
                for (Object o : parameters)
                        args.add(o);
                Object[] initial = parameters;
                try {
                        String[] url = 
engine.getDataSqueezer().squeeze(initial);
                        StringBuffer sb = new StringBuffer();
                        for (String s : url)
                                sb.append(s);
                        byte[] message = sb.toString().getBytes();
                        Signature sig = 
Signature.getInstance(fPrivateKey.getAlgorithm());
                        sig.initSign(fPrivateKey);
                        sig.update(message, 0, message.length);
                        // and now we add the sig as our last argument
                        // we'll base 64 encode our sig to make it shorter
                        sun.misc.BASE64Encoder encoder = new 
sun.misc.BASE64Encoder();
                        String encoded = encoder.encode(sig.sign());
                        args.add(encoded);
                } catch (Exception e) {
                        Log.error(e);
                }
                return constructLink(cycle, SECUREDIRECTSERVICE, context, 
args.toArray(),
                                true);
        }

        public void service(IEngineServiceView engine, IRequestCycle cycle,
                        ResponseOutputStream output) throws ServletException, 
IOException {
                IDirect direct;
                int count = 0;
                String componentPageName;
                IPage componentPage;
                RequestContext requestContext = cycle.getRequestContext();
                String[] serviceContext = getServiceContext(requestContext);

                if (serviceContext != null)
                        count = serviceContext.length;

                if (count != 3 && count != 4)
                        throw new ApplicationRuntimeException(Tapestry
                                        
.getMessage("DirectService.context-parameters"));

                boolean complex = count == 4;

                int i = 0;
                String stateful = serviceContext[i++];
                String pageName = serviceContext[i++];

                if (complex)
                        componentPageName = serviceContext[i++];
                else
                        componentPageName = pageName;

                String componentPath = serviceContext[i++];

                IPage page = cycle.getPage(pageName);

                cycle.activate(page);

                if (complex)
                        componentPage = cycle.getPage(componentPageName);
                else
                        componentPage = page;

                IComponent component = 
componentPage.getNestedComponent(componentPath);

                try {
                        direct = (IDirect) component;
                } catch (ClassCastException ex) {
                        throw new ApplicationRuntimeException(Tapestry.format(
                                        "DirectService.component-wrong-type", 
component
                                                        .getExtendedId()), 
component, null, ex);
                }

                // Check for a StateSession only the session was stateful when
                // the Gesture was created.

                if (stateful.equals("1") && direct.isStateful()) {
                        HttpSession session = 
cycle.getRequestContext().getSession();

                        if (session == null || session.isNew())
                                throw new StaleSessionException(Tapestry.format(
                                                
"DirectService.stale-session-exception", direct
                                                                
.getExtendedId()), direct.getPage());
                }

                Object[] parameters = getParameters(cycle);
                ArrayList fixed = new ArrayList();
                for (int x = 0; x < (parameters.length - 1); x++)
                        fixed.add(parameters[x]);
                String key = (String) parameters[parameters.length - 1];
                BASE64Decoder decoder = new sun.misc.BASE64Decoder();
                byte[] binKey = decoder.decodeBuffer(key);
                // ok now we know what the signature says
                // now we reconstruct the message
                DataSqueezer ds = cycle.getEngine().getDataSqueezer();
                String[] squeezed = ds.squeeze(fixed.toArray());
                StringBuffer message = new StringBuffer();
                for (String s : squeezed)
                        message.append(s);
                byte[] buffer = message.toString().getBytes();
                try {
                        PublicKey pk = fPublicKey;
                        Signature sig = 
Signature.getInstance(pk.getAlgorithm());
                        sig.initVerify(pk);
                        sig.update(buffer, 0, buffer.length);
                        boolean isValid = sig.verify(binKey);
                        if (!isValid)
                                throw new StaleLinkException();
                        
                } catch (Exception e) {
                        Log.error(e);
                }

                cycle.setServiceParameters(parameters);
                direct.trigger(cycle);

                // Render the response. This will be the response page (the 
first
                // element in the context)
                // unless the direct (or its delegate) changes it.

                engine.renderResponse(cycle, output);
        }
}



------------------------------------------------------------------------

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]


---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to