using Programming;

A Blog about some of the intrinsics related to programming and how one can get the best out of various languages.

Doing Bad Cookie Authentication, but for the Right Reasons

Poor Cookie-Based Authentication with ASP.NET

Greetings everyone. Once again, it's been a while since I've posted anything. I've been swamped with work, personal issues, and then some. I got a new dog (hi Max!), and so on. Fortunately, I have a topic I want to talk about (you can probably guess based on the title what it is), and thanks to a friend of mine, who we'll call "Jim" becasue, well, that's his name, who asked about this on Twitter, I figured we could go all-in.

Disclaimer: I'm a rambler, and this was hastily written.

Jim was asking about cookie-based authentication in ASP.NET, which is a great topic because it's something you should absolutely never do, but we are going to do to help him demonstrate how such a thing can be used for test automation. (Jim is a VERY smart person, and is working up a demo on how we can use cookie authentication hand-in-hand with test automation to do a wide-variety of things. The idea is to allow a test client to authenticate itself to quickly and easily get into the application.) We're going to spend this whole blog-post going over a worst-practice, vs. a best-practice. We're going to do all the things I always say never do, and learn why. (There's a lot of reasons we should not do any of these things, I'll try to cover a few with some examples.)

Then, at the end, I'm going to show you how we could do this type of authentication easily and via some sort of API call, so that we could stick with the core authentication principles that we value, and also satisfy our testers. This will demonstrate where the testers and the developers should be able to work together, to develop a flow that works for both.

We're going to build this out in Visual Studio, as usual, and we'll go over how we can take a blank ASP.NET website and add cookie-based authentication. Typically, we would use Forms-based authentication, or Active Directory / Windows Authentication.

What is a cookie?

A cookie is a small (usually) piece of information that a users browser stores which allows them to carry and pass information to and from a website. This cookie is a piece of data that is sent client-to-server, and server-to-client. We use cookies to transfer non-confidential data, because cookies are incredibly insecure. Anyone can spoof a cookie, and we'll look at doing exactly that in this blog post as well.

Cookie-Based Authentication in ASP.NET

Alright, so let's go ahread and create some cookie-based authentication in our ASP.NET application. For this, we'll have three pages:

  • Default.aspx: the main landing page, visible to authenticated and unauthenticated users;
  • Login.aspx: the login page, this will test the user login and create an appropriate cookie;
  • Authenticated.aspx: a page only available to authenticated users;

Step 1: Create the Authenticated.aspx

This page is pretty basic markup-wise (even code-wise):

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Authenticated.aspx.cs" Inherits="Poor_Cookie_Authentication__17_4_2018_.Authenticated" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            Hello <asp:Literal runat="server" ID="litUserName"></asp:Literal>!
        </div>
        <div>
            This page is only available to authenticated users.
        </div>
    </form>
</body>
</html>

Basically, we throw a single literal which will be the name of the authenticated user. We'll populate this from the code-behind file, which is also really simple. But, before we do the code-behind, let's build a user object.

I'm going to do extremely basic authentication: our User class will have a Username (email) and Password. It will also contain a static array of all eligible users, so as to allow us to "pretend" to log someone in. We'll create two sample users, with different passwords:

public class User
{
    public static User[] Users { get; } =
        new[]
        {
            new User() { Username = "ebrown@example.com", Password = "1234" },
            new User() { Username = "johndoe@example.com", Password = "5678" }
        };

    public string Username { get; private set; }
    public string Password { get; private set; }
}

This can be replaced with any type of user loading, but I kept it simple to allow easy demonstration.

Now, let's load the user:

public partial class Authenticated : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request.Cookies["UserId"]?.Value == null)
        {
            Response.Redirect("Login.aspx");
        }

        var userId = int.Parse(Request.Cookies["UserId"].Value);
        var user = Models.User.Users[userId];
        litUserName.Text = user.Username;
    }
}

So, if it's not apparant, cookie reading is super simple in ASP.NET: simply call Request.Cookies[name].Value, I use ?.Value to avoid the need for an additional null-check. (If the cookie doesn't exist, Request.Cookies[name] returns null instead of throwing a KeyNotFoundException like a normal dictionary would.)

Step 2: Login.aspx

