Jack O'Sullivan
January 21 2021
Setup & Introduction
In late December last year, I was testing a client's thick application and it was behaving in a way I'd never seen before. So, over the holidays, I decided to copy & paste my way into having a "playground" to test clients. The "playground" is based on IdentityServer4 where the goal of the OIDC client is to retrieve an endpoint of an API after authentication with the identity server.
public class SecretController : Controller
{
[Route("/secret")]
[Authorize]
public string Index()
{
var claims = User.Claims.ToList();
return "secret message from SecApi";
}
}
The client in this "playground" is a basic WPF client that after authentication, retrieves the endpoint above, and displays the messages from the API.
Testing The WCF Client
For the "playground" to be useful, we need to assume that we don’t have access to the source code, that we have credentials to retrieve the API endpoint, and that we've been asked to test the client's security. With that in mind, the first step in testing the client should be the enumeration phase, to identify:
- [ ] what language the client is written in and which architecture.
- [ ] how the client interacts with resources.
- [ ] any "low hanging fruit".
Identifying How the Application is Built
Understanding how the client is built will be useful when it comes to exploitation. To identify the technology used, we can use a tool like CFF Explorer. Opening up the client in CFF explorer shows the following:
Great, so now we know that the client is written in .NET and is a 32-bit binary.
How does the client interact with resources?
This stage can be split into two different areas: we need to see what network communications the client makes, and then also what local files on the local file system the client interacts with.
Network Communication
To identify the network communications the client makes, we can use a network sniffer like wireshark or tcpview and use the WCF client.
As this "playground" is local, it’s a little more difficult to digest the results, however we can see there may be something at localhost:44305 and at localhost:44337.
File System Interactions
To identify any file interactions the client makes, we can use procmon.
It’s worth making a copy of the results or leaving this running while we interact with the WPF client.
Low Hanging Fruit
At this stage, we know how the application is built, and what communications/file interactions it makes. Now, it’s time to use the application and perform some basic analysis.
Strings
Sometimes developers will hard-code values in the application, and these could include a lot of useful information. To view these hard-coded values within the application, we can use strings.
The output of strings can be quite long, and you may need to look over it a few times. In this example there was the following useful information:
[...]
MainWindow.xaml
/WpfApp;component/mainwindow.xaml
WpfApp.Properties.Resources
IdentityServer Demo Login
https://localhost:44305/
wpf
openid SecApi
http://localhost/sample-wpf-app
Unexpected Error:
UserCancel
The sign-in window was closed before authorization was completed.
Bearer
https://localhost:44337/secret
d;L
z\V
6N5
WrapNonExceptionThrows
WpfAp
[...]
From that output, we can see that the client interacts with something at https://localhost:44305/ and at https://localhost:44337/secret. The application may use "Bearer" authentication. There's also "openid SecApi" - although we don’t know the use case of this, but due to the name, we'll still make a note of it.
PE Security
Thesedays, applications typically have ASLR & DEP enabled by default, however it’s never a good idea to assume anything when it comes to testing. Therefore, we'll use the PESecurity script to quickly check.
PS C:\Users\Joe Thorpe\opt\PESecurity> Import-Module .\Get-PESecurity.psm1PS C:\Users\Joe Thorpe\opt\PESecurity> Get-PESecurity -File "C:\Users\Joe Thorpe\source\repos\IDServer\WpfApp\bin\Release\WpfApp.exe"
FileName : C:\Users\Joe Thorpe\source\repos\IDServer\WpfApp\bin\Release\WpfApp.exe
ARCH : I386
DotNET : True
ASLR : True
DEP : True
Authenticode : False
StrongNaming : False
SafeSEH : N/A
ControlFlowGuard : N/A
HighentropyVA : True
Reversing
It’s worth trying to reverse engineer the application to get a further understanding of how the application works. There are many tools to do this, but in this instance, we know that the application is written in .NET so we can use dotPeek from JetBrains.
Shown above is the source code of the WCF client in dotPeek. With this information, we can make some assumptions around how the client works, and what it interacts with.
[...]
var options = new OidcClientOptions()
{
Authority = "https://localhost:44305/",
ClientId = "wpf",
Scope = "openid SecApi",
RedirectUri = "http://localhost/sample-wpf-app",
Browser = new WpfEmbeddedBrowser()
};
[...]
We can see in the above code block the authority endpoint address, as well as the intended scopes. We then see that the embedded browser is used.
[...]
try
{
result = await _oidcClient.LoginAsync();
}
[...]
else
{
var name = result.User.Identity.Name;
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
var apiResult = await client.GetStringAsync("https://localhost:44337/secret");
Message.Text = $"{apiResult}";
}
[...]
Later in the code, it shows the attempt to access the API after authentication.
Exploitation
We should now have enough information to test the WCF client at runtime. We know that the client makes HTTP requests to authentication and access the API resources.
HTTP Proxying
To identify the requests made by the WCF client, we’re going to need a HTTP proxy; I'll be using Fiddler for this, but any HTTP proxy should do. Sometimes when testing client applications, they are not proxy aware, so in that case we can use an invisible proxy.
Now we can intercept the traffic from the WPF client, we can see the requests, and within Fiddler we can edit the requests, and reissue them. At this point we can treat this like any other web application assessment, just using the WCF client to interact with the server. For example: we get a verbose error if the username & password is not correct.
Debugging the Client at Runtime
Another area of interest would be to inspect the application at runtime. We can do this by attaching a debugger to the application and reviewing the call stack. Since this is a .NET application, I’ll be using dnSpy.
Opening up the application in dnSpy, we see decompiled source code, we can also set breakpoints to inspect the application at certain processes.
Once a breakpoint is hit, we can see the "Locals", in this example, we can see the URI the client is authenticating to:
A Quick Summary
Okay, so that should give you a good idea of a basic approach to testing compiled applications. We've gone through the initial setup, testing the WCF Client, identifying how the app is built, and had a look at how the client interacts with resources. We've also analysed network comms, file system interactions, taken advantage of low hanging fruit, as well as looked at Strings, PE security, exploitation, HTTP proxying, and debugging the client at runtime.
Want more of Joe’s insights? Check out his LinkedIn, Twitter, or head to Secarma Labs' Twitter for more offensive security musings from our testing team.
Interested in developing your pentesting knowledge? This year, we're running a series of Hacking & Defending security training courses, and if you'd like to get involved, check out our Training page.