Normal view
Before yesterdaySecurifera
6 March 2023 at 22:00
Vocera Report Server Pwnage
24 April 2023 at 14:36
This article is in no way affiliated, sponsored, or endorsed with/by Vocera Communications or Stryker Corporation. All graphics are being displayed under fair use for the purposes of this article.
Quest for RCE
Last year during a routine penetration test, our team came across a interesting target called Vocera Report Server while reviewing web endpoint screenshots.
A little research revealed that the “Vocera Report Server software and the associated report console interface provide administrators, managers, and decision makers the ability to monitor system performance and generate reports for analysis” for the Vocera Communication System. When we click on the “Vocera Report Console” link we are greeted with the following login page.
Step 1: head over to Google to see if we can find any documentation that might list default credentials for the application. As luck would have it, this page comes up and kindly tells us what the default password would be.
Fortunately the system owner didn’t change the password and we log right in. Once inside we start perusing the various endpoints to get a feel for what the application is used for. Right off, the first thing that stands out is the menu that is named “Task Scheduler”. Clicking on the menu brings up a panel that appears to let you create tasks that will be executed.
After tinkering with the various tasks, it appears we can only edit existing tasks. We also can’t seem to get arbitrary command execution or injection by modifying the existing entries. At this point we decided it would likely be more fruitful to move on to a white box approach and see what the code is actually doing. We reached out to a colleague to get us access to the server using some credentials they had cracked after pulling the hash with Responder.
Since the application is written in Java, we open up the class files in JD-GUI to begin analyzing the function responsible for executing tasks. The first issue we notice is that the function that parses the user-controlled task execFileName attempts to retrieve the filename portion of the path by searching for the last occurrence of a backslash. Unfortunately in Java, forward slashes are automatically normalized into directory separators in a file path. This means we can traverse out of the intended directory.
While the path to the executable task is controllable, a check is performed that ensures that the file contains the word “java” before executing. This means if we can control the contents of a file on disk, we can execute arbitrary commands.
How then do we get a file on disk that we control? What if we can affect the log file? It looks like if an exception happens when executing the task, a log entry is created.
Sure enough, if we specify a task execfilename that doesn’t exist, an entry is created in the log file that also includes the task parameters that we can inject arbitrary data. With a little creativity, we are able to inject arbitrary commands and then point to the log file using the directory traversal to achieve remote code execution.
Can we do better?
With a path to execute arbitrary commands identified, we shifted our focus to finding a way to accomplish the same thing but without having to authenticate first. While investigating the task execution code in the previous exercise, we noticed there is a websocket interface that the web server communicates with when executing a task. After some testing it was determined that this interface was unauthenticated. In addition to the “runTaskPage” function mentioned above, there are a few other operations that appear to be related to database management functions that are worth investigating.
If we look at the code for the restoreSqlData operation we see that it executes a bat file that in turn executes another Java JAR. Inside that JAR, the function that handles the restoreSqlData operation parses the “uploadFile” parameter and appends it to the local “backupDir”. This instance is also vulnerable to directory traversal like the one mentioned previously.
The specified file is expected to be a zip file that is then programmatically unzipped and written to disk. The problem, as you could probably guess, is the unzip function is vulnerable to directory traversal which could lead to an arbitrary file write. If the unzip succeeds, a particular file is read from the archive that is then used to completely overwrite the database. DANGER: THIS OPERATION OVERWRITES THE DATABASE SO ADDITIONAL MEASURES NEED TO BE TAKEN TO PREVENT THIS.
Since this server hosts a web server and is running as SYSTEM, an arbitrary file write can be used to achieve remote code execution by writing a webshell in the webroot. To summarize, in this instance we have found an unauthenticated endpoint that allows for a privileged file write if we can place an arbitrary file somewhere on the file system. We lack one more primitive to pull off this exploit. Back to the code!
Given the specific requirement for a file write, we search for any references to “write” and work our way back to any web endpoints that would reach that code. This concept is often referred to as source to sink data flow analysis. After some time we find a class called MultipartRequest. This class is instantiated from an incoming multipart/form-data request. If the requests contains any parameters that are named filename, the data is read and written to a file in a temp folder.
If we search for references of RequestContainer, the class responsible for creating MultipartRequest instances, we see it is created by the BaseController class on the handling of each HTTP request. Since BaseController is an abstract class, we search for any child classes and find ReportController. This is perfect since ReportController is the primary endpoint for the application. This means if we send an HTTP request with Content-Type multipart/form-data to the ReportController endpoint, the contents of any parameters with a Content-Disposition that contains a filename will be written to disk in a temp directory. This best part is this is all unauthenticated (another bug)!
Now we have all the pieces necessary to construct an exploit chain to gain unauthenticated remote code execution on the Vocera Report Server. First we construct a malicious zip file with a webshell embedded with a directory traversal path. Next we upload a zip file to the temp directory with a multipart request. Finally we send a websocket request with the restoreSqlData operation with a directory traversal path to our uploaded zip file.
WAIT!!! HOW DO I NOT CLOBBER THE DATABASE?!?!
As much as any customer likes red teamers proving exploitation, nuking an application’s database is probably not a reasonable loss to prove code execution. That means we needed to put in a little more effort into the exploit to avoid this. If we look at the SQL restore function, we can see that if a ZipException is thrown (that is not a version issue), the function will bail out.
How then do we cause a ZipException while also successfully executing our arbitrary file write? If we look at the JDK source code for ZipInputStream we can see a simple way to cause a ZipException to be thrown from a specific ZipEntry. If we set the first bit of the flag field in the ZipEntry a ZipException will be thrown since encryption is not supported.
If we lookup the offset for LOCFLG we see it is at index 6 in the ZipEntry header. We can code up some python to modify the zip entry contents after we zip up our payload as shown below.
Copy to Clipboard
Vendor Disclosure & Patch
I reported these issues through the Stryker vulnerability disclosure program and can say everything went smoothly and they worked with us to get the issues fixed and patched in a reasonable time frame. Given the severity of these findings, we strongly encourage anyone that has Vocera Report Server deployed to update to the latest version immediately. For tracking purposes, the vulnerabilities discussed here represent CVE-2022-46898, CVE-2022-46899, CVE-2022-46900, CVE-2022-46901, and CVE-2022-46902.
ScienceLogic Dumpster Fire
16 August 2023 at 02:50
This article is in no way affiliated, sponsored, or endorsed with/by ScienceLogic, Inc. All graphics are being displayed under fair use for the purposes of this article.
Just another Day
During a penetration test for a client last year, our team identified a noteworthy target that piqued our interest. A screenshot of the website appeared in our scan findings.
After a brief investigation, we found a page that provided a clear overview of the web application’s potential function and its default credentials.
The default credentials for the phpmyadmin server on port 8008 were also provided. This detail becomes crucial later, as it grants the ability to directly modify records within the application database.
Regrettably, the system owner had not updated the default passwords, allowing us to access the system. Immediately noticeable was a menu named “Device Toolbox.” We started examining the parameters provided to these tools, as they frequently have command injection vulnerabilities due to inadequate input filtering.
After trying different payloads in the request fields, the following screen appeared as we navigated through the tool wizard.
Having demonstrated command execution, we swapped the payload for a reverse shell callback and launched it. This granted us shell access to the server. Considering the simplicity of uncovering this initial command injection vulnerability, we believed there might be more similar flaws. Now, with system access, we can observe process creation events using one of our preferred tools, Pspy. As expected, four additional command injection vulnerabilities were identified by exercising various endpoints in the web application and monitoring process creation events in Pspy. An example is illustrated below.
Having access to the file system, we proceeded to examine the web application’s source code. This would make identifying vulnerabilities simpler than through blackbox testing. However, when we tried to view the PHP files, they seemed to be indecipherable.
What about root?
Given our inability to access the web application’s source code, we shifted our focus towards pinpointing potential paths for privilege escalation to root. We uploaded and ran LinPeas on the system to identify any potential privilege escalation vulnerabilities. While LinPeas didn’t reveal anything of particular use, a copy of the sudoers file was located in one of the backup folders on the file system. It had several applications that could be executed as root, without a password, that have known privilege execution capabilities.
The find command is one of my gotos after running across this article years ago. Running the following command will execute the listed command as root on the system.
Copy to Clipboard
Having gained root privileges, we could now revisit the web application and hopefully find a way to review the source code for vulnerabilities.
Let’s briefly diverge to discuss the DMCA and the laws surrounding the circumvention of copyright protections. I bring this up because even basic encoding or encryption of source code might be viewed as a method to safeguard copyright. This could potentially hinder security researchers from examining the source code for vulnerabilities. Fortunately, Title 37 recently updated laws surrounding this topic effectively granting security researcher exception to this rule if done under the auspices of good-faith security research. Given that we are red teamers performing good-faith security research, securing our customers against critical vulnerabilities, we clearly fall under this exception.
We took a look at the PHP configuration and noticed a custom module was being used to load each source file. Analyzing the module in IDA Pro, it appears to be a simple function that decrypts the file with a static AES 256 key and then decompresses the output with zlib. Nothing fancy here.
Running this algorithm against the garbled source does the trick and we end up with normal looking PHP.
Beware what’s inside!!!
With access to the source code, we started scrutinizing it for more serious vulnerabilities. We had previously observed a command injection bug where the command was saved in the database and subsequently fetched and executed. This prompted us to search for potential SQL injection vulnerabilities, which might be escalated to remote code execution. As we examined the application endpoints, we discovered what seemed like systematic SQL injection issues. After pinpointing roughly 20 SQL injection vulnerabilities, we chose to conclude our search. A few examples are provided below.
Having identified a combined 25 command injection and SQL injection vulnerabilities we decided to stop bug hunting and reach out to the vendor to begin the disclosure process.
Vendor Disclosure & Patch
We’d love to say responsibly disclosing the vulnerabilities we discovered went smoothly, but it was easily the worst we’ve experienced. What will follow will be presented as a comical list of when responsible disclosure is probably going to go bad. In reality, all of these things happened during this one disclosure.
The vendor has no public vulnerability disclosure policy
The vendor has no security related email contacts listed on their website
The vendor Twitter account refuses to give you a security contact after you explain you want to disclose a security vulnerability.
After spamming vendor emails harvested from OSINT, the only response you get is from a random engineer. Fortunately, he forwards the email to the security director.
The security director refuses to accept your report, and instead points you to a portal to submit a ticket.
After signing up for the ticketing portal, you find that you can’t submit a ticket unless you are a customer.
When you notify the company that you can’t submit a ticket unless you are a customer, they tell you to have your customer submit the report.
When you send the report anyways, encrypted, hosted on trusted website, they refuse to open it because they claim it could be a phish.
Individuals from the vendor, reach out to arbitrary contacts in your customer’s organization to report you for unusual, possibly malicious behavior.
Upon verification of your identity by multiple individuals in your customer’s organization, they agree to open the results but go silent for weeks.
You receive an email from @zerodaylaw (no seriously) saying they will be representing the vendor going forward in the disclosure process.
The law firm has no technical questions about the vulnerabilities themselves, but instead about behavior surrounding post-exploitation and why this software was “targeted“.
After multiple, unresponsive, follow-up emails with both the law firm & the vendor about coordinating with @MITREcorp to get CVEs reserved, you get an email asking to meet in person, that very week.
In the follow-up phone call (after declining to meet in person), the vendor claims most of the bugs were “features” or in “dead code“.
The primary focus on the call with the vendor is how we “got” the company’s code and not about vulnerabilities details.
The vendor claims that meeting the 90-day public disclosure is unlikely and given their customer base they have no estimate on when public disclosure could happen.
After the phone call, the vendor sends an email asking questions focused on exact times, people, authorizations, and details surrounding the vulnerability coordination with @MITREcorp
Lawyers from the vendor contact your customer’s organization requesting copies of all correspondence with @MITREcorp.
From the list, it’s evident that the interaction ranged from being challenging to outright hostile. However, there was a silver lining: Securifera opted to pursue the status of a CVE numbering authority (CNA). Obtaining CNA status enables an organization to reserve and disclose CVEs more conveniently.
In the last email correspondence with the vendor, nearly 9 months ago, the security director asserted that the vulnerabilities were addressed. However, they remained reluctant to proceed with CVE issuance. Considering the extensive duration that’s transpired, we opted to independently proceed with CVE issuance and disclosure. As a result, the vulnerabilities we identified are logged as CVE-2022-48580 through CVE-2022-48604. We hope the aforementioned list can act as a guide for vendors on practices to avoid in the realm of responsible vulnerability disclosure. For vendor’s looking for a good reference for how to properly run a coordinated vulnerability disclosure program, the following guide was put together to assist by the smart people at Carnegie Mellon.
CVE-2021-27198
25 October 2023 at 01:26
This article is in no way affiliated, sponsored, or endorsed with/by Visualware, Inc. All graphics are being displayed under fair use for the purposes of this article.
Revisiting an Old Bug: File Upload to Code Execution
A colleague recently contacted me about a bug I discovered a couple of years ago (CVE-2021-27198). The vulnerability was an unauthenticated arbitrary file upload issue in version 11.0 to 11.0b of the Visualware MyConnection Server software. At the time I hadn’t actually proven remote code execution even though I rated it as critical. So when my colleague asked me how I exploited it, I felt like I had to show it was possible. This endeavor proved to be both challenging and enlightening so I thought I’d share the experience.
The MyConnection Server software is coded in Java and intended to work seamlessly across different platforms. In this case, the arbitrary file write was privileged, granting the file server elevated permissions, with SYSTEM on Windows and root on Linux. With a privileged file write, achieving code execution is often straightforward. This post by Doyensec outlines the most common approaches, which can be broadly categorized into two groups: web application-specific and operating system-specific. Web application-specific methods involve seeking ways to initiate execution within the web server process. Examples include uploading web framework configuration files or web application source code. On the other hand, operating system-specific techniques involve finding execution triggers controlled by the operating system itself, such as services, scheduled tasks, cron jobs, and so on.
Regrettably, in the case of this particular bug, I cannot directly target the web server itself since it’s a pure Java implementation, as opposed to using a web server framework like Apache or Nginx. As a result, our focus will primarily shift towards exploring operating system-specific possibilities or more innovative approaches.
Windows RCE
With this in mind, I began researching possible techniques for achieving code execution solely through a file write. In the Windows environment, my usual strategy would involve targeting vulnerable applications susceptible to traditional DLL hijacking or phantom DLL hijacking, leveraging the privilege granted by such writes.
I opened up Sysinternals Procmon tool and began looking for “NAME NOT FOUND” or “PATH NOT FOUND” results for CreateFile. I usually look for instances of executables or DLLs. While looking through the results, I noticed that every minute the MCS java process is attempting to open several “rtaplugin” JAR files, some of which do not exist.
Based on the output, it looks like the server is dynamically loading these JARs from disk into the java process. If this is the case, I may be able to get arbitrary code execution by simply placing a custom JAR file in a specific place on the file system. I decided to open up the MCS JAR in JD-GUI to investigate.
When I searched for “rtaplugin”, I found a class named RTAPlugin that has a function that appears to load a file from disk, create a custom ClassLoader, loads a class from the file, and then creates a new instance of the class. This is exactly what I need!
To confirm execution, I created a simple POC that executes calc.exe when an instance of the class is created.
I then just manually copied the JAR into the appropriate directory to see if it was loaded. I was thrilled to see that it worked! Now I just needed to test it using the file upload vulnerability.
I loaded up Burp and pasted in the JAR file as the body of the file upload request. Unfortunately when I sent it over, I didn’t see calc.exe in the process list.
When I opened up the log file for the MyConnection server, I found the following exception was thrown when attempting to unzip the JAR.
I took the original JAR payload and diff-ed it against the one that was uploaded into the rtaplugin directory. I found that certain bytes were getting corrupted. I opened up JD-GUI again to take a closer look at the code that performed the file write.
What wasn’t immediately obvious (at least to me) was that there was an implied encoding/decoding that was happening with the calls to String.valueOf and String.getBytes. As a result, certain ranges of bytes were getting corrupted. On Windows I found that bytes between 0x80 and 0x9f were being replaced with other values. This meant I had to do some bit fiddling to get the payload to work.
After doing a little bit of googling, I found a CTF writeup by Yudai Fujiwara that faced a similar encoding problem. The writeup provided code for generating a zip file that only contained bytes in the ASCII range, 0x0 – 0x7f. The script primarily focused on two structures in the zip protocol that needed to be free of non-ascii bytes, the CRC for zipped files, and any length fields.
The script brute-forces a valid ASCII value for the CRC field by iteratively modifying the inner file. In the CTF challenge, the zip contained an ASCII script, whereas a JAR contains a binary class file. I updated the algorithm to perform a similar modification to the Java source, and then recompile the Java class file on each iteration to make the CRC update.
Copy to Clipboard
After creating the ascii-zip payload, I uploaded it using the vulnerable endpoint. As hoped, it was loaded and executed as SYSTEM on the target server. To keep from having to recompile a JAR to execute different commands, I generated a JAR that would execute a script at a known location. I could then separately upload that script with a new command whenever I wanted command execute. I added the “setExecutable” directive to ensure it worked on Linux as well.
Linux RCE
When dealing with Linux-based operating systems, one of the primary challenges when exploiting arbitrary file write vulnerabilities is ensuring that file permissions are correctly configured. Even if a file is executed, it won’t function if it isn’t marked as executable. To overcome this obstacle, I am targeting files that already have the execute bit set.
As I had anticipated, demonstrating exploitation on Linux turned out to be quite straightforward by simply overwriting scripts within the /etc/cron.* directories. On Red Hat distributions, you can achieve relatively timely execution by overwriting the /etc/cron.hourly/0anacron script. The drawback to targeting cron jobs is there is no guarantee specific scripts exist, and overwriting them can cause system instability.
Assuming outbound network connectivity isn’t blocked, a simple reverse shell is likely the simplest payload to put in the cron job. If that doesn’t work, the cron job can be modified to contain a base64 encoded copy of the unmodified rtaplugin JAR that is then copied to the correct directory. The same technique as described above can then be used to get repeated code execution by uploading different versions of the script to/tmp/b.bat.
No License, No Wurk!
After all the effort put in to develop the rtaplugin exploit, I was disappointed to find out that rtaplugins (and the associated thread that loads them periodically) are only active when the web server has a valid license. I wasn’t aware of this because my test instance was still within its trial phase. I decided to take another look and see if our privileged file write vulnerability (CVE-2021-27198) can be used once again, but this time to trick the server into thinking it is licensed. The hope here is to find a file or database entry that can modified with our file upload exploit to bypass licensing. I want to be sure to clarify here, nothing I will describe here can be used to subvert or crack the license for this software on a fully patched system. The goal is to use our already privileged file system access to bypass any license checks.
When you navigate to the web application home page, you’ll see a menu on the left that contains a link to “Licensing”. It’s safe to assume this is the right place to look.
Sadly, when you click on it, you’re presented with the login page. If you’re fortunate and the password for the admin user hasn’t been changed, the next page you’ll encounter is shown below.
If you’ve made it this far, or happen to guess some credentials, you will have sufficient permissions to access the server licensing endpoint. There’s not much to go on in regards to the format of the expected key other than the hint that it starts with MCS.
I opened up the JAR again and tracked down the code responsible for handling the license activation. The handler performs a series of checks and transforms before making a web request to a visualware domain to verify the license.
If that fails (by exception), the server then attempts to validate the license by sending a specially crafted DNS request.
Leveraging the privileged file write capability of CVE-2021-27198, there are several methods at my disposal for circumventing the licensing without having to decipher the licensing key directly. As the software initiates a network request to validate the entered key, it can be rerouted to a server under my control to authenticate the supplied key.
The most straightforward method to achieve this is by modifying the hosts file, a file which contains mappings of IP addresses to hostnames, and is typically the first location an operating system checks when resolving a hostname’s IP address. On Windows systems, you can find this file at C:\Windows\System32\drivers\etc\hosts, and on *nix systems, it’s located at /etc/hosts. To manipulate the license verification process, I can simply insert a new entry into the hosts file, directing the license server domain to the IP address of a server under my control.
An alternative approach, specific to *nix systems, involves altering the /etc/resolv.conf file, which specifies the DNS server’s IP address used for domain resolution. By changing the DNS server address to a server that I manage, I can ensure that any DNS requests for the license server are resolved to my fake license server’s IP address.
WARNING: It should be noted that overwriting the /etc/hosts or /etc/resolv.conf file can cause system instability if certain configurations are expected to properly resolve custom DNS entries or resolvers.
My next step is to trace back the activate function to the web endpoint that can trigger it. Unfortunately, it appears the key entered into the web form is not the same format as what is sent to the license server. After performing a couple string-based checks, a validate function is called on the provided license key. If you look at the function closely, it may look familiar. It appears to be implementing a handcrafted version of RSA.
From a security standpoint, the rationale behind requiring an encrypted license key as the input into the web application doesn’t make sense. First and foremost, since the unencrypted license key is subsequently transmitted over the network, unencrypted, it can easily be captured with a tool like Wireshark. Secondly, the actual license key is verified on the vendor’s server so deducing how the license key is generated is impractical. Unfortunately for me however, this has introduced a minor obstacle in reaching the network activation function detailed in the previous section.
The astute reader may have already noticed an interesting detail about the RSA parameters. That public key looks awful small. Just barely over 256 bits. It appears our CTF challenge continues… (oh wait this is real software).
For those imposters in the infosec community that claim playing CTF doesn’t provide real world experience, I can confidently say you’ve never hunted real bugs. All too often I come across exploit chains that appear as if someone neatly setup each primitive for me to uncover. I personally don’t spend a ton of time attempting crypto challenges when playing CTFs, mostly because I’m not smart enough. Luckily for me, this example would fit nicely into the baby’s first category. With such a small public key, I should be able to find a CTF writeup that guides me through the process of breaking it down into its two prime numbers. A few searches later I found an article by Dennis Yurichev that demonstrates using a tool called CADO-NFS to do just that. After about 5 mins I was presented with the answer.
With the two prime numbers in hand, I attempted to reconstruct the private key using the script provided in Yurichev’s post. Unfortunately, the RSA crypto libraries in python didn’t play well with the provided primes. Namely because the block size rounded up to 257 bits and it complained about truncation. I also discovered that the server’s custom RSA implementation didn’t implement padding. To get around these issues I wrote code to perform the calculations manually rather than using a crypto library.
Copy to Clipboard
I used my script to generate an encrypted blob with the derived RSA private key and fired off a web request with a finished license key. Keep in mind, since I’ve already overwritten the hosts file, I only need to pass the decryption checks as the key will not be transmitted to the vendor’s server for verification. To my delight, it works!
Conclusion
My journey to developing a working exploit for CVE-2021-27198 finally comes to an end. What started as a simple exercise turned into quite an endeavor. For anyone interested, I’ve uploaded my exploit here.
Okta Verify for Windows Remote Code Execution – CVE-2024-0980
2 May 2024 at 17:41
This article is in no way affiliated, sponsored, or endorsed with/by Okta, Inc. All graphics are being displayed under fair use for the purposes of this article.
Poppin shells with Okta Verify on Windows
These days I rarely have an opportunity to do bug hunting. Fortunately, over the holiday break, I found some free time. This started as it usually does with me looking at what software was running on my computer.
A while back I had installed Okta Verify on my Windows box as it was required for some “enhanced” 2FA that I was required to have to access a thing. Months later it sat there doing whatever it does. I googled to see if Okta had a bug bounty program because even though I had some time, it’d be nice to get paid if I found a thing. I was thrilled to find that Okta had a bug bounty with Bugcrowd, Okta Verify is in it, and the payouts look good, almost too good.
I started with my usual bug hunting flow when approaching a random Windows service. This typically includes looking for the usual low hanging fruit. A good article for the types of things to look for can be found here.
Firing up Sysinternal’s Procmon, I saw there is a service called Okta.Coordinator.Service that is running as SYSTEM. Without going into the details (namely because Okta hasn’t fixed it or issued it a CVE), I found a thing. I submitted the report and was promptly paid.
Well that’s weird. The bug I submitted is an unequivocal 7.8 CVSS. Which without knowing the voodoo behind P ratings (P1-P4), seems like would be a P2 at least. Instead I get a P3 and paid out at the lower end.
Looking back on it, I’m betting this is probably an old bug bounty program trick to motivate researchers to dig deeper… because, it worked. I decided to take a closer look since I hadn’t even opened up the binary to see what it was doing – and I wanted to get that big payout.
Let’s Get Crackin’
I haven’t popped Okta.Coordinator.Service.exe into a disassembler yet, but I’m betting it’s a .NET application. My guess comes from its name and the fact that there’s an Okta.Coordinator.Service.exe.config file right there with it, which you usually see with .NET applications.
When I open up the executable in JetBrains dotPeek, I can confirm it is indeed a .NET application. The binary appears to be a service wrapper. It handles the service related functionality: install, uninstall, start, stop, etc. It references a Okta.AutoUpdate.Executor class that just so happens to have a matching DLL in the same directory.
Moving on to the DLL in dotPeek, I found the code used by the service. The first thing I noticed was it sets up a NamedPipe server, which listens for commands to update the Okta Verify software. This is a common design paradigm in Windows for enabling low-privileged applications to communicate with a high-privileged service to perform updates, as these often require elevated privileges. It’s a complex mechanism that’s tricky to do right, and often a good place for finding bugs. I was able to confirm the existence of the named-pipe server with a little Powershell.
Next, I investigated how to initiate an update and what aspects of this process could be manipulated by an attacker. The handler for the named pipe processes a straightforward JSON message that includes several fields, some checked against expected values. The primary field of interest is the update URL. If the input data passes validation, the software will attempt to fetch details from the specified URL about the most recent update package available. As shown below, the URL (sub)domain is verified against a whitelist before proceeding. For now, I’ll avoid trying to meet/bypass this requirement and simply add an entry in the hosts file on my test machine.
Typically at this stage, I’d code up a proof of concept (POC) to send a JSON message to the named pipe and check if the software connected to a web server I control. But since I haven’t spotted any potential vulnerabilities yet, I skipped that step and moved on.
From here I took a look at the code responsible for processing the JSON message retrieved from the attacker controlled update server. The application is expecting a message that contains metadata about an update package including versioning and an array of file metadata objects. These objects contain several pertinent fields such the download URL, size, hash, type, and command line arguments. The provided download URL is validated with the same domain checking algorithm as before. If the check passes, the software downloads the file and writes it to disk. This is where things get interesting. The code parses the download URL from the received metadata and constructs the file path by calling the Path.Combine function.
Several factors are converging here to create a serious vulnerability. The most obvious is the use of the Path.Combine function with user supplied data. I went into depth about this issue in a previous blog post here. The TLDR is if a full path is provided as the second argument to this function, the first argument that typically specifies the parent folder, is ignored. The next issue is how the filename is parsed. The code splits the file location URL by forward slash and takes the last element as the filename. The problem (solution) is a full Windows path can be inserted here using back slashes and it’s still a valid URL. Since the service is running as SYSTEM, we have permissions to right anywhere. If we put all this together our payload looks something like the script below.
Copy to Clipboard