Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ajax In Action (2006).pdf
Скачиваний:
63
Добавлен:
17.08.2013
Размер:
8.36 Mб
Скачать

272CHAPTER 7

Security and Ajax

Under correct game play, both users may set up their pieces in any order and will then call the URL guessPosition.do in turn. The server will police the order of play, returning a “not your turn” response if a player tries to play out of turn.

Let’s now put on our black hats and try to hack the game. We’ve written a client that is able to call the web service API in any order it likes. What can we do to tip the odds in our favor? We can’t give ourselves extra turns because the server monitors that—it’s part of the published API.

One possible cheat is to move a piece after the setup phase is finished. Under the fine-grained architecture, we can try calling positionShip.do while the game is in progress. If the server code has been well written, it will note that this is against the rules and return a negative acknowledgment. However, we have nothing to lose by trying, and it is up to the server-side developer to anticipate these misuses and code defensively around them.

On the other hand, if the server is using the coarse-grained API, it isn’t possible to move individual pieces without also clearing the entire board. Fine-tuning the game in your favor isn’t a possibility.

A coarse-grained API limits the flexibility of any malicious hacker, without compromising the usability for law-abiding users. Under a well-designed server model, use of a fine-grained API shouldn’t present any exploits, but the number of entry points for potential exploits is much higher, and the burden of checking these entry points for security flaws rests firmly with the server tier developer.

In section 5.3.4, we suggested using a Façade to simplify the API exposed by a service-oriented architecture. We recommend doing so again here, from a security standpoint, because a simpler set of entry points from the Internet is easier to police.

Design can limit the exposure of our application to external entities, but we still need to offer some entry points for our legitimate Ajax client to use. In the following section, we examine ways of securing these entry points.

7.4.2Restricting access to web data

In an ideal world, we would like to allow access to the dynamic data served from our app to the Ajax client (and possibly other authorized parties) and prevent anybody else from getting in. With some rich-client technologies, we would have the opportunity of using custom network protocols, but the Ajax application is limited to communicating over HTTP. Secure HTTP can keep the data in individual transactions away from prying eyes, as we discussed earlier, but it can’t be used to determine who gets to call a particular URL.

Configure reject URL

Policing access to Ajax data streams

273

 

 

Fortunately, HTTP is quite a rich protocol, and the XMLHttpRequest object gives us a good level of fine-grained control over it. When a request arrives on the server, we have access to a range of HTTP headers from which we can infer things about the origin of the request.

Filtering HTTP requests

For the sake of providing concrete examples, we’ll use Java code here. Other server-side technologies offer similar ways to implement the techniques that we are describing, too. In the Java web application specification, we can define objects of type javax.servlet.Filter, which intercept specific requests before they are processed at their destination. Subclasses of Filter override the doFilter() method and may inspect the HTTP request before deciding whether to let it through or forward it on to a different destination. Listing 7.5 shows the code for a simple security filter that will inspect a request and then either let it through or forward it to an error page.

Listing 7.5 A generic Java security filter

public abstract class GenericSecurityFilter implements Filter { protected String rejectUrl=null;

public void init(FilterConfig config) throws ServletException {

rejectUrl=config.getInitParameter("rejectUrl"); b

}

public void doFilter(

ServletRequest request, ServletResponse response, FilterChain chain)

throws IOException,

ServletException {

 

 

 

c Check request validity

if (isValidRequest(request)){

 

 

 

 

 

 

chain.doFilter(request, response);

 

d Forward to reject URL

}else if (rejectUrl!=null){

 

 

RequestDispatcher

dispatcher

 

 

 

 

=request.getRequestDispatcher(rejectUrl); dispatcher.forward(request, response);

}

}

protected abstract boolean isValidRequest(ServletRequest request);

public void destroy(){}

}

274CHAPTER 7

Security and Ajax

The filter is an abstract class, defining an abstract method isValidRequest() that inspects the incoming request object before passing a verdict. If the method fails c, it is forwarded to a different URL d, which is defined in the configuration file for the web application b, which we’ll look at shortly.

This filter provides us with considerable flexibility in defining a concrete subclass. We can adapt it to more than one security strategy.

Using the HTTP session

