CSC/ECE 517 Fall 2009/wiki1a 10 wolf27-Manhattan
Ruby and Java from a Security Perspective
Students: wolf27 and Manhattan
Introduction
It is widely held that the three “pillars” of a secure system are confidentiality, availability, and integrity. That is, is a user's data safe from unauthorized viewing/disclosure; is the user's data available when they want it; and is the data the same as what they expected. There are many techniques, algorithms, and methods available to ensure these three pillars. The language used to develop the software on a secure system can play a major role in how effective the algorithms, etc. help protect the confidentiality, availability, and integrity of the secure data. This article compares two popular languages, Ruby and Java, with respect to some of the features each language has that aids in developing a secure software system.
In Java, Security can be considered in three contexts:
1) Virtual Machine Security
2) Application Security
3) Network Security
The key features regarding the above security issues are discussed in this article.
Ruby
“Safe Levels” in Ruby
Arguable one of Ruby’s most powerful features with respect to security is its “Safe Levels”. Ruby implements five levels of data checking to help prevent what its calls “tainted” variables from being used in other parts of the code where the system could be exploited.
All objects in Ruby are marked as “tainted” if they are derived from some external source. For example, an object is tainted if it read using ‘gets’, read from a file, is an environment variable, etc. Each Ruby object contains a method named, ‘tainted?’ as well as methods named ‘taint’ and ‘untaint’.
The ‘tainted?’ method is used to return a boolean value to indicate if the variable is tainted or not. Likewise, the ‘taint’ method is used to taint an untainted object and ‘untaint’ is used to remove a tainted label from an object. The code below shows a simple example of a String object becoming tainted.
#Tainted vs. Untainted >> my_string = String.new # A new string is created => "" >> my_string.tainted? # The string is not tainted; there has been no external influence => false >> my_string = "Hello" # Still no external influence => "Hello" >> my_string.tainted? => false >> my_string = my_string + gets #Since part of the string now came from the console, it is tainted World! => "Hello World!\n" >> my_string.tainted? => true
To help manage tainted and untainted objects, Ruby defines a built in constant named $SAFE that allows the user to define what “safe level”. The $SAFE variable is simply set at the beginning of the program in the way any other variable would be. For example,
$SAFE = 3
sets Ruby’s “safe level” to 3.
Each safe level allows (or disallows) certain actions, primarily based on whether an object is tainted or untainted. There are a few other operations that are also disallowed by Ruby at the various safe levels. Note that Ruby’s default safe level is 0, which means that all operations are allowed regardless of the the tainted status of the object. The book “Programming Ruby” [1] provides an excellent table to use as reference for the actives allowed and disallows at each safe level. Below are some highlight of each safe level.
$SAFE = 0 Default “Safe Level” No additional security features. All operations allowed on all untainted and tainted variables. $SAFE >= 1 Calling the ‘glob’ or ‘eval’ methods on tainted strings is disallowed. Loading a file using a string that is tainted is disallowed. Executing system commands using a tainted string is disallowed. $SAFE >= 2 Loading a file from a source that is writable by ‘world’ is disallowed. $SAFE >= 3 All object created are tainted and no object may not be untainted using the untaint method. $SAFE >= 4 Essentially creates a Sandbox (an isolated, safe, place to run untrused code). Writing to files is disallowed. Modifying global variable is disallowed.
Ruby’s Unbounded Polymorphism (a.k.a. Duck Typing) and Security
Ruby’s unbounded polymorphism functionally is arguably very powerful. However this powerful functionally could also create a potential problem with code developed. Since Ruby is a dynamically typed language, it is not possible, at compile time, to check and ensure that objects being used are of the expected type. A programmer can use the 'class' method of any class to check the type of a class, but Ruby itself does not require it. If an object looks like another object, it can be treated as that object in Ruby.
If not managed properly, an adversary or malicious piece of code could be executed unintentionally because of Ruby’s unbounded polymorphism. Consider the code below, where the developer fails to check the incoming class type.
Class Good_code def good_method ... # some good code end
def another_good_method ... # some more good code end end
Class Bad_code def good_method ... #some BAD code, that looks good end
def another_good_method ... #some more bad code end end
def my_method(good_code) good_code.good_method good_code.another_good_method end
my_method(Bad_code.new)
In the example above, the malicious code will run, even though the developer of my_method thought and intended to only run code from the ‘good_code’ object. The developer must take greater caution when implementing code that takes another objects as to unintentionally execute a different object.
Automatic Bounds Checking
Ruby automatically checks bounds of data structures such as arrays. Bounds checking is very important to creating a secure system. If a user, intentionally or unintentionally, is able cause code to access an array index outside of the array, the system may crash, other data in memory may be overwritten, or unexpected (possibly private) data may be returned.
Cryptographic Libraries
The ability to perform basic cryptographic functions is necessary to help ensure the confidentially of data stored on a system and the integrity of data (by cryptographic signature/hash). Ruby provides, built in, only a very limited set of cryptographic functionally. One such function is the String class's crypt method.
There are several open source library for performing cryptographic functions on objects available for Ruby such as crypt [2]. The crypt library provides access to such encryption algorithms AES, Blowfish, and IDEA.
Survey of Current and Past Publish Security Vulnerabilities
The official Ruby site [3] contains a security section that list a number of current and past security vulnerabilities found.
Java Security Features
Programs are not allowed to access arbitrary memory locations
For example, casting between an
int and an Object is strictly illegal in Java.
Variables may not be used before they are initialized
If a program were able to read the value of an uninitialized variable, the effect would be the same as if it were able to read random memory locations. A Java class wishing to exploit this defect might then declare a huge uninitialized section of variables in an attempt to snoop the memory contents of the user's machine. To prevent this type of attack, all local variables in Java must be initialized before they are used, and all instance variables in Java are automatically initialized to a default value.
Objects cannot be arbitrarily cast into other objects
Consider the below example.
public class CreditCard {
private String acctNo;
} public class CreditCardSnoop {
public String acctNo;
}
Then the following code will not be allowed execute:
CreditCard cc = Wallet.getCreditCard( );
CreditCardSnoop snoop = (CreditCardSnoop) cc;
System.out.println("Ha! Your account number is " + snoop.acctNo);
Java does not allow arbitrary casting between objects; an object can only be cast to one of its
superclasses or its subclasses.
To satisfy the compiler code can be changed as follows:
Object cc = Wallet.getCreditCard( );
CreditCardSnoop snoop = (CreditCardSnoop) cc;
In this case, the virtual machine will throw a ClassCastException when the snoop variable is assigned to thwart the attack.
The Bytecode Verifier
This is used to check whether the code compiled by the compiler is a legal java code. The Java verifier examines all such application byte code and, using a fancy set of heuristics, identifies code that doesn't play by the rules. Once byte code is verified, the virtual machine knows that it's safe to execute.
The verification process consists primarily of three types of checks:
1.Branches are always to valid locations.
2.Data is always initialized and references are always type-safe.
3.Access to "private" or "package private" data and methods is rigidly controlled.
The first two of these checks take place primarily during the "verification" step that occurs when a class is loaded and made eligible for use. The third is primarily performed dynamically, when data items or methods of a class are first accessed by another class.
For example, consider the following classes
public class CreditCard {
public String acctNo = "0001 0002 0003 0004";
}
public class Test {
public static void main(String args[]) {
CreditCard cc = new CreditCard( );
System.out.println("Your account number is " + cc.acctNo);
}
}
If we run this code, we'll create a CreditCard object and print out its account number. If we change the definition of acctNo to be private and recompile only the CreditCard class. We then have two class files and the Test class file contains Java code that illegally accesses the private instance variable acctNo of the CreditCard class and hence will not be allowed to execute.
The bytecode verifier is an internal part of the Java virtual machine and has no interface, i.e programmers cannot access it and users cannot interact with it. The verifier automatically examines most bytecodes as they are built into class objects by the class loader of the virtual machine.
Specifically, the bytecode verifier is used to check the following:
The class file has the correct format.
Final classes are not subclassed, and final methods are not overridden.
Every class has a single superclass.
There is no illegal data conversion of primitive data types.
There are no operand stack overflows or underflows.
Java Cryptographic Extension(JCE)
JCE provides a framework and implementations for encryption, key generation and key agreement, and Message Authentication Code (MAC) algorithms. Support for encryption includes symmetric, asymmetric, block, and stream ciphers.
Java Authentication and Authorization Service (JAAS)
JAAS , as the name suggests is used for
1) For authentication of users, to reliably and securely determine who is currently executing Java code, regardless of whether the code is running as an application, an applet, a bean, or a servlet.
2) For authorization of users, to ensure they have the access control rights (permissions) required to perform the actions.
Java Secure Socket Extension (JSSE)
JSSE enables secure Internet communications. It provides a framework and an implementation for the Socket Security Layer(SSL) and Transport Layer Security(TLS) protocols and includes functionality for data encryption, server authentication, message integrity, and optional client authentication.
The Secure Sockets Layer (SSL) and Transport Layer Security(TLS) protocols are used to protect the privacy and integrity of data while it is transferred across a network.
Conclusion and Summary
Ruby and Java both contain a wide array of features that aid in designing and building a secure software system. It is very difficult to conclude that one language is more “secure” than the other, since they both have individual strengths and weaknesses.
Ruby and Java both have widely supported cryptographic libraries. Well developed, mature, cryptographic libraries are necessary for developing a secure software system; without them, the developer must rely on external resources (e.g. the operating system, TCP/IP stacks, etc.) to securely store data, ensure the integrity of data, etc. Since neither Ruby nor Java have strong “built-in” support, the availability of well developed cryptographic libraries greatly enhances the security of Ruby and Java. Java’s libraries are more well developed, but Ruby’s libraries, such as crypt, are available and development on them continues.
Ruby contains a very unique security feature; the concept of “Safe Levels”. Ruby’s “Safe Levels” can be set to allow the language to passively check various security related conditions. Java does not contain such a feature, but the Java Byte Code Verifier checks that the Java code is legal.
Both languages automatically check array bounds, which is very important to ensure that private data is not accidentally compromised in case an array index that is out of bounds is input.
References and Resources
[1] Hunt, Andrew and Thomas David. #"Programming Ruby, The Pragmatic Programmer's Guide" [1].
[2] "Crypt" Library Website: http://crypt.rubyforge.org/
[3] Ruby Website, Security Sections: http://www.ruby-lang.org/en/security/
[4] Scott Oaks. "Java Security".
[5] Sun website, Security Sections: http://java.sun.com/javase/technologies/security/
[6] Byte code verifier, http://java.sun.com/docs/white/langenv/Security.doc3.html