Skip to content

Commit 7f292bf

Browse files
committed
IO-487 - ValidatingObjectInputStream, restricts which classes can be deserialized
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/io/trunk@1715217 13f79535-47bb-0310-9956-ffa450edef68
1 parent a2db8ea commit 7f292bf

10 files changed

Lines changed: 789 additions & 0 deletions
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.commons.io.serialization;
20+
21+
/** Match a Class name */
22+
public interface ClassNameMatcher {
23+
24+
/** True if the supplied class names matches.
25+
* @param className fully qualified class name
26+
* @return true if the class name matches this object's condition
27+
*/
28+
boolean matches(String className);
29+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.commons.io.serialization;
20+
21+
import java.util.Arrays;
22+
import java.util.Collections;
23+
import java.util.HashSet;
24+
import java.util.Set;
25+
26+
/** {@link ClassNameMatcher} that matches on full class names */
27+
final class FullClassNameMatcher implements ClassNameMatcher {
28+
29+
private final Set<String> classesSet;
30+
31+
/**
32+
* Constructs an object based on the specified class names.
33+
*
34+
* @param classes a list of class names
35+
*/
36+
public FullClassNameMatcher(String... classes) {
37+
classesSet = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(classes)));
38+
}
39+
40+
@Override
41+
public boolean matches(String className) {
42+
return classesSet.contains(className);
43+
}
44+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.commons.io.serialization;
20+
21+
import java.util.regex.Pattern;
22+
23+
/** {@link ClassNameMatcher} that uses regular expressions */
24+
final class RegexpClassNameMatcher implements ClassNameMatcher {
25+
26+
private final Pattern pattern; // Class is thread-safe
27+
28+
/**
29+
* Constructs an object based on the specified regular expression.
30+
*
31+
* @param regex a regular expression for evaluating acceptable class names
32+
*/
33+
public RegexpClassNameMatcher(String regex) {
34+
this(Pattern.compile(regex));
35+
}
36+
37+
/**
38+
* Constructs an object based on the specified pattern.
39+
*
40+
* @param pattern a pattern for evaluating acceptable class names
41+
*/
42+
public RegexpClassNameMatcher(Pattern pattern) {
43+
if(pattern == null) {
44+
throw new IllegalArgumentException("Null pattern");
45+
}
46+
this.pattern = pattern;
47+
}
48+
49+
@Override
50+
public boolean matches(String className) {
51+
return pattern.matcher(className).matches();
52+
}
53+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.commons.io.serialization;
20+
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.io.InvalidClassException;
24+
import java.io.ObjectInputStream;
25+
import java.io.ObjectStreamClass;
26+
import java.util.ArrayList;
27+
import java.util.List;
28+
import java.util.regex.Pattern;
29+
30+
/**
31+
* <p>
32+
* An <code>ObjectInputStream</code> that's restricted to deserialize
33+
* a limited set of classes.
34+
* </p>
35+
*
36+
* <p>
37+
* Various accept/reject methods allow for specifying which classes
38+
* can be deserialized.
39+
* </p>
40+
*
41+
* <p>
42+
* Design inspired by <a
43+
* href="http://www.ibm.com/developerworks/library/se-lookahead/">IBM
44+
* DeveloperWorks Article</a>.
45+
* </p>
46+
*/
47+
public class ValidatingObjectInputStream extends ObjectInputStream {
48+
private final List<ClassNameMatcher> acceptMatchers = new ArrayList<ClassNameMatcher>();
49+
private final List<ClassNameMatcher> rejectMatchers = new ArrayList<ClassNameMatcher>();
50+
51+
/**
52+
* Constructs an object to deserialize the specified input stream.
53+
* At least one accept method needs to be called to specify which
54+
* classes can be deserialized, as by default no classes are
55+
* accepted.
56+
*
57+
* @param input an input stream
58+
* @param acceptor a class acceptor
59+
* @throws IOException if an I/O error occurs while reading stream header
60+
*/
61+
public ValidatingObjectInputStream(InputStream input) throws IOException {
62+
super(input);
63+
}
64+
65+
private void validateClassName(String name) throws InvalidClassException {
66+
// Reject has precedence over accept
67+
for(ClassNameMatcher m : rejectMatchers) {
68+
if(m.matches(name)) {
69+
invalidClassNameFound(name);
70+
}
71+
}
72+
73+
boolean ok = false;
74+
for(ClassNameMatcher m : acceptMatchers) {
75+
if(m.matches(name)) {
76+
ok = true;
77+
break;
78+
}
79+
}
80+
if(!ok) {
81+
invalidClassNameFound(name);
82+
}
83+
}
84+
85+
/** Called to throw InvalidClassException (by default) if an invalid
86+
* class name is found in deserialization. Can be overridden, for example
87+
* to log those class names.
88+
* By default the name of the invalid class is not included in the
89+
* exception thrown, as that might give too much information from a
90+
* security point of view.
91+
*
92+
* @param className name of the invalid class
93+
* @throws InvalidClassException
94+
*/
95+
protected void invalidClassNameFound(String className) throws InvalidClassException{
96+
throw new InvalidClassException("Class name not accepted");
97+
}
98+
99+
@Override
100+
protected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {
101+
validateClassName(osc.getName());
102+
return super.resolveClass(osc);
103+
}
104+
105+
/** Accept the specified classes for deserialization, unless they
106+
* are otherwise rejected.
107+
* @param classes Classes to accept
108+
* @return this object
109+
*/
110+
public ValidatingObjectInputStream accept(Class<?>... classes) {
111+
for(Class<?> c : classes) {
112+
acceptMatchers.add(new FullClassNameMatcher(c.getName()));
113+
}
114+
return this;
115+
}
116+
117+
/** Reject the specified classes for deserialization, even if they
118+
* are otherwise accepted.
119+
* @param classes Classes to reject
120+
* @return this object
121+
*/
122+
public ValidatingObjectInputStream reject(Class<?>... classes) {
123+
for(Class<?> c : classes) {
124+
rejectMatchers.add(new FullClassNameMatcher(c.getName()));
125+
}
126+
return this;
127+
}
128+
129+
/** Accept the wildcard specified classes for deserialization,
130+
* unless they are otherwise rejected.
131+
* @param patterns Wildcard filename patterns as defined by
132+
* {@link FilenameUtils.wildcardMatch}
133+
* @return this object
134+
*/
135+
public ValidatingObjectInputStream accept(String ... patterns) {
136+
for(String pattern : patterns) {
137+
acceptMatchers.add(new WildcardClassNameMatcher(pattern));
138+
}
139+
return this;
140+
}
141+
142+
/** Reject the wildcard specified classes for deserialization,
143+
* even if they are otherwise accepted.
144+
* @param patterns Wildcard filename patterns as defined by
145+
* {@link FilenameUtils.wildcardMatch}
146+
* @return this object
147+
*/
148+
public ValidatingObjectInputStream reject(String ... patterns) {
149+
for(String pattern : patterns) {
150+
rejectMatchers.add(new WildcardClassNameMatcher(pattern));
151+
}
152+
return this;
153+
}
154+
155+
/** Accept class names that match the supplied pattern for
156+
* deserialization, unless they are otherwise rejected.
157+
* @param pattern standard Java regexp
158+
* @return this object
159+
*/
160+
public ValidatingObjectInputStream accept(Pattern pattern) {
161+
acceptMatchers.add(new RegexpClassNameMatcher(pattern));
162+
return this;
163+
}
164+
165+
/** Reject class names that match the supplied pattern for
166+
* deserialization, even if they are otherwise accepted.
167+
* @param pattern standard Java regexp
168+
* @return this object
169+
*/
170+
public ValidatingObjectInputStream reject(Pattern pattern) {
171+
rejectMatchers.add(new RegexpClassNameMatcher(pattern));
172+
return this;
173+
}
174+
175+
/** Accept class names where the supplied ClassNameMatcher matches for
176+
* deserialization, unless they are otherwise rejected.
177+
* @param m the matcher to use
178+
* @return this object
179+
*/
180+
public ValidatingObjectInputStream accept(ClassNameMatcher m) {
181+
acceptMatchers.add(m);
182+
return this;
183+
}
184+
185+
/** Reject class names where the supplied ClassNameMatcher matches for
186+
* deserialization, even if they are otherwise accepted.
187+
* @param m the matcher to use
188+
* @return this object
189+
*/
190+
public ValidatingObjectInputStream reject(ClassNameMatcher m) {
191+
rejectMatchers.add(m);
192+
return this;
193+
}
194+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.commons.io.serialization;
20+
21+
import org.apache.commons.io.FilenameUtils;
22+
23+
/** {@link ClassNameMatcher} that uses simplified regular expressions
24+
* provided by {@link FilenameUtils#wildcardMatch} */
25+
final class WildcardClassNameMatcher implements ClassNameMatcher {
26+
27+
private final String pattern;
28+
29+
/**
30+
* Constructs an object based on the specified simplified regular expression.
31+
* @param regex a {@link FilenameUtils#wildcardMatch} pattern.
32+
*/
33+
public WildcardClassNameMatcher(String pattern) {
34+
this.pattern = pattern;
35+
}
36+
37+
@Override
38+
public boolean matches(String className) {
39+
return FilenameUtils.wildcardMatch(className, pattern);
40+
}
41+
}

0 commit comments

Comments
 (0)