Even if the owner/edit is enforced, it's still a good idea to obfuscate the
ids passed through parameters. This lessens the likelihood of a curious
observer who is attempting to get an idea of how your db schema is designed.
Okay, maybe that's an exaggeration, but at a minimum nobody can determine
whether I'm using a sequential id generation scheme or a random (say guid)
scheme. I like the idea.
On 11/11/05, Ron Piterman <[EMAIL PROTECTED]> wrote:
>
> ציטוט 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]
>
>