Okay... so this is why iframes can be dangerous

274 views
Skip to first unread message

David Pollak

unread,
Aug 2, 2013, 4:00:05 PM8/2/13
to liftweb
What can we do to mitigate against this kind of attack in Lift? How can we add stuff to responses to mitigate against this kind of attack?

--
Telegram, Simply Beautiful CMS https://telegr.am
Lift, the simply functional web framework http://liftweb.net

Diego Medina

unread,
Aug 2, 2013, 4:26:01 PM8/2/13
to Lift
If I understood the attack correctly, there are two areas of information that BRACH can "read"

1- Any data on the body of the request, this could be, as I'm typing this email on the gmail UI, my first and last name, because they are part of the html that google sends back after I login
2- Information stored in the headers, like a cookie value.

I would think that 2 is somewhat more serious than one, or at least may have an easier solution.

If we take as an example, telegr.am, which uses ssl, there are two pieces of information that I really don't want anyone to have access to, these are:

  1. Cookie:
    ext_id=abc123reallylonghere; JSESSIONID=xyz

if anyone in my network can guess those values, then they can login to telegram under my username. Would it be possible to have Lift generate different "key" names for those two values, similar to how form fields have different "name" attributes on each page request?





--
--
Lift, the simply functional web framework: http://liftweb.net
Code: http://github.com/lift
Discussion: http://groups.google.com/group/liftweb
Stuck? Help us help you: https://www.assembla.com/wiki/show/liftweb/Posting_example_code
 
---
You received this message because you are subscribed to the Google Groups "Lift" group.
To unsubscribe from this group and stop receiving emails from it, send an email to liftweb+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Diego Medina
Lift/Scala Developer
di...@fmpwizard.com
http://fmpwizard.telegr.am

David Pollak

unread,
Aug 2, 2013, 5:07:38 PM8/2/13
to liftweb
On Fri, Aug 2, 2013 at 1:26 PM, Diego Medina <di...@fmpwizard.com> wrote:
If I understood the attack correctly, there are two areas of information that BRACH can "read"

1- Any data on the body of the request, this could be, as I'm typing this email on the gmail UI, my first and last name, because they are part of the html that google sends back after I login
2- Information stored in the headers, like a cookie value.

I would think that 2 is somewhat more serious than one, or at least may have an easier solution.

If we take as an example, telegr.am, which uses ssl, there are two pieces of information that I really don't want anyone to have access to, these are:

  1. Cookie:
    ext_id=abc123reallylonghere; JSESSIONID=xyz

if anyone in my network can guess those values, then they can login to telegram under my username. Would it be possible to have Lift generate different "key" names for those two values, similar to how form fields have different "name" attributes on each page request?



I don't completely understand the mechanics of the attack, but I think that it basically boils down to seeing if JSESSIONID=x and JSESSIONID=y yield different lengths in the compressed/encrypted byte-blob. Based on lengths not changing, then it's can be deduced that certain strings have been compressed.

I think if we want to secure the JSESSIONID= whatever piece (probably the most important thing in the payload), then we want a ton of JSESSION=random_stuff strings so that the compression will compress "JSESSIONID=" into a pointer but not anything that might come after "JSESSIONID="... so maybe putting an X-NO-BREACH header in with 50 random variants of JSESSIONID=random_stuff so that the compression will only create a pointer for "JSESSIONID="

David Pollak

unread,
Aug 2, 2013, 5:13:17 PM8/2/13
to liftweb
If you randomly pad the page with bytes, it can help because no two responses will be the same length, ever. So, if we create the X-NO-BREACH header with a random number of JSESSIONID strings with a random set of values with a random set of lengths for each value, then the ability to sample a lot of responses for length changes should be frustrated.

Should I add this to the code I wrote last night?

Diego Medina