Alright, so the next step is to allow the user to login. This is really simple, and we'll do it with a small HTML page and code-behind. The login will take a username and password, and match that to a user in the User.Users array. If we find one, set a cookie with the ID.

Markup:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="Poor_Cookie_Authentication__17_4_2018_.Login" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:Literal runat="server" ID="litError"></asp:Literal><br />
            Username: <asp:TextBox runat="server" ID="txtUsername" TextMode="Email"></asp:TextBox><br />
            Password: <asp:TextBox runat="server" ID="txtPassword" TextMode="Password"></asp:TextBox><br />
            <asp:Button runat="server" ID="btnSubmit" OnClick="btnSubmit_Click" Text="Login" />
        </div>
    </form>
</body>
</html>

Code:

public partial class Login : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request.Cookies["UserId"]?.Value != null)
        {
            Response.Redirect("Authenticated.aspx");
        }
    }

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        var username = txtUsername.Text;
        var password = txtPassword.Text;

        var user = Models.User.Users.FirstOrDefault(x => x.Username == username && x.Password == password);
        if (user != null)
        {
            Response.Cookies.Add(new HttpCookie("UserId", Models.User.Users.ToList().IndexOf(user).ToString()) { Expires = DateTime.Now.AddHours(8) });
            Response.Redirect("Authenticated.aspx");
        }

        litError.Text = "The username/password combination you entered does not exist.";
    }
}

There are two ways to set a cookie:

  • Use Response.Cookies.Add;
  • Directly call to Response.Cookies[name], which will create the cookie if it does not exist;

Here, I chose the former as it's clearer. We add a new response cookie, set the expiration for 8 hours from now, and then redirect the user to the Authenticated.aspx page.

Step 3: Our Default.aspx with logout

The last step here is to make our Default.aspx page, which is again simple, and perform our logout on this page.

Markup:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Poor_Cookie_Authentication__17_4_2018_.Default" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:HyperLink runat="server" ID="hlLogin" Text="Login" NavigateUrl="~/Login.aspx"></asp:HyperLink>
            <asp:LinkButton runat="server" ID="lbLogout" Text="Logout" OnClick="lbLogout_Click"></asp:LinkButton>
        </div>
    </form>
</body>
</html>

Code:

public partial class Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request.Cookies["UserId"]?.Value == null)
        {
            lbLogout.Visible = false;
        }
        else
        {
            hlLogin.Visible = false;
        }
    }

    protected void lbLogout_Click(object sender, EventArgs e)
    {
        Response.Cookies["UserId"].Expires = DateTime.Now.AddDays(-1);
        Response.Redirect("Default.aspx");
    }
}

We literally have a "Login" and "Logout", which one is shown depends on whether or not the cookie is set. On logout, we set the cookie expiration to the past so that the browser will delete it, and redirect the user.

Authentication complete!

Alright, so all-in-all we're basicallly done with designing the cookie-based authentication of the application, now let's move on to the insecurities of it.

Cookie-Based Authentication Insecurities

