├── .gitignore ├── LICENSE ├── README.md ├── config ├── blacklist-default ├── unit_test_config_1 ├── unit_test_config_2 └── unit_test_config_3 ├── mf ├── contrast.mf ├── safeois.mf └── test.mf ├── pom.xml ├── run_tests.sh └── src ├── main └── java │ └── com │ ├── akamai │ └── security │ │ ├── SafeObjectInputStream.java │ │ └── TestSafeObjectInputStream.java │ └── contrastsecurity │ └── rO0 │ ├── ObjectInputStreamVisitor.java │ ├── RO0Agent.java │ ├── RO0Config.java │ ├── RO0Transformer.java │ ├── ResolveClassController.java │ ├── ResolveClassMethodVisitor.java │ └── TestCases │ ├── BlacklistElement.java │ ├── IgnoreClassListElement.java │ ├── Test.java │ ├── TestMain.java │ ├── TestRO0Config.java │ ├── TestResolveClassController.java │ ├── UnlistedElement.java │ └── WhitelistElement.java ├── scripts └── stats.pl └── test └── java └── com └── contrastsecurity └── rO0 └── DeserializationTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | /.settings/ 10 | /.classpath 11 | /.project 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Contrast Security OSS 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of rO0 nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | contrast-rO0 2 | ======== 3 | 4 | A lightweight Java agent for preventing attacks against object deserialization 5 | like those discussed by [@breenmachine](http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/#websphere) 6 | and the original researchers [@frohoff and @gebl](http://www.slideshare.net/frohoff1/appseccali-2015-marshalling-pickles), affecting WebLogic, JBoss, Jenkins and 7 | more. 8 | 9 | ## Why did you make this? 10 | This is the only way to hotpatch your application against this vulnerability. 11 | Patching the code is possible (and this package contains SafeObjectInputStream to help), 12 | but for many applications, patches will not be available for a long time. Because of 13 | the nature of the vulnerability, some applications will have to re-architect their 14 | messaging completely. 15 | 16 | ## How do I use the library? 17 | 18 | 19 | ### JVM-wide fix 20 | 21 | Build the agent, first: 22 | ``` 23 | git clone https://github.com/Contrast-Security-OSS/contrast-rO0.git 24 | cd contrast-rO0 25 | mvn clean package test 26 | ``` 27 | The agent, contrast-rO0.jar, is now in the /target directory. Now you need to copy the contrast-rO0.jar into your classpath, and copy the configuration file somewhere you'll remember. 28 | 29 | The final step is to add the following JVM options to your server or application: 30 | ``` 31 | -javaagent:/path/to/contrast-rO0.jar -DrO0.reporting=false -DrO0.blacklist=true -DrOo.lists=filename 32 | ``` 33 | where 'filename' is the path to the default config file. 34 | 35 | Now you're safe serializing from known dangerous classes! 36 | 37 | 38 | ### Spot Fix 39 | 40 | If you'd prefer to spot fix your code, possibly because you need to use dangerous classes somewhere, or because you need to minimize stability risk, you can use the SafeObjectInputStream class. To use this class, you'll need to replace calls to ObjectInputStream in your code with calls to SafeObjectInputStream. 41 | 42 | When you use SafeObjectInputStream, you can either whitelist or blacklist classes - whitelisting is safer, but blacklisting has lower stability risk. If you want to use this, you don't need the java command line options listed above. Instead, construct your SafeObejctInputStream, tell it if you want to blacklist or whitelist, and add the whitelisted/blacklisted classes to the stream. NOTE: you'll have to repeat this everywhere in your code where you need to implement safe deserialization. 43 | 44 | ``` 45 | SafeObjectInputStream in 46 | = new SafeObjectInputStream(inputStream, true); // whitelisting mode 47 | // or 48 | // = new SafeObjectInputStream(inputStream, false); // blacklisting mode 49 | in.addToWhitelist(ClassThatIsSafeToDeserialize.getName()); 50 | in.addToWhitelist("com.my.SafeDeserializable"); 51 | // or 52 | // in.addToBlacklist(ClassThatIsDangerous.class); 53 | 54 | // then just use like normal 55 | in.readObject(); 56 | ``` 57 | 58 | ### Reporting on Serialization Usage 59 | The shim can be instructed to report when serialization occurs. This allows you to determine where in your application deserialization is actually occurring - assuming that you exercise the relevant functionality. 60 | 61 | To use this, built it just as described in "JVM-wide fix" but, use the following command line options instead: 62 | 63 | ``` 64 | -javaagent:/path/to/contrast-rO0.jar 65 | ``` 66 | 67 | ## What does it do? 68 | When protecting your application, the JVM-wide shim or the SafeObjectInputStream spot fix will throw a SecurityException if there is an attempt to deserialize a dangerous object. 69 | 70 | This represents the "last mile" of the exploit chain. The default blacklist contains the only publicly known proofs-of-concept, which are extremely unlikely to be used for legitimate purposes during deserialization. 71 | 72 | * org.apache.commons.collections.functors.InvokerTransformer 73 | * org.apache.commons.collections4.functors.InvokerTransformer 74 | * org.apache.commons.collections.functors.InstantiateTransformer 75 | * org.apache.commons.collections4.functors.InstantiateTransformer 76 | * org.codehaus.groovy.runtime.ConvertedClosure 77 | * org.codehaus.groovy.runtime.MethodClosure 78 | * org.springframework.beans.factory.ObjectFactory 79 | 80 | Though there likely exist other exploitable classes, they are difficult to find, and likely won't be part of any mass exploitation tool for a while. 81 | 82 | However, when they do become available, you can update your configuration file to include these classes in your blacklist. Or, if you want to be more secure, you can use this tool in "reporting" mode to learn what you're deserializing, and then specify a whitelist of the classes that you want to allow. If you go with this approach, and you don't happen to include a dangerous class in your whitelist, then new research finding additional dangerous classes won't affect you. 83 | 84 | ## What's the synopsis of all configuration options and usage modes? 85 | 86 | ``` 87 | -DrO0.reporting=true Enable reporting (). Reporting is enabled by 88 | default. Intended for use if we ever use this 89 | package for protecting instead of just reporting. 90 | -DrOo.lists=filename Specify a configuration file. File specifies 91 | classes to include in the whitelist, blacklist, 92 | ignore classes list, or ignore stack trace list. 93 | FIRST MEANING 94 | CHAR 95 | + If the line starts with +, it’s included in the whitelist. 96 | Attempts to deserialize any classes not whitelisted will 97 | throw a SecurityException. Only has effect when 98 | -DrO0.whitelist is enabled. 99 | 100 | - If the line starts with -, it’s included in the blacklist. 101 | Attempts to deserialize these classes will throw a 102 | SecurityException. Only has effect when -DrO0.blacklist is 103 | enabled. 104 | 105 | $ If the line starts with $, it’s included in the “ignore 106 | class list”. Classes in this list will not be reported. 107 | Only has effect when -DrO0.ignoreClasses is enabled 108 | 109 | @ If the line starts with @, it’s included in the “ignore 110 | in stack list”. When a class is deserialized and the class 111 | specified by this line is in the stack, don’t report. Only 112 | has effect when -DrO0.ignoreStack is enabled. 113 | 114 | -DrO0.whitelist=true Enable whitelisting. Classes not included in the 115 | config file as whitelisted will not be allowed to 116 | deserialize. You can enable this at the same time as 117 | blacklisting. See the section on "enabling both 118 | blacklisting and whitelisting at the same time" for 119 | details. 120 | 121 | -DrO0.blacklist=true Enable blacklisting. Classes included in the config 122 | file as blacklisted will not be allowed to 123 | deserialized; all other classes will deserialize 124 | normally. You can enable this at the same time as 125 | blacklisting. See the section on "enabling both 126 | blacklisting and whitelisting at the same time" for 127 | details. 128 | 129 | -DrO0.ignoreClasses=true Used to quiet down the tool when it’s doing 130 | reporting. Classes or packages listed as ignored 131 | will not be reported upon. Only useful if reporting 132 | is enabled. Can be used in combination with 133 | -DrO0.ignoreStack. 134 | 135 | -DrO0.ignoreStack=true Used to quiet down the tool when it’s doing 136 | reporting. If the specified class or package is 137 | in the stack during deserialization, that 138 | deserialize attempt will not be reported upon. 139 | For example, used to stop logging all memcached 140 | events if @com.danga.Memcached is included in the 141 | config file and ignoreStack is enabled., memcached 142 | should be much less pronounced in the logs. Likely 143 | to have noticeable performance impact. Can be 144 | specified in combination with -DrO0.gnoreClasses. 145 | Only useful if reporting is enabled. 146 | 147 | -DrO0.outfile=filename Used to control where output from this utility is sent. 148 | If no output is specified, it will go to System.out. If 149 | filename is specified, rO0 will try to write 150 | output to the specified location; if it cannot (for example 151 | if permissions disallow this) then rO0 will instead 152 | fall back to System.out. 153 | ``` 154 | 155 | ### Enabling both blacklisting and whitelisting at the same time 156 | This tool allows you to enable both blacklisting and whitelisting at the same time. 157 | If you do so, a class will only be allowed to deserialize if it's *both* on the 158 | whitelist and not on the blacklist. This is useful if you want to maintain a list 159 | of known dangerous classes that you never want to deserialize as backup in case 160 | someone accidentally or unknowingly adds that same class to the white list. 161 | 162 | ## Supported systems 163 | Although it's not tested on them all, the agent should work well on the following platforms: 164 | * Java 5-8 165 | * OpenJDK/HotSpot, JRockit, IBM 166 | 167 | ## Who made this? 168 | This project is sponsored by [Contrast Security](http://www.contrastsecurity.com/) and released under the BSD license for developers. It includes contributions from Akamai Technologies. 169 | 170 | ![Contrast Security Logo](http://cdn2.hubspot.net/hub/203759/file-2275798868-png/theme/Contrast-logo-transparent.png "Contrast Logo") 171 | -------------------------------------------------------------------------------- /config/blacklist-default: -------------------------------------------------------------------------------- 1 | # Default blacklist that contains all currently known dangerous 2 | # classes. 3 | -org.apache.commons.collections.functors.InvokerTransformer 4 | -org.apache.commons.collections.functors.InstantiateTransformer 5 | -org.apache.commons.collections4.functors.InvokerTransformer 6 | -org.apache.commons.collections4.functors.InstantiateTransformer 7 | -org.codehaus.groovy.runtime.ConvertedClosure 8 | -org.codehaus.groovy.runtime.MethodClosure 9 | -org.springframework.beans.factory.ObjectFactory 10 | -------------------------------------------------------------------------------- /config/unit_test_config_1: -------------------------------------------------------------------------------- 1 | ############ 2 | # HELP 3 | # 4 | # First character Meaning 5 | # -------------- ------- 6 | # # comment 7 | # - blacklist 8 | # + whitelist 9 | # $ ignore during reporting if CLASS matches 10 | # @ ignore during reporting if on stack 11 | +com.contrastsecurity.rO0.TestCases.WhitelistElement 12 | -com.contrastsecurity.rO0.TestCases.BlacklistElement 13 | $com.contrastsecurity.rO0.TestCases.IgnoreClassListElement 14 | -------------------------------------------------------------------------------- /config/unit_test_config_2: -------------------------------------------------------------------------------- 1 | ############ 2 | # HELP 3 | # 4 | # First character Meaning 5 | # -------------- ------- 6 | # # comment 7 | # - blacklist 8 | # + whitelist 9 | # $ ignore during reporting if CLASS matches 10 | # @ ignore during reporting if on stack 11 | @com.contrastsecurity.rO0.TestRO0Agent 12 | @com.contrastsecurity.Ro).TestResolveClassController 13 | -------------------------------------------------------------------------------- /config/unit_test_config_3: -------------------------------------------------------------------------------- 1 | ############ 2 | # HELP 3 | # 4 | # First character Meaning 5 | # -------------- ------- 6 | # # comment 7 | # - blacklist 8 | # + whitelist 9 | # $ ignore during reporting if CLASS matches 10 | # @ ignore during reporting if on stack 11 | @com.contrastsecurity.rO0.TestRO0Config 12 | -------------------------------------------------------------------------------- /mf/contrast.mf: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: Aaron Katz 3 | Agent-Class: com.contrastsecurity.rO0.RO0Agent 4 | Premain-Class: com.contrastsecurity.rO0.RO0Agent 5 | Boot-Class-Path: /Users/akatz/git/rO0/contrast-ro0/bin/contrast-rO0.jar 6 | Can-Redefine-Classes: true 7 | Can-Retransform-Classes: true 8 | Can-Set-Native-Method-Prefix: true 9 | 10 | -------------------------------------------------------------------------------- /mf/safeois.mf: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: Aaron Katz 3 | Main-Class: com.akamai.security.TestSafeObjectInputStream 4 | 5 | -------------------------------------------------------------------------------- /mf/test.mf: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: Aaron Katz 3 | Main-Class: com.contrastsecurity.rO0.TestCases.TestMain 4 | 5 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | com.contrastsecurity 5 | contrast-rO0 6 | 1.0.0-SNAPSHOT 7 | 8 | 9 | 10 | org.ow2.asm 11 | asm-debug-all 12 | 5.0.3 13 | 14 | 15 | junit 16 | junit 17 | 4.8.2 18 | test 19 | 20 | 21 | commons-collections 22 | commons-collections 23 | 3.2.1 24 | 25 | 26 | org.springframework 27 | spring-core 28 | 4.2.3.RELEASE 29 | test 30 | 31 | 32 | 33 | 34 | 35 | contrast-rO0 36 | 37 | 38 | org.apache.maven.plugins 39 | maven-shade-plugin 40 | 1.6 41 | 42 | 43 | package 44 | 45 | shade 46 | 47 | 48 | 49 | 50 | 51 | 52 | *:* 53 | 54 | com/contrastsecurity/rO0/** 55 | org/objectweb/** 56 | META-INF/MANIFEST.MF 57 | 58 | 59 | 60 | 61 | 62 | org.objectweb 63 | com.contrastsecurity.rO0.thirdparty.org.objectweb 64 | 65 | 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-jar-plugin 71 | 2.3.1 72 | 73 | 74 | 75 | 76 | com.contrastsecurity.rO0.RO0Agent 77 | 78 | 79 | 80 | com.contrastsecurity.rO0.RO0Agent 81 | com.contrastsecurity.rO0.RO0Agent 82 | ${build.finalName}.jar 83 | true 84 | true 85 | true 86 | ${maven.build.timestamp} 87 | 88 | 89 | 90 | 91 | 92 | maven-surefire-plugin 93 | 2.9 94 | 95 | false 96 | true 97 | 98 | -javaagent:${project.build.directory}/${build.finalName}.jar=rO0.reporting:false,rO0.blacklist:true,rO0.lists:${basedir}/config/blacklist-default 99 | 100 | 101 | 102 | 103 | verify-serialization-security 104 | integration-test 105 | 106 | test 107 | 108 | 109 | alphabetical 110 | false 111 | once 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "moving to ./bin" 4 | pushd bin 5 | 6 | echo "recompiling" 7 | javac ../src/main/java/com/contrastsecurity/rO0/*.java ../src/main/java/com/contrastsecurity/rO0/TestCases/*.java ../src/main/java/com/akamai/security/*.java -classpath ../lib/asm-5.0.4.jar:../lib/asm-commons-5.0.4.jar -d . 8 | 9 | echo "creating jar files" 10 | jar cfm contrast-test.jar ../mf/test.mf com/contrastsecurity/rO0/TestCases/*.class 11 | jar cfm contrast-ro0.jar ../mf/contrast.mf com/contrastsecurity/rO0/*.class 12 | jar cfm contrast-ro0-spotfix.jar ../mf/safeois.mf com/akamai/security/*.class 13 | 14 | echo "copying dependencies" 15 | cp ../lib/* . 16 | 17 | echo "executing tests" 18 | export JARDIR=`pwd` 19 | java -javaagent:${JARDIR}/contrast-ro0.jar -Xbootclasspath/p:"${JARDIR}/contrast-rO0.jar:${JARDIR}/asm-5.0.4.jar:${JARDIR}/asm-commons-5.0.4.jar" -jar contrast-test.jar -Dfile.encoding=UTF-8 -classpath ${JARDIR}/asm-5.0.4.jar:${JARDIR}/asm-commons-5.0.4.jar 20 | 21 | java com/akamai/security/TestSafeObjectInputStream 22 | 23 | # clean up 24 | popd 25 | -------------------------------------------------------------------------------- /src/main/java/com/akamai/security/SafeObjectInputStream.java: -------------------------------------------------------------------------------- 1 | package com.akamai.security; 2 | 3 | import com.contrastsecurity.rO0.RO0Config; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.ObjectInputStream; 7 | import java.io.ObjectStreamClass; 8 | import java.util.Hashtable; 9 | 10 | public class SafeObjectInputStream extends ObjectInputStream { 11 | 12 | RO0Config config = new RO0Config(); 13 | 14 | Hashtable classList = new Hashtable(); 15 | 16 | /** 17 | * Construct a new SafeObjectInputStream, specifying whether it is 18 | * in whitelisting mode or blacklisting mode. If whitelisting, 19 | * only classes that are added to the whitelist (via addToWhitelist()) 20 | * will be allowed to deserialize. If it's in blacklist mode, only 21 | * classes not on the blacklist will be allowed to deserialize. 22 | * 23 | * @param isWhitelist true if in whiteslist mode; false if in blacklist 24 | * mode. 25 | */ 26 | public SafeObjectInputStream(boolean isWhitelist) throws IOException { 27 | super(); 28 | this.config.setWhitelisting(isWhitelist); 29 | this.config.setBlacklisting(!isWhitelist); 30 | } 31 | 32 | public SafeObjectInputStream(InputStream in, boolean isWhitelist) throws IOException { 33 | super(in); 34 | this.config.setWhitelisting(isWhitelist); 35 | this.config.setBlacklisting(!isWhitelist); 36 | } 37 | 38 | /* By calling this function and adding a class to the whitelist, 39 | * you are attesting that the class you have whitelisted is 40 | * completely safe, in that it and its parent constructors do 41 | * NOTHING other than intiialize the class - no business logic, no 42 | * threads or processes are started, and nothing that is automatically 43 | * operated upon by other classes. DTOs that contain no business logic 44 | * are generally safe. Other classes may or may not be safe. 45 | * As a quick hint, don't do System.exec(), reflection, or reserve 46 | * large resources during initialization. 47 | */ 48 | public void addToWhitelist(Class klass) { 49 | config.addToWhitelist(klass); 50 | } 51 | 52 | public void addToBlacklist(Class klass) { 53 | config.addToBlacklist(klass); 54 | } 55 | 56 | public RO0Config getConfig() { return config; } 57 | 58 | public void setConfig(RO0Config config){ this.config = config; } 59 | 60 | protected Class resolveClass(ObjectStreamClass desc) 61 | throws IOException, ClassNotFoundException 62 | { 63 | String name = desc.getName(); 64 | 65 | if( config.isBlacklisted(name) ) { 66 | String message = "Attempt to deserialize blacklisted class:" + name; 67 | throw new SecurityException(message); 68 | } 69 | 70 | if( ! config.isWhitelisted(name) ) { 71 | String message = "Attempt to deserialize non-whitelisted class: " + name; 72 | throw new SecurityException(message); 73 | } 74 | 75 | return super.resolveClass(desc); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/akamai/security/TestSafeObjectInputStream.java: -------------------------------------------------------------------------------- 1 | package com.akamai.security; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.io.ObjectOutputStream; 7 | import java.io.Serializable; 8 | import java.util.Arrays; 9 | 10 | import com.contrastsecurity.rO0.TestCases.Test; 11 | import com.contrastsecurity.rO0.TestCases.BlacklistElement; 12 | import com.contrastsecurity.rO0.TestCases.UnlistedElement; 13 | import com.contrastsecurity.rO0.TestCases.WhitelistElement; 14 | 15 | public class TestSafeObjectInputStream 16 | extends Test 17 | { 18 | public static void main(String[] args) 19 | { 20 | TestSafeObjectInputStream test = new TestSafeObjectInputStream(); 21 | try { 22 | test.run(); 23 | } catch (Throwable t) { 24 | out("FAIL! FAIL! FAIL: Unexpected exception"); 25 | out(t); 26 | } 27 | } 28 | 29 | public void run() throws IOException { 30 | out("--------------------------------------------------"); 31 | out("----- Beginning SafeObjectInputStream tests -----"); 32 | 33 | testWhitelist(); 34 | testBlacklist(); 35 | } 36 | 37 | private void testWhitelist() throws IOException 38 | { 39 | out("----- Test in whitelisting mode -----"); 40 | WhitelistElement whitelistElement = new WhitelistElement(); 41 | BlacklistElement blacklistElement = new BlacklistElement(); 42 | UnlistedElement unlistedElement = new UnlistedElement(); 43 | 44 | byte[] w = serialize(whitelistElement); 45 | byte[] b = serialize(blacklistElement); 46 | byte[] u = serialize(unlistedElement); 47 | 48 | ByteArrayInputStream bin_w = new ByteArrayInputStream(w); 49 | ByteArrayInputStream bin_b = new ByteArrayInputStream(b); 50 | ByteArrayInputStream bin_u = new ByteArrayInputStream(u); 51 | 52 | SafeObjectInputStream in_w = new SafeObjectInputStream(bin_w, true); 53 | SafeObjectInputStream in_b = new SafeObjectInputStream(bin_b, true); 54 | SafeObjectInputStream in_u = new SafeObjectInputStream(bin_u, true); 55 | 56 | in_w.addToWhitelist(WhitelistElement.class); 57 | in_b.addToWhitelist(WhitelistElement.class); 58 | in_u.addToWhitelist(WhitelistElement.class); 59 | 60 | test("verifying whitelisted object deserializes with whitelist", 61 | tryToDeserialize(w, in_w), 62 | true); 63 | test("verifying blacklisted object doesn't deserialize with whitelist", 64 | tryToDeserialize(b, in_b), 65 | false); 66 | test("verifying unlisted object doesn't deserialize with whitelist", 67 | tryToDeserialize(u, in_u), 68 | false); 69 | } 70 | 71 | private void testBlacklist() throws IOException 72 | { 73 | out("----- Test in blacklisting mode -----"); 74 | WhitelistElement whitelistElement = new WhitelistElement(); 75 | BlacklistElement blacklistElement = new BlacklistElement(); 76 | UnlistedElement unlistedElement = new UnlistedElement(); 77 | 78 | byte[] w = serialize(whitelistElement); 79 | byte[] b = serialize(blacklistElement); 80 | byte[] u = serialize(unlistedElement); 81 | 82 | ByteArrayInputStream bin_w = new ByteArrayInputStream(w); 83 | ByteArrayInputStream bin_b = new ByteArrayInputStream(b); 84 | ByteArrayInputStream bin_u = new ByteArrayInputStream(u); 85 | 86 | SafeObjectInputStream in_w = new SafeObjectInputStream(bin_w, false); 87 | SafeObjectInputStream in_b = new SafeObjectInputStream(bin_b, false); 88 | SafeObjectInputStream in_u = new SafeObjectInputStream(bin_u, false); 89 | 90 | in_w.addToBlacklist(BlacklistElement.class); 91 | in_b.addToBlacklist(BlacklistElement.class); 92 | in_u.addToBlacklist(BlacklistElement.class); 93 | 94 | test("verifying whitelisted object deserializes with blacklist", 95 | tryToDeserialize(w, in_w), 96 | true); 97 | test("verifying blacklisted object doesn't deserialize with blacklist", 98 | tryToDeserialize(b, in_b), 99 | false); 100 | test("verifying unlisted object doesn't deserializes with blacklist", 101 | tryToDeserialize(u, in_u), 102 | true); 103 | } 104 | 105 | 106 | private boolean tryToDeserialize(byte[] orig_bytes, SafeObjectInputStream in) 107 | { 108 | try { 109 | // this first line will throw an exception if deserialization is 110 | // not allowed 111 | Serializable object = (Serializable)in.readObject(); 112 | 113 | // these lines ensure that the object really deserialized correctly 114 | // and our test is valid. 115 | byte[] new_bytes = serialize(object); 116 | if(Arrays.equals(orig_bytes, new_bytes)) return true; 117 | } catch ( SecurityException se ) { 118 | return false; 119 | } catch ( IOException ioe ) { 120 | throw new RuntimeException(ioe); 121 | } catch (ClassNotFoundException cnfe) { 122 | throw new RuntimeException(cnfe); 123 | } 124 | return false; 125 | } 126 | 127 | private byte[] serialize(Serializable object) throws IOException 128 | { 129 | ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 130 | ObjectOutputStream out = new ObjectOutputStream(bytes); 131 | out.writeObject(object); 132 | return bytes.toByteArray(); 133 | } 134 | 135 | private static void out(String s) 136 | { 137 | System.out.println(s); 138 | System.out.flush(); 139 | } 140 | 141 | private static void out(Throwable t) 142 | { 143 | t.printStackTrace(); 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/ObjectInputStreamVisitor.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0; 2 | 3 | import org.objectweb.asm.ClassVisitor; 4 | import org.objectweb.asm.MethodVisitor; 5 | import org.objectweb.asm.Opcodes; 6 | 7 | public class ObjectInputStreamVisitor extends ClassVisitor { 8 | 9 | public ObjectInputStreamVisitor(ClassVisitor cv) { 10 | super(Opcodes.ASM5, cv); 11 | } 12 | 13 | @Override 14 | public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 15 | MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 16 | if("resolveClass".equals(name) && "(Ljava/io/ObjectStreamClass;)Ljava/lang/Class;".equals(desc)) { 17 | mv = new ResolveClassMethodVisitor(mv, access, name, desc); 18 | } 19 | return mv; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/RO0Agent.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0; 2 | 3 | import java.io.FileNotFoundException; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.PrintStream; 7 | import java.lang.instrument.ClassFileTransformer; 8 | import java.lang.instrument.Instrumentation; 9 | import java.util.Properties; 10 | 11 | /** 12 | * Install a ClassFileTransformer to instrument ObjectInputStream. 13 | */ 14 | public class RO0Agent { 15 | 16 | public static RO0Config config = new RO0Config(); 17 | 18 | // added to make unit testing / prerequisite verification easier 19 | public static boolean loaded = false; 20 | 21 | /** 22 | * The location where we'll write our output. Defaults to System.out, but 23 | * can be configured to go elsewhere. 24 | */ 25 | private static PrintStream out = System.out; 26 | 27 | /** 28 | * configuration properties as passed in to premain and/or agentmain 29 | */ 30 | static private Properties properties = null; 31 | 32 | 33 | public static void premain(String args, Instrumentation inst) throws IOException { 34 | setup(args, inst); 35 | } 36 | 37 | public static void agentmain(String args, Instrumentation inst) throws IOException { 38 | setup(args, inst); 39 | } 40 | 41 | private static void setup(String args, Instrumentation inst) { 42 | properties = parseCommandLine(args); 43 | 44 | ClassFileTransformer xform = new RO0Transformer(); 45 | inst.addTransformer(xform); 46 | readConfig(args); 47 | } 48 | 49 | private static void readConfig(String args) { 50 | 51 | 52 | String outfile = getProperty("rO0.outfile"); 53 | 54 | if( outfile != null && !outfile.equals("") ) { 55 | out("redirecting output to " + outfile); 56 | try { 57 | out = new PrintStream(new FileOutputStream(outfile)); 58 | } catch (FileNotFoundException e) { 59 | out("failed to redirect output. Sending output to System.out."); 60 | e.printStackTrace(); 61 | } 62 | } 63 | 64 | try { 65 | String listfile = getProperty("rO0.lists"); 66 | config.readConfig(listfile); 67 | } catch (FileNotFoundException e) { 68 | out("failed to read config file"); 69 | out(e); 70 | } 71 | 72 | String _reporting = getProperty("rO0.reporting"); 73 | String _ignoreClasses = getProperty("rO0.ignoreClasses"); 74 | String _ignoreStack = getProperty("rO0.ignoreStack"); 75 | String _whitelist = getProperty("rO0.whitelist"); 76 | String _blacklist = getProperty("rO0.blacklist"); 77 | debug("blacklist = " + _blacklist); 78 | 79 | /* Read configuration from the environment (see the README for details about how to set this 80 | * stuff). Default is that nothing is enabled. 81 | * 82 | * If it's null, it's not set, so it's disabled. If it's not "true" it's not set as we expect 83 | * so it's disabled. 84 | */ 85 | 86 | // This one is enabled by default, so the "null" is opposite. If it's present 87 | // it must be "true" to be enabled, but if it's not present, it's still true. 88 | boolean reportingEnabled = ( _reporting == null || _reporting.equals("true") ); 89 | 90 | // These are normal 91 | boolean classIgnoreEnabled = ( _ignoreClasses != null && _ignoreClasses.equals("true") ); 92 | boolean stackIgnoreEnabled = ( _ignoreStack != null && _ignoreStack.equals("true") ); 93 | boolean whitelistEnabled = ( _whitelist != null && _whitelist.equals("true") ); 94 | boolean blacklistEnabled = ( _blacklist != null && _blacklist.equals("true") ); 95 | 96 | config.setReporting(reportingEnabled); 97 | config.setClassFiltering(classIgnoreEnabled); 98 | config.setStackFiltering(stackIgnoreEnabled); 99 | config.setWhitelisting(whitelistEnabled); 100 | config.setBlacklisting(blacklistEnabled); 101 | 102 | debug("Configuration = " + config.toString()); 103 | 104 | loaded = true; 105 | } 106 | 107 | 108 | /** 109 | * Reads the specified property from the properties object; if not found, 110 | * checks the environment. 111 | * 112 | * @param string the property to read 113 | * @param properties the properties to check 114 | * 115 | * @return the value as specified by the properties object; if it does not exist 116 | * in the properties object, the value as specified in the environment; 117 | * null if not found anywhere. 118 | */ 119 | private static String getProperty(String string) { 120 | if( properties == null ) { 121 | return null; 122 | } 123 | 124 | return properties.getProperty(string, System.getProperty(string)); 125 | } 126 | 127 | private static Properties parseCommandLine(String args) { 128 | debug("parsing command line:" + args); 129 | Properties properties = new Properties(); 130 | if( args == null ) return properties; 131 | 132 | // key value pairs separated by commas 133 | String[] pairs = args.split(","); 134 | for( String pair : pairs) { 135 | debug("kvpair = " + pair); 136 | if ( pair.length() == 0 ) continue; 137 | 138 | String[] key_value = pair.split(":"); 139 | String key = key_value[0]; 140 | String value = (key_value.length > 1) ? key_value[1] : ""; 141 | properties.setProperty(key, value); 142 | debug("Added key="+key+" value="+value); 143 | } 144 | 145 | debug("properties = " + properties); 146 | return properties; 147 | } 148 | 149 | public static void out(String msg) { 150 | String quiet = getProperty("rO0.quiet"); 151 | if(quiet == null || !"true".equalsIgnoreCase(quiet)) { 152 | out.println("[contrast-rO0] " + msg); 153 | } 154 | } 155 | 156 | 157 | public static void out(Throwable t) { 158 | String veryQuiet = getProperty("rO0.quiet"); 159 | String quiet = getProperty("rO0.noStackTraces"); 160 | 161 | if( veryQuiet != null && "true".equalsIgnoreCase(veryQuiet) ) return; 162 | if( quiet != null && "true".equalsIgnoreCase(quiet) ) return; 163 | 164 | t.printStackTrace(out); 165 | } 166 | 167 | public static void debug(String s) { 168 | String debug = getProperty("rO0.debug"); 169 | if( debug != null && debug.equals("true") ) 170 | { 171 | out.println("[contrast.rO0] DEBUG: " + s); 172 | } 173 | } 174 | 175 | public static void main(String[] args) { 176 | out.println("********************************************************************"); 177 | out.println("* contrast-rO0 - the hotpatching agent for deserialization attacks *"); 178 | out.println("********************************************************************"); 179 | out.println(); 180 | out.println("To use contrast-rO0, add it as a Java agent with the following flag: "); 181 | out.println(" -javaagent:/path/to/contrast-rO0.jar"); 182 | out.println(); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/RO0Config.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileNotFoundException; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | import java.util.Vector; 8 | 9 | public class RO0Config { 10 | 11 | /** Flag indicating if reporting is enalbed or disabled. Enabled by default. **/ 12 | private boolean reportingEnabled = true; 13 | 14 | /** 15 | * Flag indicating whether we're supposed to ignore any classes during reporting. 16 | * Enabled if the config file specified ignore classes. 17 | */ 18 | private boolean classIgnoreEnabled = false; 19 | 20 | /** 21 | * Flag indicating whether we're supposed to ignore any stack traces during 22 | * blacklisting. This can be used to indicate trusted code, but USE WITH CARE. 23 | */ 24 | private boolean stackWhitelistEnabled = false; 25 | 26 | /** 27 | * List of classes to ignore during reporting. That is, don't report if there are 28 | * deserialization attempts for these classes. This is to reduce the noise in the log 29 | * file for known safe stuff. 30 | */ 31 | private Vector classIgnoreList = new Vector(); 32 | 33 | 34 | /** 35 | * Flag indicating that we should ignore deserialization attempts for classes with any of 36 | * the listed superclasses in their stack. This is intended to allow us to ignroe things like 37 | * serialization via memcache. This can be helpful when trying to figure out whwere in the 38 | * application deserialization occurs, as it allows us to ignore the stuff we know about and 39 | * keep the output log limited to the stuff we want to investigate. 40 | */ 41 | private boolean stackIgnoreEnabled = false; 42 | 43 | /** 44 | * List of classes that, if found on the stack while in reporting mode, should result in NOT logging 45 | * the deserialization attempt. 46 | */ 47 | private Vector stackIgnoreList = new Vector(); 48 | 49 | /** 50 | * Flag to indicate if we're in whitelisting mode. Deserailziation of expected classes will be 51 | * allowed. Deserialization of non-whitelisted classes will result in a SecurityException. 52 | * Note that whitelisting is prone to usability/funcitonal failure if it's incomplete. If you 53 | * don't whitelist a class you need, you will not have access to that functionality. 54 | */ 55 | private boolean whitelistEnabled = false; 56 | 57 | /** 58 | * If we're in whitelisting mode, this array will contain all whitelisted classes. Deserialization 59 | * of these classes will be allowed; deserialization of anything unlisted willr esult in a SecurityException 60 | */ 61 | private Vector whitelist = new Vector(); 62 | 63 | /** 64 | * Flag to indicate if blacklisting is enabled. If it is, deserialization attempts on listed classes 65 | * will result in a SecurityException; deserialization attempts on unlisted classes will be allowed. 66 | * Note that blacklisting is inherently prone to security failure - if you fail to blacklist a class 67 | * that is dangerous, you'll have a vulnerability. 68 | */ 69 | private boolean blacklistEnabled = false; 70 | 71 | /** 72 | * List of classes that are blacklisted. If blacklisting is enalbed, attempts to load these 73 | * classes will result in a SecurityException. 74 | */ 75 | private Vector blacklist = new Vector(); 76 | 77 | /** 78 | * Read the specified file and parse the configuration. 79 | * @param filename 80 | * @throws FileNotFoundException 81 | */ 82 | public void readConfig(String filename) throws FileNotFoundException { 83 | if( filename == null || filename.equals("") ) return; 84 | readConfig(new FileReader(filename)); 85 | } 86 | 87 | /** 88 | * See readConfig(String) 89 | * 90 | * @param file 91 | */ 92 | public void readConfig(FileReader file) { 93 | if(file == null) return; 94 | 95 | BufferedReader in = null; 96 | try { 97 | in = new BufferedReader(file); 98 | 99 | String line = null; 100 | do { 101 | line = in.readLine(); 102 | if( line == null ) continue; 103 | line = line.trim(); 104 | 105 | if(line.startsWith("#")) continue; // # is comment character if 1st char in line 106 | if(line.startsWith("-")) addToBlacklist(line.substring(1,line.length()).trim()); 107 | if(line.startsWith("+")) addToWhitelist(line.substring(1,line.length()).trim()); 108 | if(line.startsWith("$")) addToClassIgnoreList(line.substring(1,line.length()).trim()); 109 | if(line.startsWith("@")) addToStackIgnoreList(line.substring(1,line.length()).trim()); 110 | } while(line != null ); 111 | } catch (FileNotFoundException fnfe) { 112 | RO0Agent.out("Unable to set up ignore list"); 113 | fnfe.printStackTrace(); 114 | } catch (IOException ioe) { 115 | RO0Agent.out("Error reading ignorelist config file"); 116 | ioe.printStackTrace(); 117 | } finally { 118 | try { if( in != null ) { in.close(); } } catch ( Exception e) { /* do nothing*/ } 119 | } 120 | } 121 | 122 | /** 123 | * Called during reporting to check if the current class is filtered out of the 124 | * report. It's safe to call this without first checking if reporting is enabled; 125 | * this method returns false if reporting is disabled. 126 | * 127 | * @param className 128 | * @return true if the event should be included in the output report. False if it should NOT be 129 | * included in the report. "false" can be the case if reporting is disabled, of if the class 130 | * or an element in the current call stack is filtered out via the config file. 131 | */ 132 | public boolean includeInReport(String className) { 133 | if( ! getReportingEnabled() ) 134 | { 135 | return false; 136 | } 137 | 138 | if( this.getClassIgnoreEnabled() && isOnClassIgnoreList(className) ) 139 | { 140 | return false; 141 | } 142 | 143 | if( getStackIgnoreEnabled() && isOnStackIgnoreList(Thread.currentThread().getStackTrace())) 144 | { 145 | return false; 146 | } 147 | 148 | return true; 149 | } 150 | 151 | public void setStackFiltering(boolean enabled) 152 | { 153 | this.stackIgnoreEnabled = enabled; 154 | } 155 | 156 | public void setClassFiltering(boolean enabled) 157 | { 158 | this.classIgnoreEnabled = enabled; 159 | } 160 | 161 | /** 162 | * Internal helper method to determine if a class name is on the list of classes to ignore. 163 | * 164 | * @param name the class name to check 165 | * 166 | * @return ture if it's on the list; false if it's not on the list 167 | */ 168 | private boolean isOnClassIgnoreList(String name){ 169 | return isOnList(classIgnoreList, name); 170 | } 171 | 172 | /** 173 | * Internal helper method to determine if the specified stack trace contains any element 174 | * that is on the list of stack trace elements to ignore. A match indicates that the 175 | * current stack trace matches something from the configuration file. 176 | * 177 | * @param stackTrace the stack trace to check 178 | * @return true if there's a match; false if there's no match. 179 | */ 180 | private boolean isOnStackIgnoreList(StackTraceElement[] stackTrace) { 181 | if( !getStackIgnoreEnabled() || getStackWhitelistEnabled() ) { 182 | return false; 183 | } 184 | 185 | if( stackIgnoreList.isEmpty() ) return false; 186 | 187 | for( int i=0; iNOTE: This method DOES NOT check the call stack, just the specific class. This is because 228 | * checking the call stack could have a performance hit in production, which we are not (yet?) willing 229 | * to risk. 230 | * 231 | * @param name the class to check if it's whitelisted. 232 | * @return true if the class is whitelisted or if whitelisting is disabled. False if whitelisting is 233 | * enabled but the class is not whitelisted. 234 | */ 235 | public boolean isWhitelisted(String name) { 236 | if( ! ( getWhitelistEnabled() || getStackWhitelistEnabled() ) ) { 237 | return true; 238 | } 239 | 240 | return isOnList(whitelist, name) || isOnStackIgnoreList( Thread.currentThread().getStackTrace() ); 241 | } 242 | 243 | @SuppressWarnings("rawtypes") 244 | public boolean isWhitelisted(Class klass) { 245 | return isWhitelisted(klass.getName()); 246 | } 247 | 248 | 249 | /** 250 | * Internal helper method for checking if an entry is on either a Vector or a Hashtable. 251 | * If it's in the Vector but not the Hashtable, then the Hashtable will be updated to include 252 | * the item, so that future lookups are faster. 253 | * 254 | * @param list 255 | * @param cache 256 | * @param name 257 | * @return true if the named class is listed either in the vector or the hashtable 258 | */ 259 | private boolean isOnList(Vector list, String name) { 260 | 261 | if(list.size() == 0 ) return false; 262 | 263 | for(int i=0; i list, String line) 282 | { 283 | list.addElement(line); 284 | } 285 | 286 | /** 287 | * Adds the specified line to the list of classes that we should filter out of reporting 288 | * results if the deserialization event occurred with the specified class in the current 289 | * call stack. For example, to help ignore if serialization is happening through memcache. 290 | * @param line 291 | */ 292 | public void addToStackIgnoreList(String line) { 293 | addToList(stackIgnoreList, line); 294 | } 295 | 296 | /** 297 | * Adds the specified class name to the list of classes that we should filter out of reporting 298 | * results if it is the class being deserialized. 299 | * 300 | * @param className 301 | */ 302 | public void addToClassIgnoreList(String className){ 303 | addToList(classIgnoreList, className); 304 | } 305 | 306 | 307 | /** 308 | * Add the specified class to the whitelist. If whitelisting is enabled, this class will be 309 | * allowed to deserialize, but non-whitelisted classes won't be allowed to deserialize. 310 | * @param line 311 | */ 312 | public void addToWhitelist(String line) { 313 | addToList(whitelist, line); 314 | } 315 | 316 | @SuppressWarnings("rawtypes") 317 | public void addToWhitelist(Class klass){ 318 | addToWhitelist(klass.getName()); 319 | } 320 | 321 | /** 322 | * Add the specified class to the blacklist. If blacklisting is enabled, this class NOT will be 323 | * allowed to desereialize, but non-listed classes will be allowed to deserialize. 324 | * 325 | * @param line 326 | */ 327 | public void addToBlacklist(String line) { 328 | addToList(blacklist, line); 329 | } 330 | 331 | @SuppressWarnings("rawtypes") 332 | public void addToBlacklist(Class klass) { 333 | addToBlacklist(klass.getName()); 334 | } 335 | 336 | /** 337 | * Turns on or off whitelisting. 338 | * 339 | * @param isWhitelist 340 | */ 341 | public void setWhitelisting(boolean enabled) { 342 | this.whitelistEnabled = enabled; 343 | } 344 | 345 | /** 346 | * Turns on or off blacklisting. 347 | * 348 | * @param isBlacklist 349 | */ 350 | public void setBlacklisting(boolean enabled) { 351 | this.blacklistEnabled = enabled; 352 | } 353 | 354 | /** 355 | * Turn reporting on or off. 356 | * 357 | * @param enabled 358 | */ 359 | public void setReporting(boolean enabled) { 360 | this.reportingEnabled = enabled; 361 | } 362 | 363 | @SuppressWarnings("rawtypes") 364 | public void addToClassIgnoreList(Class class1) { 365 | addToClassIgnoreList(class1.getName()); 366 | } 367 | 368 | @SuppressWarnings("rawtypes") 369 | public boolean includeInReport(Class class1) { 370 | return this.includeInReport(class1.getName()); 371 | } 372 | 373 | public String toString() { 374 | String string = "rO0Config:{"; 375 | string += " BLACKLIST:(" + this.blacklistEnabled + this.blacklist + ")"; 376 | string += " WHITELIST(" + this.whitelistEnabled + this.whitelist + ")"; 377 | string += " REPORTING(" + this.reportingEnabled; 378 | string += "CLASSIGNORE(" + this.classIgnoreEnabled + this.classIgnoreList + ")"; 379 | string += "STACKIGNORE(" + this.stackIgnoreEnabled + this.stackIgnoreList + ")"; 380 | string += ")"; 381 | string += "}"; 382 | 383 | return string; 384 | } 385 | 386 | } 387 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/RO0Transformer.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0; 2 | 3 | import java.lang.instrument.ClassFileTransformer; 4 | import java.lang.instrument.IllegalClassFormatException; 5 | import java.security.ProtectionDomain; 6 | 7 | import org.objectweb.asm.ClassReader; 8 | import org.objectweb.asm.ClassVisitor; 9 | import org.objectweb.asm.ClassWriter; 10 | 11 | /** 12 | * Just transform a single class - java.io.ObjectInputStream. Let 13 | * the user know if there are any problems doing that via sysout. 14 | */ 15 | public class RO0Transformer implements ClassFileTransformer { 16 | 17 | public byte[] transform(ClassLoader cl, String className, Class parentClass, ProtectionDomain pd, byte[] originalBytecode) throws IllegalClassFormatException { 18 | byte[] transformedBytecode = null; 19 | if("java/io/ObjectInputStream".equals(className)) { 20 | transformedBytecode = weavePatch(originalBytecode); 21 | } 22 | return transformedBytecode; 23 | } 24 | 25 | private byte[] weavePatch(byte[] originalBytecode) { 26 | byte[] transformedBytecode = null; 27 | try { 28 | ClassReader reader = new ClassReader(originalBytecode); 29 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); 30 | ClassVisitor visitor = new ObjectInputStreamVisitor(writer); 31 | reader.accept(visitor, ClassReader.EXPAND_FRAMES); 32 | transformedBytecode = writer.toByteArray(); 33 | RO0Agent.out("Protection against deserialization attacks added to java.io.ObjectInputStream"); 34 | } catch (Throwable t) { 35 | RO0Agent.out("Problem instrumenting java.io.ObjectInputStream -- no deserialization protection in place"); 36 | t.printStackTrace(); 37 | } 38 | return transformedBytecode; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/ResolveClassController.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0; 2 | 3 | import java.io.ObjectStreamClass; 4 | 5 | public class ResolveClassController { 6 | 7 | /** 8 | * This is invoked at the beginning of java.io.ObjectInputStream#resolveClass(). 9 | * @param streamClass the parameter passed to resolveClass() 10 | */ 11 | public static void onResolveClass(ObjectStreamClass streamClass) { 12 | String name = streamClass.getName(); 13 | 14 | if( RO0Agent.config.isBlacklisted(name) ) { 15 | String message = "Likely exploit gadget encoutered during deserialization: " + name; 16 | RO0Agent.out(message); 17 | throw new SecurityException(message); 18 | } 19 | 20 | if( ! RO0Agent.config.isWhitelisted(name)) { 21 | String message = "Non-whitelisted class found during deserialization: " + name; 22 | RO0Agent.out(message); 23 | throw new SecurityException(message); 24 | } 25 | 26 | // LAST thing to do is report. If something else failed, an exception would have been 27 | // thrown, and an error reported; we'd never get here; or if something was found on 28 | // an ignore list, we'd never get here. Note that there are two ways to ignore stuff, 29 | // so that we don't get really noisy logging. First is ignoring classes to be deserialized. 30 | // second is ignoring deserialization attempts with entries on the stack. 31 | if( ! RO0Agent.config.includeInReport(name)) { 32 | return; 33 | } 34 | 35 | RO0Agent.out("Deserializing " + name + ": " + Thread.currentThread().getStackTrace().toString()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/ResolveClassMethodVisitor.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0; 2 | 3 | import org.objectweb.asm.MethodVisitor; 4 | import org.objectweb.asm.Opcodes; 5 | import org.objectweb.asm.Type; 6 | import org.objectweb.asm.commons.AdviceAdapter; 7 | import org.objectweb.asm.commons.Method; 8 | 9 | public class ResolveClassMethodVisitor extends AdviceAdapter { 10 | 11 | protected ResolveClassMethodVisitor(MethodVisitor mv, int access, String name, String desc) { 12 | super(Opcodes.ASM5, mv, access, name, desc); 13 | } 14 | 15 | /** 16 | * Fire off our sensor at the beginning of ObjectInputStream#resolveClass(java.io.ObjectStreamClass). 17 | */ 18 | @Override 19 | protected void onMethodEnter() { 20 | Type type = Type.getType(ResolveClassController.class); 21 | Method method = new Method("onResolveClass", "(Ljava/io/ObjectStreamClass;)V"); 22 | loadArg(0); 23 | invokeStatic(type,method); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/TestCases/BlacklistElement.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0.TestCases; 2 | 3 | import java.io.Serializable; 4 | 5 | public class BlacklistElement implements Serializable { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = 1L; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/TestCases/IgnoreClassListElement.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0.TestCases; 2 | 3 | import java.io.Serializable; 4 | 5 | public class IgnoreClassListElement implements Serializable { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = 1L; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/TestCases/Test.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0.TestCases; 2 | 3 | import com.contrastsecurity.rO0.RO0Agent; 4 | 5 | public class Test { 6 | public void test(String test, boolean result, boolean expected){ 7 | RO0Agent.out((result==expected?"PASS: ":"FAIL: ") + test); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/TestCases/TestMain.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0.TestCases; 2 | 3 | public class TestMain { 4 | 5 | public static void main(String[] args) { 6 | TestRO0Config test1 = new TestRO0Config(); 7 | TestResolveClassController test2 = new TestResolveClassController(); 8 | 9 | test1.run(); 10 | test2.run(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/TestCases/TestRO0Config.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0.TestCases; 2 | 3 | import java.io.FileNotFoundException; 4 | 5 | import com.contrastsecurity.rO0.RO0Config; 6 | import com.contrastsecurity.rO0.RO0Agent; 7 | import com.contrastsecurity.rO0.TestCases.BlacklistElement; 8 | import com.contrastsecurity.rO0.TestCases.IgnoreClassListElement; 9 | import com.contrastsecurity.rO0.TestCases.UnlistedElement; 10 | import com.contrastsecurity.rO0.TestCases.WhitelistElement; 11 | 12 | public class TestRO0Config 13 | extends Test 14 | { 15 | 16 | public void run() { 17 | RO0Agent.out("----- Beginning RO0Config tests -----"); 18 | RO0Config config = new RO0Config(); 19 | 20 | RO0Agent.out("----- Testing All-Disabled Config -----"); 21 | config.setBlacklisting(false);; 22 | config.setReporting(false); 23 | config.setWhitelisting(false); 24 | config.setBlacklisting(true); 25 | test("check disabled blacklist disallows nothing", 26 | config.isBlacklisted(Object.class), 27 | false); 28 | test("check disabled whitelist allows everything", 29 | config.isWhitelisted(Object.class), 30 | true); 31 | test("check disabled report filters filter everything", 32 | config.includeInReport(Object.class), 33 | false); 34 | 35 | 36 | RO0Agent.out("----- Testing Empty Config, each enabled in turn -----"); 37 | config.setBlacklisting(true); 38 | test("check empty, enabled blacklist disallows nothing", 39 | config.isBlacklisted(Object.class), 40 | false); 41 | config.setWhitelisting(true); 42 | test("check empty, enabled whitelist allows nothing", 43 | config.isWhitelisted(Object.class), 44 | false); 45 | config.setReporting(true); 46 | test("check empty, enalbed report still filters filter nothing", 47 | config.includeInReport(Object.class), 48 | true); 49 | 50 | 51 | RO0Agent.out("----- Test 1 blacklisted element -----"); 52 | config.setBlacklisting(true); 53 | config.setWhitelisting(false);; 54 | config.addToBlacklist(Object.class); 55 | test("check blacklisted element is found on blacklist", 56 | config.isBlacklisted(Object.class), 57 | true); 58 | test("check NON-blacklisted element is not found on blacklist", 59 | config.isBlacklisted(Integer.class), 60 | false); 61 | test("check whitelist pretends everything is whitelisted when blacklist is enabled", 62 | config.isWhitelisted(Object.class), 63 | true); 64 | test("check reporting is unaffected by enabling blacklisting - still nothing is filtered", 65 | config.includeInReport(Object.class), 66 | true); 67 | 68 | RO0Agent.out("----- Test 1 whitelisted element -----"); 69 | config.setWhitelisting(true); 70 | config.setBlacklisting(false); 71 | config.addToWhitelist(Object.class); 72 | test("check blacklisting is disabled (pretends blacklisted element isn't blacklisted)", 73 | config.isBlacklisted(Object.class), 74 | false); 75 | test("check NON-blacklisted element is still not on the blacklist", 76 | config.isBlacklisted(Integer.class), 77 | false); 78 | test("check whitelisted element is on the whitelist", 79 | config.isWhitelisted(Object.class), 80 | true); 81 | test("check non-whitelisted element is not on the white list", 82 | config.isWhitelisted(Integer.class), 83 | false); 84 | test("check reporting hasn't changed", 85 | config.includeInReport(Object.class), 86 | true); 87 | 88 | RO0Agent.out("----- Test that reporting class filters correctly work"); 89 | config.addToClassIgnoreList(Object.class); 90 | config.setReporting(true); 91 | config.setWhitelisting(false); 92 | test("check blacklisting continues to pretend the list is empty when reporting is enabled", 93 | config.isBlacklisted(Object.class), 94 | false); 95 | test("check NON-blacklisted element still isn't found on the blacklist, either", 96 | config.isBlacklisted(Integer.class), 97 | false); 98 | test("check whitelisted still allows the previously whitelisted object...", 99 | config.isWhitelisted(Object.class), 100 | true); 101 | test("and that whitelisting is disabled and therefore pretends everything is whitelisted when reporting enabled", 102 | config.isWhitelisted(Integer.class), 103 | true); 104 | test("check reporting wants to include a non-filtered class in the report", 105 | config.includeInReport(Integer.class), 106 | true); 107 | test("check reporting wants filtered classes in the report when class filtering is disabled", 108 | config.includeInReport(Object.class), 109 | true); 110 | test("check reporting wants unfiltered classes in the report when class filtering is enabled", 111 | config.includeInReport(Integer.class), 112 | true); 113 | config.setClassFiltering(true); 114 | test("check reporting doesn't want filtered classes in the report when class filtering is enabled", 115 | config.includeInReport(Object.class), 116 | false); 117 | test("check reporting still wants unfiltered classes in the report when class filtering is enabled", 118 | config.includeInReport(Integer.class), 119 | true); 120 | config.addToStackIgnoreList(getClass().getName()); 121 | test("check reporting wants configured-but-disaled stack-filtered stuff in the report when stack-filtering is disabled", 122 | config.includeInReport(Integer.class), 123 | true); 124 | test("check that the configred-but-disabled stack ignore list has no effect when stack ignore is disabled - unfiltered class is still reported", 125 | config.includeInReport(Integer.class), 126 | true); 127 | config.setClassFiltering(false);; 128 | test("check that cofigred-but-disabled stack filtering still has no effect if class filtering is disabled. filtered class is reported", 129 | config.includeInReport(Object.class), 130 | true); 131 | test("Check that configured-but-disabled stack filtering also allows non-filtered class);", 132 | config.includeInReport(Integer.class), 133 | true); 134 | config.setStackFiltering(true); 135 | test("Check that enabled stack filtering is allowed when on the stack", 136 | config.includeInReport(Integer.class), 137 | false); 138 | 139 | RO0Agent.out("----- Test proper loading of config file -----"); 140 | config = new RO0Config(); 141 | try { 142 | config.readConfig("../config/unit_test_config_1"); 143 | config.readConfig("../config/unit_test_config_2"); 144 | } catch (FileNotFoundException e) { 145 | RO0Agent.out("FAIL: Unable to load config file 1"); 146 | RO0Agent.out(e); 147 | } 148 | config.setBlacklisting(true); 149 | config.setWhitelisting(true); 150 | config.setReporting(true); 151 | config.setClassFiltering(true);; 152 | test("Test class ignore list includes class ignore element", 153 | config.includeInReport(IgnoreClassListElement.class), 154 | false); 155 | test("Test class ignore list does not include unlisted element", 156 | config.includeInReport(UnlistedElement.class), 157 | true); 158 | test("Test class ignore list does not include whitelist element", 159 | config.includeInReport(WhitelistElement.class), 160 | true); 161 | test("Test class ignore list does not include blacklist element", 162 | config.includeInReport(BlacklistElement.class), 163 | true); 164 | 165 | // testing stack ignore requires a separate config file, to put this class 166 | // in the ignore stack, so this piece had to be set up after the above 167 | // class-ignore tests 168 | try { 169 | config.readConfig("../config/unit_test_config_3"); 170 | } catch (FileNotFoundException e) { 171 | RO0Agent.out("FAIL: Unable to load config file 1"); 172 | RO0Agent.out(e); 173 | } 174 | test("Test stack ignore list affects class ignore element", 175 | config.includeInReport(IgnoreClassListElement.class), 176 | false); 177 | test("Test reporting includes unlisted element", 178 | config.includeInReport(UnlistedElement.class), 179 | true); 180 | test("Test reporting includes whitelisted element", 181 | config.includeInReport(WhitelistElement.class), 182 | true); 183 | test("Test reporting includes blacklisted element", 184 | config.includeInReport(BlacklistElement.class), 185 | true); 186 | 187 | 188 | test("test blacklist includes blacklisted element", 189 | config.isBlacklisted(BlacklistElement.class), 190 | true); 191 | test("Test blacklist does not include unlisted element", 192 | config.isBlacklisted(UnlistedElement.class), 193 | false); 194 | test("Test blacklist does not include whitelisted element", 195 | config.isBlacklisted(WhitelistElement.class), 196 | false); 197 | test("Test blacklist does not include report ignore element", 198 | config.isBlacklisted(IgnoreClassListElement.class), 199 | false); 200 | test("Test blacklist does not include stack ignore element", 201 | config.isBlacklisted(TestResolveClassController.class), 202 | false); 203 | 204 | test("Test whitelist includes whitelisted element", 205 | config.isWhitelisted(WhitelistElement.class), 206 | true); 207 | test("test whitelist does not include unlisted element", 208 | config.isWhitelisted(UnlistedElement.class), 209 | false); 210 | test("test whitelist does not include blacklisted element", 211 | config.isWhitelisted(BlacklistElement.class), 212 | false); 213 | test("Test whitelist does not include class-ignore element", 214 | config.isWhitelisted(IgnoreClassListElement.class), 215 | false); 216 | test("Test whitelist does not include stack ignore element", 217 | config.isWhitelisted(TestResolveClassController.class), 218 | false); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/TestCases/TestResolveClassController.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0.TestCases; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | import java.io.ObjectInputStream; 8 | import java.io.ObjectOutputStream; 9 | import java.io.Serializable; 10 | import java.util.Arrays; 11 | 12 | import com.contrastsecurity.rO0.RO0Agent; 13 | import com.contrastsecurity.rO0.TestCases.BlacklistElement; 14 | import com.contrastsecurity.rO0.TestCases.UnlistedElement; 15 | import com.contrastsecurity.rO0.TestCases.WhitelistElement; 16 | 17 | public class TestResolveClassController 18 | extends Test 19 | { 20 | 21 | public void run() { 22 | RO0Agent.out("--------------------------------------------------"); 23 | RO0Agent.out("----- Beginning ResolveClassController tests -----"); 24 | 25 | RO0Agent.out("----- trying to load java agent -----"); 26 | 27 | BlacklistElement blacklistElement = new BlacklistElement(); 28 | WhitelistElement whitelistElement = new WhitelistElement(); 29 | UnlistedElement unlistedElement = new UnlistedElement(); 30 | 31 | test("verifying flag to load rO0 has been set", 32 | RO0Agent.loaded, 33 | true); 34 | 35 | test("verifying whitelisting is disabled", 36 | RO0Agent.config.getWhitelistEnabled(), 37 | false); 38 | test("verifying blacklisting is disabled", 39 | RO0Agent.config.getBlacklistEnabled(), 40 | false); 41 | test("verifying reporting is disabled by default", 42 | RO0Agent.config.getReportingEnabled(), 43 | false); 44 | RO0Agent.config.setReporting(false); 45 | test("verifying reporting is now disabled", 46 | RO0Agent.config.getReportingEnabled(), 47 | false); 48 | test("verifying class ignore is disabled", 49 | RO0Agent.config.getClassIgnoreEnabled(), 50 | false); 51 | test("verifying stack ignore is disabled", 52 | RO0Agent.config.getStackIgnoreEnabled(), 53 | false); 54 | 55 | RO0Agent.out("----- If no lists are loaded. Everything should deserailize. -----"); 56 | 57 | test("verifying whitelisted object serializes", 58 | tryToSerialize(whitelistElement), 59 | true); 60 | 61 | test("verifying blacklisted object serializes", 62 | tryToSerialize(blacklistElement), 63 | true); 64 | test("verifying unlisted object serializes", 65 | tryToSerialize(unlistedElement), 66 | true); 67 | 68 | 69 | RO0Agent.out("----- Load all lists, but don't enable any of them -----"); 70 | try { 71 | RO0Agent.config.readConfig("../config/unit_test_config_1"); 72 | RO0Agent.config.readConfig("../config/unit_test_config_2"); 73 | RO0Agent.config.readConfig("../config/unit_test_config_3"); 74 | } catch (FileNotFoundException e) { 75 | RO0Agent.out("FAIL: unable to load config files"); 76 | e.printStackTrace(); 77 | } 78 | test("verifying whitelisted object serializes", 79 | tryToSerialize(whitelistElement), 80 | true); 81 | test("verifying blacklisted object serializes", 82 | tryToSerialize(blacklistElement), 83 | true); 84 | test("verifying unlisted object serializes", 85 | tryToSerialize(unlistedElement), 86 | true); 87 | 88 | RO0Agent.out("----- Enable whitelisting -----"); 89 | RO0Agent.config.setWhitelisting(true); 90 | test("verifying whitelisted object serializes", 91 | tryToSerialize(whitelistElement), 92 | true); 93 | test("verifying blacklisted object doesn't serialize", 94 | tryToSerialize(blacklistElement), 95 | false); 96 | test("verifying unlisted object doesn't serialize", 97 | tryToSerialize(unlistedElement), 98 | false); 99 | 100 | RO0Agent.out("----- Disable whitelisting and enable blacklisting -----"); 101 | RO0Agent.config.setWhitelisting(false); 102 | RO0Agent.config.setBlacklisting(true); 103 | test("verifying whitelisted object serializes", 104 | tryToSerialize(whitelistElement), 105 | true); 106 | test("verifying blacklisted object doesn't serialize", 107 | tryToSerialize(blacklistElement), 108 | false); 109 | test("verifying unlisted object serializes", 110 | tryToSerialize(unlistedElement), 111 | true); 112 | 113 | // Reporting can't easily be tested without replacing RO0Agent.out... that doesn't seem 114 | // necessary but unit tests could be added if deemed required. There is related coverage 115 | // in TestRO0Config, to at least ensure that configuration for reporting 116 | // is correctly implemented. 117 | } 118 | 119 | private boolean tryToSerialize(Serializable object) 120 | { 121 | try { 122 | byte[] bytes = serialize(object); 123 | Serializable object2 = deserialize(bytes); 124 | 125 | // validate match 126 | byte[] bytes2 = serialize(object2); 127 | if(Arrays.equals(bytes, bytes2)) return true; 128 | } catch ( SecurityException se ) { 129 | return false; 130 | } catch ( IOException ioe ) { 131 | throw new RuntimeException(ioe); 132 | } catch (ClassNotFoundException cnfe) { 133 | throw new RuntimeException(cnfe); 134 | } 135 | return false; 136 | } 137 | 138 | private byte[] serialize(Serializable object) throws IOException 139 | { 140 | ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 141 | ObjectOutputStream out = new ObjectOutputStream(bytes); 142 | out.writeObject(object); 143 | return bytes.toByteArray(); 144 | } 145 | 146 | private Serializable deserialize(byte[] bytes) throws IOException, ClassNotFoundException { 147 | ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); 148 | return (Serializable)in.readObject(); 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/TestCases/UnlistedElement.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0.TestCases; 2 | 3 | import java.io.Serializable; 4 | 5 | public class UnlistedElement implements Serializable { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = 1L; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/contrastsecurity/rO0/TestCases/WhitelistElement.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0.TestCases; 2 | 3 | import java.io.Serializable; 4 | 5 | public class WhitelistElement implements Serializable { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = 1L; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/scripts/stats.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | use strict; 3 | 4 | if ( $#ARGV < 0 ) { die "you need to specify an input file.\n"; } 5 | 6 | open FILE, "<$ARGV[0]" or die "can't open $!\n";; 7 | 8 | 9 | #################### 10 | # hashes to store our histograms 11 | # and other related variables 12 | my($num_deserializations); 13 | my(%deserialized_classes); 14 | my(%stack_locations); 15 | my($num_memcache, $memcache_check_flag); 16 | 17 | 18 | #################### 19 | # function: handle_class 20 | # adds the class to the class statistics hash(es) 21 | sub handle_class { 22 | my($klass) = (split ' ', shift)[-1]; 23 | $deserialized_classes{$klass}++; 24 | $num_deserializations++; 25 | $memcache_check_flag = 1; # OK to check for memcache in stack 26 | # flag helps avoid duplicates 27 | } 28 | 29 | 30 | 31 | ################### 32 | # function: handle_stack 33 | # adds the stack to the various stack statistcs hashes 34 | sub handle_stack { 35 | my($stack_location) = (split ' ', shift)[-1]; 36 | $stack_locations{$stack_location}++; 37 | 38 | if( $memcache_check_flag ) { 39 | if( $stack_location =~ /memcache/i ) { 40 | $num_memcache++; 41 | $memcache_check_flag = 0; # wait for next stack to check for memcache 42 | # to avoid double counting 43 | } 44 | } 45 | } 46 | 47 | 48 | 49 | 50 | #################### 51 | # main 52 | # 53 | 54 | # chew up all lines prior to the first output we care about 55 | my($line); 56 | UNINTERESTING_LINE: while($line = ) { 57 | chomp $line; 58 | if( $line =~ /contrast/ ) { 59 | last UNINTERESTING_LINE; 60 | } 61 | } 62 | 63 | # now start processing our stuff 64 | LINE: while($line = ) { 65 | # skip lines that are clearly not our stuff... 66 | if( not $line =~ /\./ ) { next LINE; } 67 | if( $line =~ /\// ) { next LINE; } 68 | if( $line =~ /NRMUtil/ ) { next LINE; } 69 | 70 | if( $line =~ /contrast-rO0/ ) { 71 | handle_class($line); 72 | next LINE; 73 | } 74 | handle_stack($line); 75 | 76 | } 77 | 78 | 79 | ############ 80 | # print histograms 81 | my($klass,$count); 82 | my(@klasses) = keys %deserialized_classes; 83 | my(%packages, @packages, $package); 84 | foreach $klass (@klasses) { 85 | # count the number of unique classes deserialized 86 | $count += $deserialized_classes{$klass}; 87 | 88 | # get and count the unique packages deserialized 89 | @packages = split /\./, $klass; 90 | $package = @packages[0...$#packages-1]; 91 | $packages{$package}++; 92 | } 93 | my(@stack_locations) = keys %stack_locations; 94 | 95 | printf "recorded $count deserializations out of $num_deserializations\n"; 96 | printf "number of unique classes deserialized: $#klasses\n"; 97 | printf "number of unique packages from which deserialized classes were found: $#packages\n"; 98 | printf "number of unique stack locations involved (includes multiple entries for each deserialize event): $#stack_locations \n"; 99 | printf "number of traces that have memcache in the stack: $num_memcache\n"; 100 | 101 | 102 | 103 | 104 | #my($max)=50; 105 | #printf "top $max highest stack locations (occuring least often in the histograms): \n"; 106 | # 107 | #my($i)=0; 108 | #my($loc); 109 | #foreach $loc ( sort {$stack_locations{$a} <=> $stack_locations{$b}} keys %stack_locations) { 110 | # printf "$stack_locations{$loc} $loc\n"; 111 | # if( $i++ > $max ) { last; } 112 | #} 113 | 114 | -------------------------------------------------------------------------------- /src/test/java/com/contrastsecurity/rO0/DeserializationTest.java: -------------------------------------------------------------------------------- 1 | package com.contrastsecurity.rO0; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | import java.io.ObjectInputStream; 8 | import java.io.ObjectOutputStream; 9 | import java.io.Serializable; 10 | import java.util.BitSet; 11 | 12 | import org.apache.commons.collections.functors.InvokerTransformer; 13 | 14 | import junit.framework.AssertionFailedError; 15 | import junit.framework.TestCase; 16 | 17 | public class DeserializationTest extends TestCase { 18 | 19 | 20 | public void testDeserialization_Safe() throws Exception { 21 | BitSet bitset = new BitSet(); 22 | bitset.set(1,2); 23 | File serializedFile = serialize(bitset); 24 | BitSet bitset2 = (BitSet) deserialize(serializedFile); 25 | assertEquals(bitset,bitset2); 26 | } 27 | 28 | public void testDeserialization_Unsafe() throws Exception { 29 | InvokerTransformer transformer = new InvokerTransformer("foo", new Class[]{}, new Object[]{}); 30 | File serializedFile = serialize(transformer); 31 | try { 32 | // try deserialized the file we just wrote -- should break! 33 | transformer = (InvokerTransformer) deserialize(serializedFile); 34 | fail("should have failed to deserialize!"); 35 | } catch(AssertionFailedError e) { 36 | throw e; 37 | } catch (SecurityException e) { 38 | // expected 39 | } catch (Throwable t) { 40 | fail("Shouldn't have failed for non-security reasons"); 41 | } 42 | } 43 | 44 | private File serialize(Serializable serializable) throws IOException { 45 | File tmpFile = File.createTempFile("contrast-test", ".ser"); 46 | FileOutputStream fos = new FileOutputStream(tmpFile); 47 | ObjectOutputStream oos = new ObjectOutputStream(fos); 48 | oos.writeObject(serializable); 49 | oos.close(); 50 | fos.close(); 51 | return tmpFile; 52 | } 53 | 54 | private Object deserialize(File serializable) throws Exception { 55 | FileInputStream fis = new FileInputStream(serializable); 56 | ObjectInputStream ois = new ObjectInputStream(fis); 57 | Object object = ois.readObject(); 58 | ois.close(); 59 | fis.close(); 60 | return object; 61 | } 62 | } 63 | --------------------------------------------------------------------------------