在去年12月,我们向您报告了CVE-2022-1471。在适当情况下,这个不安全的避免问题很容易导致任意代码执行。
在“深度潜水博客”文章中,我解释了该库中的问题以及如何执行它。问题的要旨是,默认情况下,Snakeyaml将传入的YAML解析为通用对象类型。这创造了一个机会,可以使课程路径上可用的其他类别化。无论抛出的ClassCastException
如何,如果对象已经加载,则损坏都会造成。
影响很大程度上取决于您如何使用库。例如,许多开发人员仅使用YAML为其应用提供配置。只有当您接受来自未知来源的YAML时,这种漏洞才能利用。但是,我们简直无法预测人们将如何使用这样的库,默认设置应该是安全的。
用Snyk开源升级Snakeyaml
让我们使用Snyk开源来找到旧的SnakeYaml
库的替代品。如果我与Snyk CLI本地运行snyk test
,我会发现有一个可用的替代品。
Web界面还告诉我,可以使用SnykyAML 2.0版本来解决该问题。唯一的问题是Spring Boot 3仍将1.X版本发票,因此我必须在Maven或Gradle清单文件中手动替换版本。
请注意,手动将库更改为新的主要版本可能会破坏事物。不要盲目地进行这些更改,并意识到这对应用程序内部运作的潜在影响。
用Snakeyaml 2.0缓解避难所
SnakeyAML 2.0于2023年初发布,以减轻可能导致可能的任意代码执行的默认行为。在此版本中,每个新YAML()使用的构造函数现在扩展了SafeConstructor。结果,我们只能解析有限的类型。 Snakeyaml的Safeconstrutor可以构建标准Java类,例如原始类和基本类,例如字符串和地图。
默认情况下不能再解析特定类型。因此,下面我的yaml文件将提供一个例外:
!!Gadget ["env"]
Exception in thread "main" Global tag is not allowed: tag:yaml.org,2002:Gadget
in 'reader', line 1, column 1:
!!Gadget ["env"]
^
现在,默认实现不再脆弱。但是,这是一个打破的变化,因此您的初始代码可能会不再起作用。
使用SnakeyAml 2.x时如何修复我的yaml解析逻辑
首先,我们必须确保不再使用snakeyaml 1.x。即使是最新的Spring Boot版本3.1当前也没有与Snakeyaml 2.x一起发货。这意味着我们必须在清单文件中升级它。对于Maven实施,我们可以使用POM文件among other things的一部分。在Gradle中,也可以使用dependency constraints。
更新及其依赖性。请注意,与早期版本相比,SnakeyAml 2.x会破坏API。这意味着我们必须将YAML解析实施重写为新的安全默认设备,以使其再次工作。
让我们考虑一个非常简单的域,其中有两个类:
- 人
- 评论
person.java:
public class Person {
private String name;
private int age;
private Comment comment;
public Person() {
}
public Person(String name, int age, Comment class2) {
this.name = name;
this.age = age;
this.comment = class2;
}
//getters and setters
}
comment.java:
public class Comment {
private String text;
private String dateTime;
public Comment() {}
public Comment(String text) {
this.text = text;
this.dateTime = LocalDateTime.now().toString();
}
//getters and setters
}
如果要从上述实体创建yaml文件,则可能在使用SnakeyAml 1.x时写下类似于以下内容的内容:
var john = new Person("John", 31, new Comment("This is a comment"));
dumpYaml(john, "file.yaml");
public static void dumpYaml(Person pojo, String filename) throws IOException {
Yaml yaml = new Yaml();
try (FileWriter writer = new FileWriter(filename)) {
yaml.dump(pojo, writer);
}
}
这将导致以下yaml文件:
!!mypackage.Person
age: 31
comment: {dateTime: '2023-06-15T16:53:23.175989', text: This is a comment}
name: John
使用snakeyaml 2.x,!!mypackage.Person
不再出现了。现在,将对象解析到yaml文件时,我们可以摆脱对象引用。但是,在此迁移之前导出的YAML文件仍然存在问题。
幸运的是,什么时候仍然可以解决此问题!解析特定对象时,您可以设置解析器需要使用的构造函数。此外,我们可以将特定的TagInspector
添加到允许我们的包装标签的TagInspector
中。这使您只允许允许适合对象的YAML文件,并且与先前与1.x版本创建的YAML向后兼容。
public static Person parseYaml(String filename) throws IOException {
var loaderoptions = new LoaderOptions();
TagInspector taginspector =
tag -> tag.getClassName().equals(Person.class.getName());
loaderoptions.setTagInspector(taginspector);
Yaml yaml = new Yaml(new Constructor(Person.class, loaderoptions));
try (InputStream in = new FileInputStream(filename)) {
// Parse the YAML file into a mypackage.MyYamlClass object
Person obj = yaml.load(in);
return obj;
}
}
此外,最好从YAML文件中删除对实际对象的引用或标签。在SnakeyAml的早期版本中,这已经可以通过将代表添加到您的YAML对象中,以将顶级对象的标签映射到映射中。下面,您会看到一个与Snakeyaml 2.x兼容的示例:
public static void dumpYaml(Person pojo, String filename) throws IOException {
Representer customRepresenter = new Representer(new DumperOptions());
customRepresenter.addClassTag(Person.class, Tag.MAP);
Yaml yaml = new Yaml(new Constructor(Person.class, new LoaderOptions()),
customRepresenter);
try (FileWriter writer = new FileWriter(filename)) {
yaml.dump(pojo, writer);
}
}
yaml文件将不再以!!mypackage.Person
(或类似)开头。如果您所有的YAML文件都干净,则可以从解析器中删除Taginspector表单。
与Snyk保持最新
与库的所有版本保持最新有关,对于开源安全性至关重要。如果您直接或间接接受外部来源的YAML文件,则使用SnakeyAml 1.x版本可能会导致不必要的安全问题。
Snyk Open Source可以帮助您找到并解决这些问题,或者在必要时将您指向替代版本。 /p>
另外,如果您将GIT存储库连接到Snyk,即使没有关键的安全问题,我们也可以提供拉动请求,以使您的依赖关系保持最新状态。最好的预防是最好的,并且在图书馆中保持最新有助于您最大程度地降低脆弱性的风险,并减少由于安全问题而随附的额外工作。