There are literally hundreds of things we could list that are reasons to not use cookie-based authentication, but let's just go over some of the basics:

  • Cookies are handled entirely client-side. That means, the client must be trusted to track the entire lifetime of the cookie: value, expiration, etc., all of it is in the client's hand.
  • Cookies are always plain-data. That is to say, they are not secured in any manner. In a non-HTTPS environment, cookies are clearly visible on transmission from client-to-server, and server-to-client. A man-in-the-middle attack with cookies is so unbelievably easy. (Facebook used to be vulnerable to session-hijacking using this attack, and I'll pull references on that later.)
  • Cookies carry small amounts of data. There are limits to the size of a cookie, due to the nature of it. Because cookies are passed in the headers of a web request or response, they are limited to the maximum size of a header.

Alright, so let's exploit some cookie insecurity. We're going to do four things:

  1. Change the expiration to keep ourselves logged in longer;
  2. Change the user ID to log in as someone else;
  3. Change the user ID to cause the server to error (if you place cookies into SQL this can do really bad stuff);
  4. Create a cookie with a user ID to login without credentials;

So the first task on our to-do list is to change a cookie expiration. We set it for 8 hours, but any sufficiently skilled user (and you really don't have to be all that skilled) can alter it. In fact, if you download the EditThisCookie plugin for Opera, you can do so with a two clicks. I'm going to use this to do the rest, and I'm just going to throw all the pictures with basic captions.

Step 1: We'll Login via Opera. That "Cookie" icon is our editor.

Opera Login

Step 2: Open the cookie. We click the "Cookie" Icon and expand the cookie we want ("UserId").

Opening the Cookie

Step 3: Edit the Expiration and click the "Checkmark" to save.

Edit Expiration

As you can see, pretty easy.

Next, we'll edit the value to be someone else:

Edit Value

New Page

Now I did not relogin, I used the original login and edited who I was. That's important to remember because it demonstrates why this is insecure.

We can error the server:

Bad Cookie

And even make a new cookie without logging in:

New Cookie

Automating our Tests

Ok, on to the fun part: let's automate a login, but without ever hitting the login page or function.

This is almost too easy with .NET, we'll use the WebClient and manually send the Cookie header. To do this, I created a TestAutomation.aspx page:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestAutomation.aspx.cs" Inherits="Poor_Cookie_Authentication__17_4_2018_.TestAutomation" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:Literal runat="server" ID="litResponseData"></asp:Literal>
        </div>
    </form>
</body>
</html>

The code is trivial:

public partial class TestAutomation : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        using (var wc = new WebClient())
        {
            wc.Headers.Add("Cookie", "UserId=1");
            litResponseData.Text = wc.DownloadString("http://localhost:60078/Authenticated.aspx");
        }
    }
}

Yes, we logged in and downloaded the Authenticated.aspx text with 5 lines of code, 2 of which were braces. By manualy sending the header, we made it too easy for us to work with.

If you comment out the wc.Headers.Add line, you'll see that it returns the login form. This is because the WebClient follows the redirects. With that line in, we get the Hello ...! message.

An Ideal World

Alright, so all of this is done to demonstrate the point of how easy it is to use cookie-based authentication, and how we can exploit it, but also the ease of which it does what we want. One of the things Jim had mentioned to me was that he wanted the ability to either turn authentication off, or some other way to allow the user to login, but without needing to do too much complex work.

Typically, in this scenario, this is where the developer and tester would work together on a solution, one of which might be a very simple API call to do login: pass a username and password, then it logs that session in. We can simulate this by accepting Username and Password query-string parameters in our Login.aspx:

public partial class Login : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request.QueryString["Username"] != null && Request.QueryString["Password"] != null)
        {
            var username = Request.QueryString["Username"];
            var password = Request.QueryString["Password"];
            doLogin(username, password);
        }

        if (Request.Cookies["UserId"]?.Value != null)
        {
            Response.Redirect("Authenticated.aspx");
        }
    }

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        var username = txtUsername.Text;
        var password = txtPassword.Text;
        doLogin(username, password);
    }

    private void doLogin(string username, string password)
    {
        var user = Models.User.Users.FirstOrDefault(x => x.Username == username && x.Password == password);
        if (user != null)
        {
            Response.Cookies.Add(new HttpCookie("UserId", Models.User.Users.ToList().IndexOf(user).ToString()) { Expires = DateTime.Now.AddHours(8) });
            Response.Redirect("Authenticated.aspx");
        }

        litError.Text = "The username/password combination you entered does not exist.";
    }
}

So Login.aspx didn't change much, we just handle both cases now. By pushing login into a function, it made it easy to deal with the query-string based login, and the form-based login. We need to modify our TestAutomation.aspx page a little to accommodate:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestAutomation.aspx.cs" Inherits="Poor_Cookie_Authentication__17_4_2018_.TestAutomation" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:Literal runat="server" ID="litResponseData1"></asp:Literal><br /><br />
            <asp:Literal runat="server" ID="litResponseData2"></asp:Literal>
        </div>
    </form>
</body>
</html>

The code is the big change:

public partial class TestAutomation : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // Method 1
        using (var wc = new WebClient())
        {
            wc.Headers.Add("Cookie", "UserId=1");
            litResponseData1.Text = wc.DownloadString("http://localhost:60078/Authenticated.aspx");
        }

        // Method 2
        var cookieContainer = new CookieContainer();
        var req = WebRequest.CreateHttp("http://localhost:60078/Login.aspx?Username=ebrown@example.com&Password=1234");
        req.CookieContainer = cookieContainer;
        req.GetResponse(); // We don't need to do anything with the response

        req = WebRequest.CreateHttp("http://localhost:60078/Authenticated.aspx");
        req.CookieContainer = cookieContainer;
        var response = (HttpWebResponse)req.GetResponse();
        using (var sr = new StreamReader(response.GetResponseStream()))
        {
            litResponseData2.Text = sr.ReadToEnd();
        }
    }
}

