My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Saturday, October 13, 2007

[COW] Web RAM - a JavaScript function to share safely strings

This time my COW is really strange as Web concept ... a total size of 256 bytes (seems hilarius?) to share strings inside each browser window.

This idea is not so new, I wrote JSTONE few months ago to do something similar, however it seems that JSTONE requires dedicated JSON implementation or something similar to work correctly.

Now, let me explain why I wrote this tiny function and for what it should be used for.


Random Access Memory


window.name is a string always present every time We open a window.
The cool, but not so secure, behaviour of this property is that this persists even if We change site or domain or We refresh page using F5.
While We use the same window, window name will be available, seems cool?
RAM function is capable to save a lot of informations using a Random Access strategy, where the "memory address" is created by yourself, using your personal keyword.
Both keywords and saved data can contain every kind of char excepted for \x00 one (End Of String or null char).

It's based on a sort of automatic namespace management but your keyword is not really your one but as is for libraries, everyone should use a dedicated namespace without conflicts, don't You agree?
Finally, window.name can save strings of hundred of thousand chars, I didn't find a limit for saved informations but as is for real RAM, if You turn off your PC (in this case browser window) it will be automatically resetted.


Why RAM function ?


These days I'm testing my last creation, packed.it, to find problems or to test decompression speed with big data size.
One thing I tough is that with packed code (using packed.it, packer or similar compressors), size is not the real problem as decompression CPU overload is.
I was try to find a way to cache or speed up unpack operation but every test I did failed (bad results).
That's why I created this simple function, to test clear code evaluation performances instead of runtime decompression, allowing users to unpack every kind of code 1 time each window instead of every time.
This means that during navigation in the same window, a new user will unpack code only first time but after first clear code creation procedure, You sohuld save them inside window.name and evaluate them in every other page viewed inside same browser window.
At the same time, if user will leave your page and then come back using back button it will have again code stored if external site didn't modify window.name or modify them without RAM function.
Finally, I could save an address, called for example jQuery :-) ... saving clear source and check them every time one page that use them is called:

eval(RAM("jQuery") || RAM("jQuery", function(p,a,c,k,e,d){/* ... */}));

Every link in the same window and every site viewed after this one should use same strategy to perform packed sources evaluations faster than ever because decompression will be performed only first time ... not a real solution but at least a simple way to optmize, a bit, navigation and decompression speed?

So, RAM function should be used to ... ?



  • save each kind of informations rappresented as a string (JSON as every other kind of string)

  • share common libraries between different pages / sites

  • evaluate big strings if compressor believes in RAM function



I'll try to do some test with packed.it, probably as option.
The battle is between a little overload of 256 bytes against 1 to 5 seconds for really big packed sources ... uhm ... what do You think is better?

Do I forget something? Uhm, both keywords and sources could use SOH char too (\x01) without problems but each SOH will be duplicated inside window.name and replaced before You'll read them again to preserve string as is when You get them using RAM.


<script type="text/javascript" src="RAM.js"><!--// Function RAM //--></script>
<script type="text/javascript"><!--//
eval(
/* from second time You visit this page */
RAM("my personal key") ||

/* only first time You visit this page */
RAM("my personal key", "alert('Hello World')")
);
//--></script>




Update 2007/10/15
DarkCraft sayd that NoScript doesn't allow many chars inside window.name when You change domain, reload the page or do something similar.

I wonder why NoScript guys allows few chars in window.name (someone shoud use them to save user and passwords, for example) instead of clear one ... however, RAM should work without problems with NoScript enabled browsers too, just using a try catch statement.

// RAM example for NoScript browsers
try{
eval(RAM("my lib")||"this=0")
}catch(e){
eval(RAM("my lib", (function(p,a,c,k,e,d){return 'alert("hello my lib")'})()))
};

If You use this piece of code NoScript enabled browsers will just "decompile" each time saved informations, if these contains special chars too.
Please remember that RAM goal is to believe in itself, it's quite obvious that if some malicius site/page/code replace informations or uses malicius version of common libraries RAM function should become totally insecure.

The best practice I could suggest is to use a "private mutable key":

try{
eval(RAM("jQuery-1.2.1-" + location.hostname)||"this=0")
}catch(e){
eval(RAM("jQuery-1.2.1-" + location.hostname, (function(p,a,c,k,e,d){return 'alert("hello my lib")'})()))
};

That in a session based enviroment should be something like this code:

try{
eval(RAM("jQuery-1.2.1-<?php echo session_id();?>")||"this=0")
}catch(e){
eval(RAM("jQuery-1.2.1-<?php echo session_id();?>", (function(p,a,c,k,e,d){return 'alert("hello my lib")'})()))
};

To be sure library is shared only during a valid session.
A random cookie should be good enough too but paranoia is always with us :D

Finally, in packed.it I'll add a RAM option based on project name, different each time.
These practices disable cross site library sharing but I hope one day malicious site, developers, will be banned from the Web (just a "little utopia")

10 comments:

