🤬
Offensive Umbraco

Part 4 – Let's Get Pasted

Steven Harland

whoami 👀

  • Offensive Security Consultant
  • Former Umbraco Developer
  • Umbraco Security Contributor

Security Advisories – March 2023

https://umbraco.com/blog/security-advisory-march-21-2023-patch-is-now-available/

Demo!

Vulnerable Code

					
var tmpImages =
	htmlDoc.DocumentNode.SelectNodes("//img[@data-tmpimg]");

foreach (var img in tmpImages)
{
	var tmpImgPath = img.GetAttributeValue("data-tmpimg");
	// tmpImgPath = "appsettings.json"

	var absoluteTempImagePath =
		_hostingEnvironment.MapPathContentRoot(tmpImgPath);
	// absoluteTempImagePath =
	//   "C:\inetpub\wwwroot\appsettings.json"

	// absoluteTempImagePath is copied to media section...

	// Delete parent folder!!
	var folderName = Path.GetDirectoryName(absoluteTempImagePath);
	Directory.Delete(folderName, true);
}
					
				
RichTextEditorPastedImages.cs

First Patch

					
var tmpImages =
	htmlDoc.DocumentNode.SelectNodes("//img[@data-tmpimg]");

foreach (var img in tmpImages)
{
	var tmpImgPath = img.GetAttributeValue("data-tmpimg");

	if (IsValidPath(tmpImgPath) == false)
	{
		continue;
	}

	var absoluteTempImagePath =
		_hostingEnvironment.MapPathContentRoot(tmpImgPath);

	// absoluteTempImagePath is copied to media section...
}

private bool IsValidPath(string imagePath) =>
	imagePath.StartsWith("~/umbraco/Data/TEMP/FileUploads/rte");
					
				

Demo!

Vulnerable Code

					
var tmpImages =
	htmlDoc.DocumentNode.SelectNodes("//img[@data-tmpimg]");

foreach (var img in tmpImages)
{
	var tmpImgPath = img.GetAttributeValue("data-tmpimg");
	// tmpImgPath =
	//   "~/umbraco/Data/TEMP/FileUploads/rte/../../../../../appsettings.json"

	// `IsValidPath()` returns `true` because tmpImgPath starts
	// with "~/umbraco/Data/TEMP/FileUploads/rte".
	if (IsValidPath(tmpImgPath) == false)
	{
		continue;
	}

	var absoluteTempImagePath =
		_hostingEnvironment.MapPathContentRoot(tmpImgPath);
	// absoluteTempImagePath =
	//   "C:\inetpub\wwwroot\appsettings.json"

	// absoluteTempImagePath is copied to media section...
}
					
				

Second/Final Patch

					
var tmpImages =
	htmlDoc.DocumentNode.SelectNodes("//img[@data-tmpimg]");

foreach (var img in tmpImages)
{
	var tmpImgPath = img.GetAttributeValue("data-tmpimg");

	var absoluteTempImagePath =
		Path.GetFullPath(
			_hostingEnvironment.MapPathContentRoot(tmpImgPath));
	
	if (IsValidPath(absoluteTempImagePath) == false)
	{
		continue;
	}

	// absoluteTempImagePath is copied to media section...
}

private bool IsValidPath(string imagePath)
{
	var tempFolderAbsolutePath =
		_hostingEnvironment.MapPathContentRoot(
			"~/umbraco/Data/TEMP/FileUploads/rte");
	return imagePath.StartsWith(tempFolderAbsolutePath);
}
					
				

Takeaways 🍔🍟

  • Avoid exposing internal file paths on the front-end of your applications
					
						
					
				

Takeaways 🍔🍟

  • Do not pass untrusted input to HostingEnvironment.MapPath or Server.MapPath
    • ~ is mapped to the web root directory
    • ../ is mapped to the parent directory

Takeaways 🍔🍟

  • Map relative paths to absolute paths before validating the path prefix

Takeaways 🍔🍟

  • Open source = good?

@stvnhrlnd@umbracocommunity.social