You see the // Method 2 comment? That is the part that uses the query-string to login. It can also use a POST to a form to login, if we wanted, though that is far more complex. Due to the ASP.NET event validation, it is far easier to virtually create the UI form in that case, and submit it. Instead, what we did is query-string based to support the testers use-case, and make it easy to do the testing, while also retaining most of our security.


This project is available on GitHub and I am allowing anyone to use it to any purpose, I simply ask that if you use the project directly, throw some sort of nice message on where you found it.

Phishing: how do we identify it?

Holy phishing batman

Note: this will be part of a longer series on identifying, preventing, and remedying issues related to phishing attempts.

I don't usually post about issues like this, but this is a huge problem and can definitely ruin people's lives, I think it's important to get more information on the topic out there.


So recently I tweeted about two very obscure phishing scams I was sent:

Recently I got two very good-looking phishing emails, annotated to help future potential victims identify them and prevent scams.

It was obvious to me that they were scams, based on all the tell-tale signs of the email I was immediately thrown to the fact that they were not legitimate emails from either of these organizations. (I happen to have accounts with both of them, which made identification slightly easier.)

We're in the year 2017, and if anyone tells you Email or computer systems are dying then they're absolutely wrong. We're entering an age where the internet is becoming a necessary requirement for anyone's life, and I want to talk about one of the biggest problems with email: phishing,

What is phishing?

When I went to college (and even before then) we studied a term called 'social engineering', that is, the idea that an attacker will not attempt to hack/crack/break into your system through technical means, but will convince you to do it for them. This is the broader category that phishing fits into.

Phishing is the act of an attacker convincing a victim (usually a user) to deliver secured information to them in a means that makes the victim completely unaware that they just gave secure information away through the use of faked online forms/websites/emails. The most common (in my experience) is banking information.

With all this in mind, I want to try to help you prevent becoming a victim. There isn't a true "guide" for how you can stop yourself from becoming a victim, but there are steps you can take. I'm going to use actual phishing emails I was sent for this demonstration. Real emails you may receive and how you can identify them and prevent yourself from becoming a victim.

Do note: I'm not even going to talk about the technical aspects of these emails, this guide will be an end-user guide, for the people these emails are designed to victimize.

What does phishing look like?

To see what phishing looks like we're going to use two very real examples, which I've already annotated with some of the tell-tale signs of a phishing email (any one of these signs on it's own isn't necessarily an indicator, but all of them together add up quickly).

Screenshot 1 - Wells Fargo Phishing Attempt Screenshot 2 - Capital One Phishing Attempt

As you can see, I drew a lot of red. Let's talk about this section by section to explore how we can apply this more broadly.


The Wells Fargo email broke down

