When using a lot of references to nodes in the application.yaml
the parser fails with the error: Number of aliases for non-scalar nodes exceeds the specified max=50
.
This happens because in org.springframework.boot.env.OriginTrackedYamlLoader#createYaml
a new org.yaml.snakeyaml.LoaderOptions
object is created, which has per default an upper limit of 50, because of Billion laugh attacks.
This limit is new with either Spring Boot 2.2 or 2.3, it did not exist (at least wasn't that low) in 2.1.
There is also no way for a user to increase the limit.
In my opinion application.yaml
files can be considered trusted and the limit should be severely increased or disabled. I can make a Pull Request for this, but I am not sure what your preferred solution is.
Full Exception:
java.lang.IllegalStateException: Failed to load property source from 'jar:file:/opt/svn/mainframe-application-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/application-defaults.yaml' (classpath:/application-defaults.yaml) for profile defaults
at org.springframework.boot.context.config.ConfigFileApplicationListener$Loader.load(ConfigFileApplicationListener.java:554)
at org.springframework.boot.context.config.ConfigFileApplicationListener$Loader.loadForFileExtension(ConfigFileApplicationListener.java:488)
at org.springframework.boot.context.config.ConfigFileApplicationListener$Loader.load(ConfigFileApplicationListener.java:469)
at org.springframework.boot.context.config.ConfigFileApplicationListener$Loader.lambda$null$7(ConfigFileApplicationListener.java:448)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at org.springframework.boot.context.config.ConfigFileApplicationListener$Loader.lambda$load$8(ConfigFileApplicationListener.java:448)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at org.springframework.boot.context.config.ConfigFileApplicationListener$Loader.load(ConfigFileApplicationListener.java:445)
at org.springframework.boot.context.config.ConfigFileApplicationListener$Loader.lambda$load$0(ConfigFileApplicationListener.java:348)
at org.springframework.boot.context.config.FilteredPropertySource.apply(FilteredPropertySource.java:54)
at org.springframework.boot.context.config.ConfigFileApplicationListener$Loader.load(ConfigFileApplicationListener.java:336)
at org.springframework.boot.context.config.ConfigFileApplicationListener.addPropertySources(ConfigFileApplicationListener.java:226)
at org.springframework.boot.context.config.ConfigFileApplicationListener.postProcessEnvironment(ConfigFileApplicationListener.java:210)
at org.springframework.boot.context.config.ConfigFileApplicationListener.onApplicationEnvironmentPreparedEvent(ConfigFileApplicationListener.java:200)
at org.springframework.boot.context.config.ConfigFileApplicationListener.onApplicationEvent(ConfigFileApplicationListener.java:188)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127)
at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:80)
at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:53)
at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:345)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at nwi.svn.mainframe.application.SvnMainframeApplication.main(SvnMainframeApplication.java:9)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:109)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:58)
at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:88)
Caused by: org.yaml.snakeyaml.error.YAMLException: Number of aliases for non-scalar nodes exceeds the specified max=50
at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:147)
at org.yaml.snakeyaml.composer.Composer.composeValueNode(Composer.java:257)
at org.yaml.snakeyaml.composer.Composer.composeMappingChildren(Composer.java:248)
at org.yaml.snakeyaml.composer.Composer.composeMappingNode(Composer.java:236)
at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:162)
at org.yaml.snakeyaml.composer.Composer.composeSequenceNode(Composer.java:209)
at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:160)
at org.yaml.snakeyaml.composer.Composer.composeValueNode(Composer.java:257)
at org.yaml.snakeyaml.composer.Composer.composeMappingChildren(Composer.java:248)
at org.yaml.snakeyaml.composer.Composer.composeMappingNode(Composer.java:236)
at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:162)
at org.yaml.snakeyaml.composer.Composer.composeValueNode(Composer.java:257)
at org.yaml.snakeyaml.composer.Composer.composeMappingChildren(Composer.java:248)
at org.yaml.snakeyaml.composer.Composer.composeMappingNode(Composer.java:236)
at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:162)
at org.yaml.snakeyaml.composer.Composer.getNode(Composer.java:95)
at org.yaml.snakeyaml.constructor.BaseConstructor.getData(BaseConstructor.java:134)
at org.yaml.snakeyaml.Yaml$1.next(Yaml.java:494)
at org.springframework.beans.factory.config.YamlProcessor.process(YamlProcessor.java:200)
at org.springframework.beans.factory.config.YamlProcessor.process(YamlProcessor.java:164)
at org.springframework.boot.env.OriginTrackedYamlLoader.load(OriginTrackedYamlLoader.java:76)
at org.springframework.boot.env.YamlPropertySourceLoader.load(YamlPropertySourceLoader.java:50)
at org.springframework.boot.context.config.ConfigFileApplicationListener$Loader.loadDocuments(ConfigFileApplicationListener.java:608)
at org.springframework.boot.context.config.ConfigFileApplicationListener$Loader.load(ConfigFileApplicationListener.java:524)
... 33 common frames omitted
Comment From: wilkinsona
The limit of 50 was introduced in SnakeYAML 1.26. We upgraded to 1.26 in 2.3.
Comment From: Dav1dde
Ah this was discussed in https://github.com/spring-projects/spring-boot/issues/20366 - The way described to upgrade snakeyaml can also be used to downgrade it again.
I haven't tried it, since I already rewrote the configuration, but I assume this works for anyone coming across the issue.
Comment From: leonard84
I would suggest to configure the LoaderOptions
used for configuration parsing to allow Integer.MAX_VALUE
for maxAliasesForCollections
and set allowRecursiveKeys
to true.
As already pointed out in #20366
Spring Boot uses SnakeYaml to parse the application configuration - so by default, no untrusted input is fed to the parser.
And I would argue that when an attacker controls your configuration files, that he can do much worse things than the billion laughs attack.
With the current settings you get another type of DOS, as you application simply won't start if you have more than 50 aliases.
And as those LoaderOptions
are local to configuration parsing those settings should not negatively affect potential other yaml parser instances.
Comment From: bencalegari
We still encountered the same exception when attempting to upgrade from 2.2.6
to 2.3.4
, so it appears that this isn't fully resolved:
Caused by: org.yaml.snakeyaml.error.YAMLException: Number of aliases for non-scalar nodes exceeds the specified max=50
at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:147)
Downgrading the version of snakeyaml to 1.25
as suggested above now fails as well since these functions in OriginTrackedYamlLoader
no longer exist:
loaderOptions.setMaxAliasesForCollections(Integer.MAX_VALUE);
loaderOptions.setAllowRecursiveKeys(true);
Resulting in:
java.lang.NoSuchMethodError: 'void org.yaml.snakeyaml.LoaderOptions.setMaxAliasesForCollections(int)'
at org.springframework.boot.env.OriginTrackedYamlLoader.createYaml(OriginTrackedYamlLoader.java:67)
Let us know if you need more information to recreate this.
Comment From: philwebb
@bencalegari Could you please open a new issue for this. If you have time to create a sample that shows the problem, that would be very helpful.