ציטוט 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]