The first image is from 'Wells Fargo' (or someone that wants you to think they're Wells Fargo). We can see an issue with this right off the bat: the 'From' address. I happen to be a Wells Fargo customer (funny: the email that this phishing scam was sent to is not my account email), and all the emails I get from them have a specific from address: Wells Fargo Online <alerts@notify.wellsfargo.com>. So of course that was the first big red flag for me. However, let's assume you don't know that the usual alert email is alerts@notify.wellsfargo.com, the address itself has one fatal flaw that should give you at least a yellow flag:

wellsfargo_notification_alerts@wellsfargo.com

Generally a bank won't send you an email from bankname_notification_alerts@bankname.com, that's not their usual M.O., typically the email address is just notifications@bankname.com or alerts@bankname.com.

So let's assume that the sender address isn't the problem, that's fine. We'll move to the 'To' line. They sent this email To: Recipients. If you are using Outlook you can expand the contact card and you'll find that the Recipients email address is wellsfargo_notification_alerts@wellsfargo.com. So the attacker sent the email to themselves, and they must have blind-carbon-copied us on it. That immediately tells me it's a phishing attempt. Why would my bank send an email to itself and blind copy me in?

If that wasn't obvious enough, and for some reason you're still in the yellow zone, we have this attachment named Wells Fargo Online Verification.htm.

For those of you that aren't technical users: an htm document is synonymous for an html document, which is basically a webpage. (It is a webpage, but it's not on the web at this point, it's on your PC.) This type of document can easily be processed by a web-browser on your computer (Internet Explorer, Mozilla Firefox, Apple Safari, Opera, Microsoft Edge, Google Chrome) and can do many very cool things, and also many very bad things.

The problem is most people aren't aware of what to do with these documents, so they're not used much for actions with the end-user. Generally you can just double-click the document and it will open in your browser of choice, whatever your default is.

Professional organizations don't usually send you these types of documents. Usually when this type of information needs to be sent you get one of two things: a link to a web-page or a PDF file. Why? Because msot people know what to do with both of those. They know to click a link, and they know to download/save/print a PDF.

The problem with HTML files is that they can contain malicious data. (There are more technical terms for it, but I'll save you the hassle.) They can install files on your PC, they can change settings (in some cases), and they can make you think you're going to a legitimate banking website.

We're not going to open it yet, we're going to mark it as a red flag and continue with the email.

The next thing we see that's a moderately yellow flag is the email body. There is a lot of text in this body that rubs me the wrong way.

We recently reviewed your account, and we are suspecting that your Wells Fargo account may have been accessed from an unauthorized computer.

This may be due to changes in your IP address or location. Protecting the security of your account and of the Wells Fargo network is our primary concern.

We are asking you to immediately login and report any unauthorized withdrawals, and check your account profile to make sure no changes have been made.

This alone isn't a bad phrase, but when it's combined with the next phrase it becomes more disturbing:

To protect your account please follow the instructions below:

  • LOG OFF AFTER USING YOUR ONLINE ACCOUNT

If it were truly a security concern, the bank wouldn't just recommend logging off after you finish using your account. They would also recommend changing your credentials. If your account is being accessed by another PC/user then logging off will not fix it. (In almost all cases, this is true.) So, if the sender were truly concerned about your information, as a bank would be, the recommendation would be to change your password.

Please Download the Attachment file of your Wells Fargo Online Verification and Open on on a browser to complete your account verification process:

Verify the information you entered is correct.

We apologize for any inconvenience this may cause, and appreciate your support in helping us maintaining the integrity of the entire Wells Fargo System. Please verify your account as soon as possible.

Why would I need to download an attachment to login? If I simply need to login to my account, send me a URL/address.

Lastly:

Copyright © 1999 - 2016 Wells Fargo. All rights reserved..

No self-respecting bank would leave that double-period typo in an email. Bank emails go through a rigorous approval process, that typo alone isn't justification for a phishing attempt, but when added to the rest of the email, 'Mark as Spam'.


Breaking down the Capital One attempt

So we just broke the Wells Fargo attempt down, let's do the same for the Capital One phishing attempt.

I have an account with Capital One, and I have seen three distinct email addresses from them: Capital One <capitalone@service.capitalone.com>, Capital One <capitalone@notification.capitalone.com>, and Capital One <capitalone@email.capitalone.com>.

I've also seen a fourth one, but the address itself is slightly disconcerting: Capital One <capitalone@capitaloneemail.com>. That's a really bad email for a bank.

We do at least see a pattern: emails from them are generally Capital One <capitalone@somedomain.capitalone.com>, which is a mostly good thing. This means we can write off Capital One <notifications.alerts@capitalone.com> as a non-legitimate email.

Next we have the same problem as the Wells Fargo email: Recipients and the .htm attachment. We'll skip those since we talked about them above.

We get to the body, and we run into a few things that are fairly alarming:

It has come to our attention that your Billing Information records are recently changed.

That's grammatically wrong, 'records have recently changed' is better.

That requires you to verify your Billing Information. Failure to validate your billing information may result to account termination.

Capital One isn't going to terminate my auto-loan over this, they would call me and verify it first. Better: 'may result to'? Bad grammer again.

To verify your billing information, Please Download Attachment and open in a browser to Continue. We value your privacy and your preferences...

Why are 'Please Download Attachment' and 'Continue' all upper-case on the first letter (capital-case)? That's not normal. Just as well: 'value your privacy and your preferences'? What does that even mean? Then the three dots / elipsis? This is atrocious.

Failure to abide by these instructions may subject you to Capital One account restrictions or inactivity.

That's not what you said above.

TM and copyright © 2017 Capital One Inc. 1 Infinite Loop, MS 96-DM, Cupertino, CA 95015.

For those who don't know, that's Apple's address.


Summary

Overall, I hope this is helpful to increase your ability (and your friends, family, coworkers and loved one's abilities) to identify phishing scams that look legitimate, and prevent becoming a victim to the tactics that these attackers use to steal your information. In a future blog post I'll talk about why it's important to identify them, and how they steal your information.

Demonstrating Insecurity of Managed Windows Program Memory

Is Memory in a Managed Windows Program Secure?

Recently I was on one of the (many) Stack Exchange sites answering a question a user posted, like usual. This question was a bit different though: the asker was concerned about the best way to make sure people couldn't read the password the user entered out of memory.

Unfortunately, this is not a task that can really be solved on consumer devices. Anyone with enough knowledge (and it's not really a lot) can do it. I'm going to demonstrate how to today with a simple Visual Studio programme.

Essentially, what I'm going to do is 'connect' to a fake SQL server (nothing about it will really exist) and then demonstrate how one can (with a copy of Visual Studio) extract the entire Connection String of that SQL connection out. It's actually quite trivial and with enough practice can be done in seconds.

Of course there are other ways to do this, it can be done programatically, there are other bits of software for it, etc. I'm just going to demonstrate how any developer can do it with the tools (s)he has at their disposal.

Creating our test projects

So the first step is to create a test project we can use to 'attack'. We're going to consider this an attacker/victim scenario, since that's what one of the real world applications is.

Our code is going to be pretty simple:

using Evbpc.Framework.Utilities.Prompting;
using System;
using System.Data.SqlClient;

namespace VictimApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var consolePrompt = new ConsolePrompt(null);

            var connectionString = new SqlConnectionStringBuilder();
            connectionString.DataSource = consolePrompt.Prompt<string>("Enter the SQL server hostname/ip", PromptOptions.Required);
            connectionString.UserID = consolePrompt.Prompt<string>("Enter the SQL server user id", PromptOptions.Required);
            connectionString.Password = consolePrompt.Prompt<string>("Enter the SQL server password", PromptOptions.Required);
            connectionString.InitialCatalog = consolePrompt.Prompt<string>("Enter the SQL server database", PromptOptions.Required);

            using (var sqlConnection = new SqlConnection(connectionString.ToString()))
            {
                try
                {
                    Console.WriteLine("Connecting...");
                    sqlConnection.Open();

                    using (var command = new SqlCommand("SELECT 15", sqlConnection))
                    {
                        Console.WriteLine($"Command output: {command.ExecuteScalar()}");
                    }
                }
                catch
                {
                    Console.WriteLine("Could not establish a connection to the server.");
                }
            }

            Console.WriteLine("Press enter to exit.");
            Console.ReadLine();
        }
    }
}

Do note this uses my ConsolePrompt from GitHub.

So we have our victim application, now we'll go ahead and attack it.

Attaching Visual Studio to a running application

You're probably expecting a title like 'attacking an application with Visual Studio' but that's not as descriptive as what we're doing. Yes, this is how you attack it, but attack sounds nefarious. We're not doing anything nefarious, we're just attaching a debugger to a running application.

So we're going to open a new instance of Visual Studio, and not open or create a project. Just open the instance.

Screenshot 1 - Fresh Visual Studio Instance

So, we've opened Visual Studio (I'm using 2015 but this should work on 2010+). The next thing we'll do is launch our application.

Screenshot 2 - Launch Application Outside Debugger

Right, so we have the application running outside the debugger. No other instances of Visual Studio need to be open, nothing else needs to be running, just that application and our fresh instance. The next step is to attach the debugger to a process.

This is under Debug -> Attach to Process. You should see a new window open, and we want to find our 'Victim Application' (VictimApplication.exe).

Screenshot 3 - Attach to Process

We'll go ahead and attach it. Our screen should change to look like we're in a regular debug session, even though we didn't launch the program through Visual Studio.

Screenshot 4 - Debug Session is Green err Blue

Now we still have our other window open with our running application in it. All we have to do next is start checking it out and see what we can inspect.

This next part isn't required, but it should help you familiarize yourself with what we're going to do. Let's hit the 'Break All' button (CTRL + ALT + Break with default shortcuts).

Screenshot 5 - Break mode

As of this moment the program is paused. Since it's a console application, you can still type into it, but your typing will not be processed by the program at this point.

Screenshot 6 - Text not handled by program

Next we'll hit 'Show Diagnostic Tools' and then select the 'Memory Usage' tab. Once we have done that, we'll hit 'Take Snapshot'.

Screenshot 7 - Taking our first memory snapshot

So now we're at the point we can start inspecting objects in our program. The first thing we'll want to do is click the blue 429 (your number may vary) link to the list of objects.

We'll then sort them by name since we're not concerned about the count, we just want to look through them.

I'm going to inspect our ConsolePrompt as an example, which in this case is listed as Evbpc.Framework.Utilities.Prompting.ConsolePrompt. When you find an object you want to inspect, hover over it and you should see an icon that looks like a square grid with a circular shape on the top-left corner, click that and a new page should open.

Screenshot 8 - Selecting an object to inspect

We'll then see a new page with all the instances of that object listed. If you hover over the Value, you should get a tool-tip that has a breakdown of the object itself, which you can explore just like normal. We'll see that the Logger is in fact null like we wanted.

Screenshot 9 - Exploring our object

Now that we've played with our explorer, we can go ahead and close that breakdown and continue with our program. We'll hit 'Continue' and resume execution. If you typed a server host into the console, you'll see as soon as we hit continue that the program continues to the next step. We'll fill out all our requirements and then break our program again when it starts connecting, and take another memory snapshot.

Screenshot 10 - Connecting to our server

We see that the new snapshot has 3,825 objects allocated, and the difference is an increase of 3,396. Our graph shows that we allocated a lot more memory (relatively speaking) and we can now go ahead and inspect our snapshot to try to find our password. We'll be looking for a string type with a value of pass.

We know it'll be part of the SqlConnection, so we'll sort that by name and then go down to SqlConnection and explore it like before.

Screenshot 11 - Find our SqlConnection

Upon exploring it we'll just a different method of extracting our string. Click 'Referenced Objects' at the bottom of our window, and hover over the middle String object. (Mine is 0x2FB0BD8)

Screenshot 12 - Extracting our Connection String

And there we have it. We have successfully extracted our password from a separate Visual Studio instance while the original application was running completely separately.

Debug Symbols and why they are important

Of course, our demonstration was made slightly easier by the inclusion of the .pdb files (debug symbols), usually you won't have access to these for the running application, so you'll have to look a little harder sometimes to find what you're looking for.

If you don't know what Debug Symbols are, Wikipedia has a nice description. Essentially, the pdb file (stands for 'Program Database') is the symbol map for .NET programs. It contains each generated instruction header and what the generated name of it was.

Finding our String without SqlConnection

The last thing we'll do is find our string value without exploring the SqlConnection object. We're only going to look with the Diff list, and run from there.

So, we'll restart our application, then attach the debugger, then enter our host and user, then take a memory snapshot like we did earlier.

Screenshot 13 - Round 2 First Snapshot

Then we'll hit 'Continue', enter our password, and take another snapshot.

Screenshot 14 - Round 2 Second Snapshot

The next step is to disable 'Just My Code' in the filter. If we don't do this it becomes much more difficult to locate what we changed.

Screenshot 15 - Round 2 Disable Just My Code

So we see that it created one string, by the Count Diff. being +1 on the String type, this helps us narrow down what we're looking for. If click once into it, and view our 'Paths to Root', it helps us discover that we have +1 in String [Local Variable]. So we're in the right place.

Screenshot 16 - Round 2 String Local Variable

We'll inspect the String like before (Square icon with Round outset) and we'll see that by default it sorts the list by `'Inclusive Size (Bytes)', we'll sort it by 'Instance'. Theorhetically our password should be the last instance listed. If we scroll to the bottom of the list we see that, indeed, it is.

We also see that our user id is right above it.

Screenshot 17 - Round 2 Find our Password


And there we have it! We learned how to inspect objects in our program when it was launched outside Visual Studio by attaching a debug instance of Visual Studio to it.