Hedger Wang said...

This is amazingly simple!
I love it and thanks for sharing!.

Anonymous said...

Looks like the guys making NoScript were a bit concerned about the security of window.name ;)
When you change the domain, reload the page or press enter in the urlbar they "clean up" window.name and remove chars like ")'?=}], so basically anything you would need for working javascript code.

If you just browse on the page by clicking links or disable NoScript it works like in any other browser.

Andrea Giammarchi said...

oops, darkcraft I didn't know about NoScript project:
http://noscript.net/

However, do they think that window.name should be cleaned allowing scripts to save password instead of everything else?

JavaScript should be used both to do good things and bad things as every other program language / application.

If You're worry about NoScript users (how many?) just use a try catch statement like this one:

try{
eval(RAM("my scritp")||"this=0")
}catch(e){
eval(RAM("my scritp", (function(){return 'alert("Hello World")'})()))
};

seems simple? It should be compatible with NoScript users too, isn't it? :-)

Anonymous said...

"(how many?)"
NoScript is a recommended(By Mozilla) Add-on for Firefox and since it's also very useful to block Ads too, many people are using it.

@Code
Yes it works, but there's the need for another small function.
NoScript also converts \x01 and so on to " ", so each time you visit the page and window.name is cleaned up, RAM will append more and more of the cached stuff to the string and you get a ton of garbage infront of it.

Example(with Example Syntax for seperating keys/values)
1. Visit, Caching some code
|key||alert('test')|
2. Visit(NoScript cleanes up and we're caching again)
key alert test |key||alert('test')|
3. Visit(NoScript cleanes up again..)
key alert test key alert test |key||alert('test')|

And so on, now imagine that with a 50 or 100K JS Framework :O

My solution, just add a small key with " as data and check if it's there, if not set window.name to "" and recache, if it's there just do nothing.

//Again using my syntax(yes you can't use \x01 as data but I was a bit lazy, maybe i fix it ;)
//Check for the ff key with " as value
//If it's there do nothing, otherwise clear window.name and set it
//Returns true if the cache is valid and false when not
function Foxy(){
return /ff\x01\x01"\x01/.test(window.name) && true || (window.name = '') || Cache('ff', '"') && false;
}

var CacheOK = Foxy();
eval(Cache('test') || Cache('test', 'alert("hello world")'));

Basically, I think NoScripts approch is good, it makes the use of window.name secure(yes it screws it up sometimes but it's still better then getting exploited by someone)
Only the logic behind "I'm cleaning it up" and "I don't cleaning it up" is scary e.g. open a page, and press F5(without browsing somewhere on that page) it will clean up(but not on file://) open a page, click a link to somewhere on the page and it won't. And it's not a referrer check, it really checks if you just openend the page or not.

PS: A note on "While We use the same window..." actually it's "While we use the same Tab", at least in FF2, Opera9, Safari 3 and IE7 where I tested it :)

Andrea Giammarchi said...

Thank You DarkCraft for these info, however for same window I mean same window, two tabs are two different windows inside same browser (window), isn't it ? :-)

I suppose the better thing that NoScript project should do is to add a global scope variable that indicates when NoScript is installed and enabled.

eval(window.NoScript&&RAM("lib")||RAM("lib",function(){ ... }))

problem solved for everyone ;-)

Anonymous said...

And then websites start to block NoScript users since they can block the ads :/

Not the best solution I think, maybe browser vendors should implement a cache or something like that for web pages. So you can store Javascript in there or other stuff. Maybe Google Gears could be used for that, I'll give it a try.

Andrea Giammarchi said...

I just thought about Gears but its policy is more strict or secure than NoScript one.

At the same time, Gears requires a dedicated plugin (however, FireFox 3 should has a native SQLite wrapper) but now, using Gears, You can't share in the same domain / subdomains and in different sites.

RAM is simple, as JSON is, but unlikely there will be always malicious scripts as malicious programs (web or desktop, it's the same).

For RAM purpose I suppose an embed Flash MX file, using Shared Object to store data, should be a better choice (Flash 6 is embedded in IE6 or greater and Flash plugin is available for many browsers, more than Gears plugin)

At the same time, I read about NoScript and its Flash block ... uhm, should We forget window.name behaviour for a program like NoScript?

I hope they will accept RAM purpose :-)

Anonymous said...

Can you create some sort of a demo for me/us?

Andrea Giammarchi said...

alert(RAM("random") || RAM("random", Math.random()));

This is a basic example. Write it in your page, press F5, the saved Math.random() number will be always the same. It's in RAM.

If you change site and site has the same script inside, you'll see again the same random number :)

Anonymous said...

This is a fantastic idea and piece of code! I came to RAM via a comment from Tomas Frank's SessionVars blog entry.
In fact, I like it so much I have integrated a slightly modified version in the next version of my monitoring utility: MessAdmin.
I have modified RAM as to detect NoScript tampering as per DarkCraft suggestion. I'll try to blog about that sometime.^^