unread,
Aug 2, 2013, 5:24:35 PM8/2/13
to Lift
On Fri, Aug 2, 2013 at 5:13 PM, David Pollak <feeder.of...@gmail.com> wrote:
If you randomly pad the page with bytes, it can help because no two responses will be the same length, ever. So, if we create the X-NO-BREACH header with a random number of JSESSIONID strings with a random set of values with a random set of lengths for each value, then the ability to sample a lot of responses for length changes should be frustrated.

Should I add this to the code I wrote last night?


sounds like a reasonable mitigation, one thing though, could we make the actual string "JSESSIONID" a configuration value, iirc, you can rename the name of it on your container.

Thanks
 
  Diego 

David Pollak

unread,
Aug 2, 2013, 5:25:53 PM8/2/13
to liftweb
On Fri, Aug 2, 2013 at 2:24 PM, Diego Medina <di...@fmpwizard.com> wrote:



On Fri, Aug 2, 2013 at 5:13 PM, David Pollak <feeder.of...@gmail.com> wrote:
If you randomly pad the page with bytes, it can help because no two responses will be the same length, ever. So, if we create the X-NO-BREACH header with a random number of JSESSIONID strings with a random set of values with a random set of lengths for each value, then the ability to sample a lot of responses for length changes should be frustrated.

Should I add this to the code I wrote last night?


sounds like a reasonable mitigation, one thing though, could we make the actual string "JSESSIONID" a configuration value, iirc, you can rename the name of it on your container.

Sure. Do you want to take a crack at it or should I?

Diego Medina

unread,
Aug 2, 2013, 7:59:46 PM8/2/13
to Lift
sure, I'll work on it tonight

Richard Dallaway

unread,
Aug 3, 2013, 7:47:32 AM8/3/13
to liftweb
Interesting attack. Hopefully we'll learn more about the mechanisms soon. 

gzip uses deflate, so it should be possible to measure the effectiveness of adding more JSESSIONID= values using the shell.

From what I've understood, we'd want an attacker to receive the same information if they try to guess with a correct or incorrect JSESSIONID string. 

I tried that from the shell using gzip: https://gist.github.com/d6y/6146157

I only used three decoy JSESSION values, so it looks like there is information leak in that test, so I suspect a better test would be to include a lot of JSESSION= values (a significant number of permutations for the start of the value, at least).

Alternative: I wonder if duplicating the real JSESSION ID, but changing a character N times, would mean an attacker would get N false-positive matches?  I suppose N would have to be pretty huge to put off an automated attack.

Not sure - I need to re-read that article a few more times....

Richard

Diego Medina

unread,
Aug 3, 2013, 9:40:39 AM8/3/13
to Lift
Hi Richard,

the attacker would not add this to the header:

Set-Cookie: JSESSIONID=b"

it would add this:

JSESSIONID=b

making that change to your script I get (for the confirmed case):

method  crc     date  time           compressed        uncompressed  ratio uncompressed_name
defla 016cd147 Aug  3 09:31                 309                 414  32.9% test.1
defla 2d5e0320 Aug  3 09:31                 312                 426  34.0% test.2
defla a4b099f7 Aug  3 09:18                 312                 427  34.2% test.3
defla 4974fb89 Aug  3 09:19                 312                 428  34.3% test.4
defla 20f3fb80 Aug  3 09:32                 313                 429  34.3% test.5
defla 89c5301a Aug  3 09:34                 312                 429  34.5% test.6


test.1 had nothing added

test.2 added:
JSESSIONID=
test.3 added:
JSESSIONID=b
test.4 I added:
JSESSIONID=b5
test.5 added: 
JSESSIONID=b5a //triggering a wrong/ different compressed size, which would tell the attacker a is not the 3rd letter/number to try
test.6 had
JSESSIONID=b5z // which is the correct 3rd letter, and notice how the compressed size went back down.


Another post that has more details is this one


Now, about our possible mitigation, we would not be adding several copies of the string JSESSIONID=zye, but instead we would add just one, but with different strings on each page load/request sent to the server.

