Coordinated Disclosure Timeline
- 2024-02-19: The report was sent to MSRC according to the policy.
- 2024-02-20: The report was acknowledged.
- 2024-02-28: The issue was fixed in https://github.com/NuGet/NuGetGallery/commit/c52b023659f4ad7b626874c1063f2b5e878a4fe0
- 2024-05-28: CVE-2024-37304 got assigned.
- 2024-05-28: v2024.05.28 contains the fix.
Summary
NuGetGallery powers https://nuget.org/ - the main public source for dotnet packages. Readme files in markdown format associated with NuGet packages are rendered as HTML. NuGetGallery filters JavaScript from links, but fails to do so with autolinks.
Project
NuGetGallery
Tested Version
Details
JavaScript protocol links in the rendered markdown (GHSL-2024-016
)
NuGetGallery displays ReadMe markdown files either embedded or external to a NuGet package. In order to do that it converts the markdown ReadMe to a HTML file with the help of the markdig library by default. Raw HTML in markdown is not allowed [1] and protocols other than HTTP or HTTPS [2] are sanitized [3] in links.
However the sanitization fails for autolinks. A markdown such as <javascript:alert(1)>
or any other protocol is rendered as a link with the same text.
MarkdownService.cs:
var pipeline = new MarkdownPipelineBuilder()
.UseGridTables()
.UsePipeTables()
.UseListExtras()
.UseTaskLists()
.UseEmojiAndSmiley()
.UseAutoLinks()
.UseReferralLinks("noopener noreferrer nofollow")
.UseAutoIdentifiers()
.UseEmphasisExtras(EmphasisExtraOptions.Strikethrough)
.DisableHtml() //block inline html <------------------------------ [1]
.UseBootstrap()
.Build();
using (var htmlWriter = new StringWriter())
{
var renderer = new HtmlRenderer(htmlWriter);
pipeline.Setup(renderer);
var document = Markdown.Parse(markdownWithoutBom, pipeline);
foreach (var node in document.Descendants())
{
if (node is Markdig.Syntax.Block)
{
...
}
else if (node is Markdig.Syntax.Inlines.Inline)
{
if (node is LinkInline linkInline)
{
if (linkInline.IsImage)
{
...
}
else
{
// Allow only http or https links in markdown. Transform link to https for known domains.
if (!PackageHelper.TryPrepareUrlForRendering(linkInline.Url, out string readyUriString))
{
if (linkInline.Url != null && !linkInline.Url.StartsWith("#")) //allow internal section links
{
linkInline.Url = string.Empty; // <------------------------------ [3]
}
}
else
{
linkInline.Url = readyUriString;
}
}
}
}
}
PackageHelper.cs:
public static bool TryPrepareUrlForRendering(string uriString, out string readyUriString, bool rewriteAllHttp = false)
{
Uri returnUri = null;
readyUriString = null;
if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri))
{
if (uri.IsHttpProtocol()) // <------------------------------ [2]
{
if (rewriteAllHttp || uri.IsDomainWithHttpsSupport())
{
returnUri = uri.ToHttps();
}
else
{
returnUri = uri;
}
}
else if (uri.IsHttpsProtocol() || uri.IsHttpProtocol())
{
returnUri = uri;
}
}
if (returnUri != null)
{
readyUriString = returnUri.AbsoluteUri;
return true;
}
return false;
}
Impact
The attacker can only obfuscate the name of a rendered autolink. The payload may raise suspicion and some social engineering may be needed to trick the victim to click on the link. NuGetGallery cookies have HttpOnly flag set, however there are other actions attackers can make through XSS - redirecting the victim, spoofing the content/links on the page, extracting data from the page.
Steps to reproduce
- Create a NuGet package with the following readme.md:
For more information click on the link: <javascript:%61%6C%65%72%74%28%64%6F%63%75%6D%65%6E%74%2E%64%6F%6D%61%69%6E%29>
- Upload the created package to NuGetGallery.
- Navigate to the listed package and click on the
javascript:%61%6C%65%72%74%28%64%6F%63%75%6D%65%6E%74%2E%64%6F%6D%61%69%6E%29
link.
CVE
- CVE-2024-37304
Credit
This issue was discovered and reported by GHSL team member @JarLob (Jaroslav Lobačevski).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2024-016
in any communication regarding this issue.