Coordinated Disclosure Timeline

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

v2023.04.25

Details

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

CVE

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.