But I think there is a small possibility that the attack can still go through, if by any chance, the attacker's first try (like in test.3), he/she adds the correct first character to the JSESSIONID. In that case, they would stop trying any other character for the first position, and move on to the second, so, if the real sessionid is

abc

and on the first page load we send

abc //the real one
bca //randomly generated one

but the attacker tried a first, our fake session id makes no difference.

so he moves to the second position,

tries ab

we went random zxy

again, our fake sessionid makes no difference. Granted you have to be pretty unlucky as a site owner, but I wonder if this would mean we want to try something different in our defense against this attack.

P.S. Thanks for the shell version of BREACH :)

Thanks

  Diego






Richard Dallaway

unread,
Aug 3, 2013, 1:30:55 PM8/3/13
to liftweb
Thanks Diego! In effect, what we do is mitigate by making the response size random, so an attacker would have to sample each guess a number of times to make progress. 

Presumably, the JSESSION part is irrelevant, just as long as the overall length changes.

Looking back at the original article: "In general, any secret that's relevant [and] located in the body, whether it be on a webpage or an Ajax response, we have the ability to extract that secret in under 30 seconds, typically."  I've seen some use the quote to deduce that, although the focus is on SSL, it's the HTTP compression applied to the body (not headers), that's the way in here (e.g., http://security.stackexchange.com/questions/39925/breach-a-new-attack-against-https-what-can-be-done/39953#39953)

If that's the case, we'd need to pad the body somehow, in some general way, not via a header?  I'm unsure at the moment.

Richard

David Pollak

unread,
Aug 4, 2013, 11:45:18 AM8/4/13
to liftweb
The JSESSIONID part is important because we want the compression algorithm to create a token for "JSESSIONID=" rather than "JSESSIONID=ab" By repeating the string by having lots of differences after the "=", we make it harder to fish for the session token.

ti com

unread,
Aug 4, 2013, 4:49:07 PM8/4/13
to lif...@googlegroups.com
hi. maybe it's possible also, to detect the pattern of someone doing the attack?

Diego Medina

unread,
Aug 4, 2013, 6:08:09 PM8/4/13
to Lift
almost got it working, first implementation ended up giving me a stackoverflow :). I'll post again once the testing looks good

Thanks

  Diego


On Sun, Aug 4, 2013 at 4:49 PM, ti com <tic...@gmail.com> wrote:
hi. maybe it's possible also, to detect the pattern of someone doing the attack?

--
--
Lift, the simply functional web framework: http://liftweb.net
Code: http://github.com/lift
Discussion: http://groups.google.com/group/liftweb
Stuck? Help us help you: https://www.assembla.com/wiki/show/liftweb/Posting_example_code
 
---
You received this message because you are subscribed to the Google Groups "Lift" group.
To unsubscribe from this group and stop receiving emails from it, send an email to liftweb+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Diego Medina

unread,
Aug 4, 2013, 6:39:27 PM8/4/13
to Lift
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:



$ curl  -v -XHEAD http://127.0.0.1:8080
* 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


Viktor Hedefalk

unread,
Aug 5, 2013, 8:29:31 AM8/5/13
to lif...@googlegroups.com
Interesting stuff!

As far as I can tell from http://breachattack.com/:

key wording is "Reflect user-input in HTTP response bodies". So it's
the compressed size of the response coming back that is measured.
Wouldn't it therefore be enough to just add some randomized padding?

A X-NO-BREACH header with 0-20 random characters in every response?
Then it wouldn't be possible to do incremental guesses by looking at
the size of the responses.

I think this should probably be inside a an extra servlet filter or
even better, handled by nginx/apache?

If I misundestood this and its the generated REQUEST that is measured,
then we're screwed anyway since the intruder could just find all our
JSESSIONID values and try them one by one, right? Only really gets
lineary harder per extra JSESSIONID?

Thanks,
Viktor

Diego Medina