One common approach is to create a token in the user’s HTTP session when she logs in and check for the existence of that object in session during subsequent requests before performing any other actions. Listing 7.6 demonstrates a simple filter of this type.

Listing 7.6 Session token-checking filter

public class SessionTokenSecurityFilter extends GenericSecurityFilter {

protected boolean isValidRequest(ServletRequest request) { boolean valid=false;

HttpSession session=request.getSession(); if (session!=null){

UserToken token=(Token) session.getAttribute('userToken'); if (token!=null){

valid=true;

}

}

return valid;

}

}

This technique is commonly used in conventional web applications, typically forwarding to a login screen if validation fails. In an Ajax application, we are free to return a much simpler response in XML, JSON, or plain text, which the client could respond to by prompting the user to log in again. In chapter 11, we discuss a fuller implementation of such a login screen for our Ajax Portal application.

Using encrypted HTTP headers

Another common strategy for validating a request is to add an additional header to the HTTP request and check for its presence in the filter. Listing 7.7 shows a second example filter that looks for a specific header and checks the encrypted value against a known key held on the server.

Get header value
Configure header name

Policing access to Ajax data streams

275

 

 

Listing 7.7 HTTP header-checking filter

public class SecretHeaderSecurityFilter extends GenericSecurityFilter {

private String headerName=null;

public void init(FilterConfig config) throws ServletException { super.init(config); headerName=config.getInitParameter("headerName");

}

protected boolean isValidRequest(ServletRequest request) { boolean valid=true;

HttpServletRequest hrequest=(HttpServletRequest)request; if (headerName!=null){

valid=false;

String headerVal=hrequest.getHeader(headerName); b Encrypter crypt=EncryptUtils.retrieve(hrequest);

if (crypt!=null){

valid=crypt.compare(headerVal); c Compare header value

}

}

return valid;

}

}

When testing the request, this filter reads a specific header name b and compares it with an encrypted value stored in the server session c. This value is transient and may be generated randomly for each particular session in order to make the system harder to crack. The Encrypter class uses the Apache Commons

Codec classes and javax.security.MessageDigest classes to generate a hexencoded MD5 value. The full class listing is available in the downloadable code that accompanies this book. The principle of deriving a hex-encoded MD5 in Java is shown here:

MessageDigest digest=MessageDigest.getInstance("MD5"); byte[] data=privKey.getBytes();

digest.update(data);

byte[] raw=digest.digest(pubKey.getBytes()); byte[] b64=Base64.encodeBase64(raw);

return new String(b64);

where privKey and pubKey are the private and public keys, respectively. To configure this filter to review all URLs under the path /Ajax/data, we can add the following filter definition to the web.xml configuration file for our web application:

276CHAPTER 7

Security and Ajax

<filter id='securityFilter_1'> <filter-name>HeaderChecker</filter-name> <filter-class>

com.manning.ajaxinaction.web.SecretHeaderSecurityFilter </filter-class>

<init-param id='securityFilter_1_param_1'> <param-name>rejectUrl</param-name> <param-value>/error/reject.do</param-value>

</init-param>

<init-param id='securityFilter_1_param_2'> <param-name>headerName</param-name> <param-value>secret-password</param-value>

</init-param> </filter>

This configures the filter to forward rejected requests to the URL /error/reject.do, after checking the value of HTTP header “secret-password.” To complete the configuration, we define a filter mapping to match this filter to everything under a specific path:

<filter-mapping> <filter-name>HeaderChecker</filter-name> <url-pattern>/ajax/data/*</url-pattern>

</filter-mapping>

On the client side, the client can generate Base64 MD5 digests using Paul Johnston’s libraries (which we discussed earlier in this chapter). To add the required HTTP header on our Ajax client, we use the setRequestHeader() method, as outlined here:

function loadXml(url){ var req=null;

if (window.XMLHttpRequest){ req=new XMLHttpRequest();

} else if (window.ActiveXObject){

req=new ActiveXObject("Microsoft.XMLHTTP");

}

if (req){ req.onreadystatechange=onReadyState; req.open('GET',url,true);

req.setRequestHeader('secret-password',getEncryptedKey()); req.send(params);

}

}

where the encryption function is simply defined as the Base64 MD5 digest of a given string: