Skip to content

Commit b112258

Browse files
authored
added a new read config flag of anchor to enable/disable following an… (#164)
* added a new read config flag of anchor to enable/disable following anchors ( enabled by default ). Also added a new SafeYamlConfig class that disables Class Tags and Anchors to remediate CVE-2023-24620 CVE-2023-24621 * setAnchor() and setClassTag() now throws an IllegalArgumentException if an attempt is made to set them to true.
1 parent 08f4f7b commit b112258

File tree

4 files changed

+156
-9
lines changed

4 files changed

+156
-9
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.esotericsoftware.yamlbeans;
2+
3+
4+
/**
5+
* SafeYamlConfig extends YamlConfig and hard codes the read anchor and read class tag flags to false.
6+
* When these flags are enabled, it is possible to perform a deserialization attack if the Yaml being parsed is from an
7+
* untrusted source.
8+
* Using SafeYamlConfig is the equivalent of using YamlConfig after setting
9+
* yamlConfig.readConfig.setAnchors(false);
10+
* yamlConfig.readConfig.setClassTags(false);
11+
*
12+
* It should be noted by setting these two values neither anchors or specifying class names are supported.
13+
* It is still possible to deserialize back to a specific object, but you need to specify the Class type in the code.
14+
* e.g
15+
* SafeYamlConfig yamlConfig = new SafeYamlConfig();
16+
* YamlReader reader = new YamlReader(yamlData.toString(),yamlConfig);
17+
* Data data = reader.read(Data.class);
18+
*
19+
*/
20+
public class SafeYamlConfig extends YamlConfig {
21+
22+
23+
public SafeYamlConfig () {
24+
super();
25+
super.readConfig = new SafeReadConfig();
26+
}
27+
28+
static public class SafeReadConfig extends ReadConfig {
29+
30+
public SafeReadConfig(){
31+
super.anchors = false;
32+
super.classTags = false;
33+
}
34+
35+
@Override
36+
public void setClassTags(boolean classTags) {
37+
if(classTags) {
38+
throw new IllegalArgumentException("Class Tags cannot be enabled in SafeYamlConfig.");
39+
}
40+
}
41+
42+
@Override
43+
public void setAnchors(boolean anchors) {
44+
if(anchors) {
45+
throw new IllegalArgumentException("Anchors cannot be enabled in SafeYamlConfig.");
46+
}
47+
}
48+
}
49+
50+
}

‎src/com/esotericsoftware/yamlbeans/YamlConfig.java‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public class YamlConfig {
3737
public final WriteConfig writeConfig = new WriteConfig();
3838

3939
/** Configuration for reading YAML. */
40-
public final ReadConfig readConfig = new ReadConfig();
40+
public ReadConfig readConfig = new ReadConfig();
4141

4242
final Map<String, String> classNameToTag = new HashMap();
4343
final Map<String, Class> tagToClass = new HashMap();
@@ -268,6 +268,10 @@ static public class ReadConfig {
268268
boolean classTags = true;
269269
boolean guessNumberTypes;
270270

271+
272+
273+
boolean anchors = true;
274+
271275
ReadConfig () {
272276
}
273277

@@ -320,6 +324,11 @@ public void setAutoMerge (boolean autoMerge) {
320324
public void setGuessNumberTypes (boolean guessNumberTypes) {
321325
this.guessNumberTypes = guessNumberTypes;
322326
}
327+
328+
public void setAnchors(boolean anchors) {
329+
this.anchors = anchors;
330+
}
331+
323332
}
324333

325334
static class ConstructorParameters {

‎src/com/esotericsoftware/yamlbeans/YamlReader.java‎

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ public Object get (String alias) {
7878
return anchors.get(alias);
7979
}
8080

81+
private void addAnchor(String key, Object value) {
82+
if(config.readConfig.anchors) {
83+
anchors.put(key, value);
84+
}
85+
}
86+
87+
8188
public void close () throws IOException {
8289
parser.close();
8390
anchors.clear();
@@ -157,7 +164,7 @@ protected Object readValue (Class type, Class elementType, Class defaultType)
157164
parser.getNextEvent();
158165
anchor = ((AliasEvent)event).anchor;
159166
Object value = anchors.get(anchor);
160-
if (value == null) throw new YamlReaderException("Unknown anchor: " + anchor);
167+
if (value == null&&config.readConfig.anchors) throw new YamlReaderException("Unknown anchor: " + anchor);
161168
return value;
162169
case MAPPING_START:
163170
case SEQUENCE_START:
@@ -238,7 +245,7 @@ private Object readValueInternal (Class type, Class elementType, String anchor)
238245
Number number = valueConvertedNumber(value);
239246
if (number != null) {
240247
if (anchor != null) {
241-
anchors.put(anchor, number);
248+
addAnchor(anchor, number);
242249
}
243250
parser.getNextEvent();
244251
return number;
@@ -284,7 +291,9 @@ private Object readValueInternal (Class type, Class elementType, String anchor)
284291
convertedValue = Byte.decode(value);
285292
} else
286293
throw new YamlException("Unknown field type.");
287-
if (anchor != null) anchors.put(anchor, convertedValue);
294+
if (anchor != null) {
295+
addAnchor(anchor, convertedValue);
296+
}
288297
return convertedValue;
289298
} catch (Exception ex) {
290299
throw new YamlReaderException("Unable to convert value to required type \"" + type + "\": " + value, ex);
@@ -298,7 +307,7 @@ private Object readValueInternal (Class type, Class elementType, String anchor)
298307
if (event.type != SCALAR) throw new YamlReaderException("Expected scalar for type '" + type
299308
+ "' to be deserialized by scalar serializer '" + serializer.getClass().getName() + "' but found: " + event.type);
300309
Object value = serializer.read(((ScalarEvent)event).value);
301-
if (anchor != null) anchors.put(anchor, value);
310+
if (anchor != null) addAnchor(anchor, value);
302311
return value;
303312
}
304313
}
@@ -326,7 +335,7 @@ private Object readValueInternal (Class type, Class elementType, String anchor)
326335
} catch (InvocationTargetException ex) {
327336
throw new YamlReaderException("Error creating object.", ex);
328337
}
329-
if (anchor != null) anchors.put(anchor, object);
338+
if (anchor != null) addAnchor(anchor, object);
330339
ArrayList keys = new ArrayList();
331340
while (true) {
332341
if (parser.peekNextEvent().type == MAPPING_END) {
@@ -404,7 +413,7 @@ private Object readValueInternal (Class type, Class elementType, String anchor)
404413
if (object instanceof DeferredConstruction) {
405414
try {
406415
object = ((DeferredConstruction)object).construct();
407-
if (anchor != null) anchors.put(anchor, object); // Update anchor with real object.
416+
if (anchor != null) addAnchor(anchor, object); // Update anchor with real object.
408417
} catch (InvocationTargetException ex) {
409418
throw new YamlReaderException("Error creating object.", ex);
410419
}
@@ -426,7 +435,7 @@ private Object readValueInternal (Class type, Class elementType, String anchor)
426435
elementType = type.getComponentType();
427436
} else
428437
throw new YamlReaderException("A sequence is not a valid value for the type: " + type.getName());
429-
if (!type.isArray() && anchor != null) anchors.put(anchor, collection);
438+
if (!type.isArray() && anchor != null) addAnchor(anchor, collection);
430439
while (true) {
431440
event = parser.peekNextEvent();
432441
if (event.type == SEQUENCE_END) {
@@ -440,7 +449,7 @@ private Object readValueInternal (Class type, Class elementType, String anchor)
440449
int i = 0;
441450
for (Object object : collection)
442451
Array.set(array, i++, object);
443-
if (anchor != null) anchors.put(anchor, array);
452+
if (anchor != null) addAnchor(anchor, array);
444453
return array;
445454
}
446455
default:
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.esotericsoftware.yamlbeans;
2+
3+
import org.junit.Test;
4+
5+
import java.util.HashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
import static junit.framework.Assert.assertEquals;
10+
import static junit.framework.Assert.assertNull;
11+
import static junit.framework.Assert.assertTrue;
12+
13+
public class SafeYamlConfigTest {
14+
15+
16+
private static final String TESTOBJECT_TAG = "!com.esotericsoftware.yamlbeans.SafeYamlConfigTest$TestObject";
17+
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
18+
19+
20+
@Test
21+
public void testDeserializationOfClassTag() throws YamlException {
22+
SafeYamlConfig yamlConfig = new SafeYamlConfig();
23+
StringBuilder yamlData = new StringBuilder();
24+
yamlData.append(TESTOBJECT_TAG).append(LINE_SEPARATOR)
25+
.append("a: test").append(LINE_SEPARATOR);
26+
YamlReader reader = new YamlReader(yamlData.toString(),yamlConfig);
27+
Object data = reader.read();
28+
assertTrue(data instanceof HashMap);
29+
Map dataMap = (Map) data;
30+
assertTrue(dataMap.containsKey("a"));
31+
assertEquals("test",dataMap.get("a"));
32+
}
33+
34+
35+
36+
@Test
37+
public void testIgnoreAnchor() throws YamlException {
38+
SafeYamlConfig yamlConfig = new SafeYamlConfig();
39+
StringBuilder yamlData = new StringBuilder();
40+
yamlData.append("oldest friend:").append(LINE_SEPARATOR)
41+
.append(" &1 !contact").append(LINE_SEPARATOR)
42+
.append(" name: Bob").append(LINE_SEPARATOR)
43+
.append(" age: 29").append(LINE_SEPARATOR)
44+
.append("best friend: *1").append(LINE_SEPARATOR);
45+
YamlReader reader = new YamlReader(yamlData.toString(),yamlConfig);
46+
Object data = reader.read();
47+
assertTrue(data instanceof HashMap);
48+
Map dataMap = (Map) data;
49+
assertTrue(dataMap.containsKey("oldest friend"));
50+
Map old = (Map) dataMap.get("oldest friend");
51+
assertTrue(old.containsKey("name"));
52+
assertEquals("Bob",old.get("name"));
53+
assertNull(dataMap.get("best friend"));
54+
}
55+
56+
57+
static class TestObject {
58+
private String a;
59+
public int age;
60+
public String name;
61+
public Object object;
62+
public List<Object> objects;
63+
64+
private TestObject() {
65+
}
66+
67+
public TestObject(String a) {
68+
this.a = a;
69+
}
70+
71+
public String getA() {
72+
return a;
73+
}
74+
75+
public void setA(String a) {
76+
this.a = a;
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)