unread,
Aug 5, 2013, 10:35:45 PM8/5/13
to Lift
Ok, I sent an initial pull request

It has a flag you can set if you want to disable it. I added this hoping that as time goes by, nginx or others will come up with either the same technique or something better.


A X-NO-BREACH header with 0-20 random characters in every response?
Then it wouldn't be possible to do incremental guesses by looking at
the size of the responses.

I think this should probably be inside a an extra servlet filter or
even better, handled by nginx/apache?



 

If I misundestood this and its the generated REQUEST that is measured,
then we're screwed anyway since the intruder could just find all our
JSESSIONID values and try them one by one, right? Only really gets
lineary harder per extra JSESSIONID?


I read it a few times, and I think that the technique looks at the "response" so not the request., so padding the response should do it.


Thanks

  Diego

Łukasz Kuczera

unread,
Aug 6, 2013, 1:06:47 AM8/6/13
to lif...@googlegroups.com
Skimmed through the problem and suggested solutions also contain length hiding by random bytes padding to the response. Could this be simple white characters padding as additional mitigation to Diegos solution ?

Viktor Hedefalk

unread,
Aug 6, 2013, 3:28:19 AM8/6/13
to lif...@googlegroups.com
Random padding was only at place 6 in efficiency though:
http://breachattack.com/.

Probably due to the fact that an intruder can use simple statistics if
allowed enough tries. I mean, if we add randomized length padding,
they could still figure out which character is the most probable if
they try each character enough times. And the size of the problem only
grows linearly against the max length of padding too, right? And we
don't want to bloat our responses with too much data. Maybe combined
with rate-limit it could be good enough. Rate-limit is probably always
a good idea anyway? I mean to protect against DOS-attacks.

Anyone know of say an nginx-plugin that does that post-ssl termination
based on say an header like JSESSIONID? So I can say: Only allow 100
requests / minute per JSESSIONID?

Anyway, half-assed tries are better than nothing. At least like
putting up an "Alarm"-sticker. Add a "Dog warning" sign too and
they'll go next doors :)

Cheers,
Viktor

Viktor Hedefalk

unread,
Aug 6, 2013, 3:43:54 AM8/6/13
to lif...@googlegroups.com
This module should probably do it:

http://wiki.nginx.org/HttpLimitReqModule

I suck at nginx config, but I guess something like:

limit_req_zone $cookie_JSESSIONID zone=one:10m rate=1r/s;

server {
...
limit_req zone=one burst=5;
}


might work?

Thanks,
Viktor

Viktor Hedefalk

unread,
Aug 6, 2013, 5:18:30 AM8/6/13
to lif...@googlegroups.com
Just realized that I'm not effected by this since I haven't used any
gzip in my stuff, probably should though to lower my EC2-bill :)

Anyway, I put a random header thing in a filter:

https://github.com/hedefalk/random-response-padding

I figured that would probably be the easiest way to get it into all my
webapps. Just some lines in pom/sbt-file plus web.xml. And then I'll
have to look into the rate-limit thing in nginx.

Warning:
* Haven't tried it yet (it just compiles)
* I wasn't too worried about "really random".
* I'm in a bad mood since I just wrote some Java. Man, did that hurt!

Maybe useful for someone.

Cheers,
Viktor

Diego Medina

unread,
Aug 6, 2013, 9:12:06 AM8/6/13
to Lift
On Tue, Aug 6, 2013 at 5:18 AM, Viktor Hedefalk <hede...@gmail.com> wrote:
Just realized that I'm not effected by this since I haven't used any
gzip in my stuff, probably should though to lower my EC2-bill :)

Anyway, I put a random header thing in a filter:

https://github.com/hedefalk/random-response-padding


nice, one question (and I think the answer is yet but ...), does the filter add the cookie to all request?
Also, is there a way to have the filter add a header, but not necessarily a cookie? using a cookie means that the real clients will also send this on each Request they send.

