Ok, got it working, the code I added to LiftRules is:
/**
* We use this value on listOfSupplimentalHeaders
*/
val containerSessionIdName: FactoryMaker[String] = new FactoryMaker(() => "JSESSIONID") {}
/**
* Compute the headers to be sent to the browser in addition to anything else that's sent
*/
val listOfSupplimentalHeaders: FactoryMaker[List[(String, String)]] = {
import scala.util.Random
/**
* We add 10 fake JSESSIONID strings to the header
* Each sessionid has a random string and random length between 10 and 25 character long
*
*/
val numberOfFakeSessionIds = 1 to 10
def length = (10 to 25)(Random.nextInt(15))
def noBreachSessionIds = numberOfFakeSessionIds.foldLeft(new StringBuilder){
case (acc, _ ) =>
acc.append ( (containerSessionIdName.vend + "=" + randomString(length)) + "; " + (containerSessionIdName.vend + "=" + randomString(length) + "; ") ) }
new FactoryMaker(() => List(
("X-Lift-Version", liftVersion), ("X-Frame-Options", "SAMEORIGIN"), ("X-NO-BREACH", noBreachSessionIds.toString())
)) {}
}
and curl against an app with this patch gives:
* About to connect() to 127.0.0.1 port 8080 (#0)
* Trying 127.0.0.1... connected
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> HEAD / HTTP/1.1
> User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8x zlib/1.2.5
> Accept: */*
>
< HTTP/1.1 302 Found
< Set-Cookie: JSESSIONID=fc2mdniwiz061i8eup513y88u;Path=/
< Location: /login?F920981047703FLXCU5=_
< Expires: Sun, 4 Aug 2013 22:32:17 GMT
< Content-Length: 0
< Cache-Control: no-cache, private, no-store
< Content-Type: text/plain
< Pragma: no-cache
< Date: Sun, 4 Aug 2013 22:32:17 GMT
< X-Lift-Version: 2.6-SNAPSHOT
< X-Frame-Options: SAMEORIGIN
< X-NO-BREACH: JSESSIONID=4VOSKYJ4FC2FS5NCQCU; JSESSIONID=W0L30LTDERBJJHAHPPATUWV; JSESSIONID=NFNDKECEYHHM; JSESSIONID=IQZ4QFRAGTSHTXG12; JSESSIONID=QLLDUTCYOH0; JSESSIONID=HMJAHOUFXX3TJZ; JSESSIONID=02CPR41ABKCECI5QN2FM0IG; JSESSIONID=TPPLKPK0UZMOVPXLAZ; JSESSIONID=KPURMA45PGYM5MX; JSESSIONID=X2SLF3AP53CMROZZW3IBX5; JSESSIONID=RQXC3BQYH0PWUID; JSESSIONID=0QCW4VERX5EY0O; JSESSIONID=2YX41EH0LSSDSUA4V0QM; JSESSIONID=L0BH3V3UMZVL2ORWUQN; JSESSIONID=XNFI3XOWPBQ; JSESSIONID=EE4MUM54QVEGB5YOSL; JSESSIONID=0LHKPML35FGTYM; JSESSIONID=D5MYH5F3DNQAZTSWPJEHSR; JSESSIONID=5WL4QL3MJ1CN35; JSESSIONID=SU4XISD5NJ53PS2I43C1XY;
< Server: Jetty(8.1.7.v20120910)
<
* Connection #0 to host 127.0.0.1 left intact
* Closing connection #0
And all the values for X-NO-BREACH change on each request, each fake jsessionid changes strings and also length.
But now I see one issue, or at least one way the attacker could bypass our work, Lift will append the header to requests that Lift handles, but for example, if I request some js file that I have in webapp/js, that request will give you the jsessionid value, but it will not include the X_NO_BREACH header. I guess this has to be a two fold mitigation, one is Lift, and then Jetty.
I'll be pushing this as a pull request later tonight.
Thanks
Diego