Thanks

  Diego

Antonio Salazar Cardozo

unread,
Aug 6, 2013, 11:35:16 AM8/6/13
to lif...@googlegroups.com
I've been reading more discussion on this, and it also seems like random request padding takes care of the cookie attack with or without the cookie id in front of it, with the caveats Viktor pointed out. Our CSRF strategy also isn't vulnerable as far as I can tell (because field ids are random per page load).

The best solution that preserves compression seems like it would be at the container level, with a changing session id per request. Can we add a step like that at our level? Require two cookies, one container-based and one Lift-based, to properly identify a session, and keep an LRU cache in each session of the last, say, 1000 valid cookies for a given session so as to avoid invalidating pages if possible. Just thinking out loud here, so feel free to tell me if I'm totally off base.
Thanks,
Antonio

Viktor Hedefalk

unread,
Aug 6, 2013, 11:47:26 AM8/6/13
to lif...@googlegroups.com
> Also, is there a way to have the filter add a header, but not necessarily a
> cookie? using a cookie means that the real clients will also send this on
> each Request they send.

Heh, should of course just be a header, not a cookie. Nice catch!

Fix: https://github.com/hedefalk/random-response-padding/commit/746ddeea150e2a1b288a4a817dac61b9d996fc5f


> nice, one question (and I think the answer is yet but ...), does the filter
> add the cookie to all request?

I guess you'd have to decide that with the mapping. Added to readme:
https://github.com/hedefalk/random-response-padding/commit/2e39e6ced9c3c162c5c820aca9d70adab9e8a2b5

Viktor Hedefalk

unread,
Aug 6, 2013, 11:48:27 AM8/6/13
to lif...@googlegroups.com
I monitored my traffic against a webapp and I realized that the
Set-cookie: JSESSIONID is only there on the initial response. Then of
course the cookie is sent with each request from the client, but the
server only send a set-cookie header ONCE per created session. So if
the breach is limited to length-monitoring on responses I don't
actually think we have any problem with session-stealing?

Of course there could be other sensitive data though.

Thanks,
Viktor

Aleph-1

unread,
Aug 6, 2013, 11:55:41 AM8/6/13
to lif...@googlegroups.com
Just a user here, but, I feel that the key strengths of Lift as web framework is its resilience to web attacks. That being said; relying on just container security may not be the best approach. Having a servlet filter that takes care of this would be the least intrusive approach. Lift users don't necessarily need to be aware of the mechanics of this and basically the filter would work to obfuscate the/pad the response with random gibberish.

OTOH perhaps as an industry we need to re-evaluate still using technology so old that it basically is brought to its knees when combined with more recent improvements. ;)

Diego Medina

unread,
Aug 6, 2013, 2:01:18 PM8/6/13
to Lift
I look forward to getting the PoC from http://breachattack.com/ to try it out. But now I watched the video they have there and the "secret" they got was in the html page that the server sent the user back. We don't have that issue because we don't store any data of that sort on the page. Our form names are re-generated on each request, so they can't get that either. And it doesn't seem that they read the http header information, but I could be wrong there.

So I also think that we don't need to have the JSESSIONID string in the "padded" header, and I kind of like the idea that Vicktor brough up, of maybe having one more filter shipped with Lift, that wil ladd the header,regardless of Lift processing the request or not (see my earlier comment that thie current path I sent does not add a header for requests sent to a js file in the webapp folder)

Thanks

  Diego


Diego Medina

unread,
Aug 6, 2013, 2:04:18 PM8/6/13
to Lift
sorry, meant Viktor 

David Pollak

unread,
Aug 6, 2013, 2:25:00 PM8/6/13
to liftweb
Let's wait for the testing tools and see what we can do to automatically protect.
Telegram, Simply Beautiful CMS https://telegr.am
Lift, the simply functional web framework http://liftweb.net
Reply all
Reply to author
Forward
0 new messages