feeds

by zer0x0ne — on

cover-image

some of my favourite websites: portswigger almost secure dark reading packet storm xkcd



xkcd

Retrieved title: xkcd.com, 3 item(s)
Planet Killer Comet Margarita

I'll take mine on the rocks, no ice.

Lane Change Highway

I just think lane markers should follow the local magnetic declination.

Runtime

At least there's a general understanding all around that Doctor Who is its own thing.

PortSwigger Research

Retrieved title: PortSwigger Research, 6 item(s)
Top 10 web hacking techniques of 2022 - nominations open

Update: Voting is now closed, and the panel vote is in progress.  Nominations are now open for the top 10 new web hacking techniques of 2022! Every year, security researchers share their latest f

Hijacking service workers via DOM Clobbering

In this post, we'll briefly review how service worker hijacking works, then introduce a variant that can be triggered via DOM clobbering thanks to a quirk in document.getElementById(). Understanding s

Stealing passwords from infosec Mastodon - without bypassing CSP

The story of how I could steal credentials on Infosec Mastodon with a HTML injection vulnerability, without needing to bypass CSP. Everybody on our Twitter feed seemed to be jumping ship to the infose

Detecting web message misconfigurations for cross-domain credential theft

We released a new version of Burp recently on the Early Adopter channel that updates DOM Invader to help find cross-domain secrets. In this post we are going to show you how to use DOM Invader to dete

Safari is hot-linking images to semi-random websites

Every image is potentially a URL on Safari, thanks to over-enthusiastic OCR (Optical Character Recognition). This means you can link any image to an external website - and Safari might already be send

HTTP/3 connection contamination: an upcoming threat?

I recently documented a dangerous reverse-proxy behaviour called first-request routing, which enables host-header attacks on back-end systems. In this post, I'll show how first-request routing also en

Dark Reading

Retrieved title: Dark Reading, 6 item(s)
Snyk Gets Nod of Approval With ServiceNow Strategic Investment

One of the most closely watched security startups continues to build bank because its platform appeals to both developers and security pros.

KORE Delivers IoT SAFE Solution for Massive IoT Use Cases with AWS

Delivering secure, global IoT device connectivity, deployment, and management at scale.

Data Privacy Day: Privado Flags Data Privacy Challenges In 2023 As It Hails Industry Stars

Microsoft Azure-Based Kerberos Attacks Crack Open Cloud Accounts

Two common attacks against on-premises Kerberos authentication servers — known as Pass the Ticket and Silver Ticket — can be used against Microsoft's Azure AD Kerberos, a security firms says.

Zacks Investment Research Hack Exposes Data for 820K Customers

Zacks Elite sign-ups for the period 1999–2005 were accessed, including name, address, email address, phone number, and the password associated with Zacks.com.

Google Pushes Privacy to the Limit in Updated Terms of Service

In the Play Store's ToS, a paragraph says Google may remove "harmful" applications from users' devices. Is that a step too far?

Almost Secure

Retrieved title: Almost Secure, 3 item(s)
IPinside: Korea’s mandatory spyware

Note: This article is also available in Korean.

On our tour of South Korea’s so-called security applications we’ve already took a look at TouchEn nxKey, an application meant to combat keyloggers by … checks notes … making keylogging easier. Today I want to shed some light on another application that many people in South Korea had to install on their computers: IPinside LWS Agent by Interezen.

The stated goal of the application is retrieving your “real” IP address to prevent online fraud. I found however that it collects way more data. And while it exposes this trove of data to any website asking politely, it doesn’t look like it is all too helpful for combating actual fraud.

How does it work?

Similarly to TouchEn nxKey, the IPinside LWS Agent application also communicates with websites via a local web server. When a banking website in South Korea wants to learn more about you, it will make a JSONP request to localhost:21300. If this request fails, the banking website will deny entry and ask that you install IPinside LWS Agent first. So in South Korea running this application isn’t optional.

On the other hand, if the application is present the website will receive various pieces of data in the wdata, ndata and udata fields. Quite a bit of data actually:

Screenshot of a browser window with the address 127.0.0.1:21300/?t=A&value= open. The response is a jQuery callback with some data including wdata, ndata and udata fields and base64-encoded values.

This data is supposed to contain your IP address. But even from the size of it, it’s obvious that it cannot be only that. In fact, there is a whole lot more data being transmitted.

What data is it?

wdata

Let’s start with wdata which is the most interesting data structure here. When decrypted, you get a considerable amount of binary data:

A hex dump with some binary data but also obvious strings like QEMU Harddisk or Gigabit Network Connection

As you can see from the output, I am running IPinside in a virtual machine. It even says VirtualBox at the end of the output, even though this particular machine is no longer running on VirtualBox.

Another obvious thing are the two hard drives of my virtual machine, one with the serial number QM00001 and another with the serial number abcdef. That F0129A45 is the serial number of the primary hard drive volume. You can also see my two network cards, both listed as Intel(R) 82574L Gigabit Network Connection. There is my keyboard model (Standard PS/2 Keyboard) and keyboard layout (de-de).

And if you look closely, you’ll even notice the byte sequences c0 a8 7a 01 (standing for my gateway’s IP address 192.168.122.1), c0 a8 7a 8c (192.168.122.140, the local IP address of the first network card) and c0 a8 7a 0a (192.168.122.10, the local IP address of the second network card).

But there is way more. For example, that 65 (letter e) right before the hard drive information is the result of calling GetProductInfo() function and indicates that I’m running Windows 10 Home. And 74 (letter t) before it encodes my exact Windows version.

Information about running processes

One piece of the data is particularly interesting. Don’t you wonder where the firefox.exe comes from here? It indicates that the Mozilla Firefox process is running in the background. This information is transmitted despite the active application being Google Chrome.

See, websites give IPinside agent a number of parameters that determine the output produced. One such parameter is called winRemote. It’s mildly obfuscated, but after removing the obfuscation you get:

TeamViewer_Desktop.exe|rcsemgru.exe|rcengmgru.exe|teamviewer_Desktop.exe

So banking websites are interested in whether you are running remote access tools. If a process is detected that matches one of these strings, the match is added to the wdata response.

And of course this functionality isn’t limited to searching for remote access tools. I replaced the winRemote parameter by AGULAAAAAAtmaXJlZm94LmV4ZQA= and got the information back whether Firefox is currently running. So this can be abused to look for any applications of interest.

And even that isn’t the end of it. IPinside agent will match substrings as well! So it can tell you whether a process with fire in its name is currently running.

That is enough for a website to start searching your process list without knowing what these processes could be. I created a page that would start with the .exe suffix and do a depth-first search. The issue here was mostly IPinside response being so slow, each request taking half a second. I slightly optimized the performance by testing multiple guesses with one request and got a proof of concept page that would turn up a process name every 40-50 seconds:

Screenshot of a page saying: “Please wait, fetching your process list… Testing suffix oerver-svg.exe cortana.exe.” It also lists already found processes: i3gproc.exe asdsvc.exe wpmsvc.exe i3gmainsvc.exe

With sufficient time, this page could potentially enumerate every process running on the system.

ndata

The ndata part of the response is much simpler. It looks like this:

��HDATAIP=▚▚▚.▚▚▚.▚▚▚.▚▚▚��VD1NATIP=▚▚▚.▚▚▚.▚▚▚.▚▚▚��VD1CLTIP=192.168.122.140��VD2NATIP=��VD2CLTIP=192.168.122.10��VPN=2��ETHTYPE=ETH1

No, I didn’t mess up decoding the data. Yes, is really in the response. The idea here was actually to use (reverse tilde symbol) as a separator. But since my operating system isn’t Korean, the character encoding for non-Unicode applications (like IPinside LWS Agent) isn’t set to EUC-KR. The application doesn’t expect this and botches the conversion to UTF-8.

▚▚▚.▚▚▚.▚▚▚.▚▚▚ on the other hand was me censoring my public IP address. The application gets it by two different means. VD1NATIP appears to come from my home router.

HDATAIP on the other hand comes from a web server. Which web server? That’s determined by the host_info parameter that the website provides to the application. It is also obfuscated, the actual value is:

www.securetrueip.co.kr:80:/vbank_01.jsc:_INSIDE_AX_H=

Only the first two parts appear to be used, the application makes a request to http://www.securetrueip.co.kr:80/androidagent.jsc. One of the response headers is RESPONSE_IP. You guessed it: that’s your IP address as this web server sees it.

The application uses low-level WS2_32.DLL APIs here, probably as an attempt to prevent this traffic from being routed through some proxy server or VPN. After all, the goal is deanonymizing you.

udata

Finally, there is udata where “u” stands for “unique.” There are several different output types here, this is type 13:

[52-54-00-A7-44-B5:1:0:Intel(R) 82574L Gigabit Network Connection];[52-54-00-4A-FD-6E:0:0:Intel(R) 82574L Gigabit Network Connection #2];$[QM00001:QEMU HARDDISK:];[abcdef:QEMU HARDDISK:];[::];[::];[::];

Once again a list of network cards and hard drives, but this time MAC addresses of the network cards are listed as well. Other output types are mostly the same data in different formats, except for type 30. This one contains a hexadecimal CPU identifier, representing 16 bytes generated by mashing together the results of 15 different CPUID calls.

How is this data protected?

So there is a whole lot of data which allows deanonymizing users, learning about the hardware and software they use, potentially facilitating further attacks by exposing which vulnerabilities are present on their systems. Surely this kind of data is well-protected, right? I mean: sure, every Korean online banking website has access to it. And Korean government websites. And probably more Interezen customers. But nobody else, right?

Well, the server under localhost:21300 doesn’t care who it responds to. Any website can request the data. But it still needs to know how to decode it.

When talking about wdata, there are three layers of protection being applied: obfuscation, compression and encryption. Yes, obfuscating data by XOR’ing it with a single random byte probably isn’t adding much protection. And compression doesn’t really count as protection either if people can easily find the well-known GPL-licensed source code that Interezen used without complying with the license terms. But there is encryption, and it is even using public-key cryptography!

So the application only contains the public RSA key, that’s not sufficient to decrypt the data. The private key is only known to Interezen. And any of their numerous customers. Let’s hope that all these customers sufficiently protect this private key and don’t leak it to some hackers.

Otherwise RSA encryption can be considered secure even with moderately sized keys. Except… we aren’t talking about a moderately sized key here. We aren’t even talking about a weak key. We are talking about a 320 bits key. That’s shorter than the very first key factored in the RSA Factoring Challenge. And that was in April 1991, more than three decades ago. Sane RSA libraries don’t even work with keys this short.

I downloaded msieve and let it run on my laptop CPU, occupying a single core of it:

$ ./msieve 108709796755756429540066787499269637…

sieving in progress (press Ctrl-C to pause)
86308 relations (21012 full + 65296 combined from 1300817 partial), need 85977
sieving complete, commencing postprocessing
linear algebra completed 80307 of 82231 dimensions (97.7%, ETA 0h 0m)
elapsed time 02:36:55

Yes, it took me 2 hours and 36 minutes to calculate the private key on very basic hardware. That’s how much protection this RSA encryption provides.

When talking about ndata and udata, things look even more dire. The only protection layer here is encryption. No, not public-key cryptography but symmetric encryption via AES-256. And of course the encryption key is hardcoded in the application, there is no other way.

To add insult to injury, the application produces identical ciphertext on each run. At first I thought this to be the result of the deprecated ECB block chaining mode being used. But: no, the application uses CBC block chaining mode. But it fails to pass in an initialization vector, so the cryptography library in question always fills the initialization vector with zeroes.

Which is a long and winded way of saying: the encryption would be broken regardless of whether one can retrieve the encryption key from the application.

To sum up: no, this data isn’t really protected. If the user has the IPinside LWS Agent installed, any website can access the data it collects. The encryption applied is worthless.

And the overall security of the application?

That web server the application runs on port 21300, what is it? Turns out, it’s their own custom code doing it, built on low-level network sockets functionality. That’s perfectly fine of course, who hasn’t built their own rudimentary web server using substring matches to parse requests and deployed it to millions of users?

Their web server still needs SSL support, so it relies on the OpenSSL library for that. Which library version? Why, OpenSSL 1.0.1j of course. Yes, it was released more than eight years ago. Yes, end of support for OpenSSL 1.0.1 was six years ago. Yes, there were 11 more releases on the 1.0.1 branch after 1.0.1j, with numerous vulnerabilities fixed, and not even these fixes made it into IPinside LWS Agent.

Sure, that web server is also single-threaded, why wouldn’t it be? It’s not like people will open two banking websites in parallel. Yes, this makes it trivial for a malicious website to lock up that server with long-running requests (denial-of-service attack). But that merely prevents people from logging into online banking and government websites, not a big deal.

Looking at how this server is implemented, there is code that essentially looks like this:

BYTE inputBuffer[8192];
char request[8192];
char debugString[8192];

memset(inputBuffer, 0, sizeof(inputBuffer));
memset(request, 0, sizeof(request));

int count = ssl_read(ssl, inputBuffer, sizeof(inputBuffer));
if (count <= 0)
{
  
}

memcpy(request, inputBuffer, count);

memset(debugString, 0, sizeof(debugString));
sprintf(debugString, "Received data from SSL socket: %s", request);
log(debugString);

handle_request(request);

Can you spot the issues with this code?

Come on, I’m waiting.

Yes, I’m cheating. Unlike you I actually debugged that code and saw live just how badly things went here.

First of all, it can happen that ssl_read will produce exactly 8192 bytes and fill the entire buffer. In that case, inputBuffer won’t be null-terminated. And its copy in request won’t be null-terminated either. So attempting to use request as a null-terminated string in sprintf() or handle_request() will read beyond the end of the buffer. In fact, with the memory layout here it will continue into the identical inputBuffer memory area and then into whatever comes after it.

So the sprintf() call actually receives more than 16384 bytes of data, and its target buffer won’t be nearly large enough for that. But even if this data weren’t missing the terminating zero: taking a 8192 byte string, adding a bunch more text to it and trying to squeeze the result into a 8192 byte buffer isn’t going to work.

This isn’t an isolated piece of bad code. While researching the functionality of this application, I couldn’t fail noticing several more stack buffer overflows and another buffer over-read. To my (very limited) knowledge of binary exploitation, these vulnerabilities cannot be turned into Remote Code Execution thanks to StackGuard and SafeSEH protection mechanisms being active and effective. If somebody more experienced finds a way around that however, things will get very ugly. The application has neither ASLR nor DEP protection enabled.

Some of these vulnerabilities can definitely crash the application however. I created two proof of concept pages which did so repeatedly. And that’s another denial-of-service attack, also effectively preventing people from using online banking in South Korea.

When will it be fixed?

I submitted three vulnerability reports to KrCERT on October 21st, 2022. By November 14th KrCERT confirmed forwarding all these reports to Interezen. I did not receive any communication after that.

Prior to this disclosure, a Korean reporter asked Interezen to comment. They confirmed receiving my reports but claimed that they only received one of them on January 6th, 2023. Supposedly because of that they plan to release their fix in February, at which point it would be up to their customers (meaning: banks and such) to distribute the new version to the users.

Like other similar applications, this software won’t autoupdate. So users will need to either download and install an update manually or perform an update via a management application like Wizvera Veraport. Neither is particularly likely unless banks start rejecting old IPinside versions and requiring users to update.

Does IPinside actually make banking safer?

Interezen isn’t merely providing the IPinside agent application. According to their self-description, they are a company who specializes in BigData. They provide the service of collecting and analyzing data to numerous banks, insurances and government agencies.

Screenshot of a website section titled: “Client Companies. With the number one products in this industry, INTEREZEN is providing the best services for more than 200 client companies.” Below it the logos of Woori Bank, Industrial Bank of Korea, KEB Hana Card, National Tax Service, MG Non-Life Insurance, Hyundai Card as well as a “View more” button.

Online I could find a manual from 2009 showing screenshots from Interezen’s backend solution. One can see all website visitors being tracked along with their data. Back in 2009 the application collected barely more than the IP addresses, but it can be assumed that the current version of this backend makes all the data provided by the agent application accessible.

Screenshot of a web interface listing requests for a specific date range. Some of the table columns are: date, webip, proxyip, natip, attackip
Screenshot from IPinside 3.0 product manual

In addition to showing detailed information on each user, in 2009 this application was already capable of producing statistical overviews based e.g. on IP address, location, browser or operating system.

Screenshot of a web interface displaying user shares for Windows 98, Windows 2000, Windows 2003 and Windows XP
Screenshot from IPinside 3.0 product manual

The goal here isn’t protecting users, it’s protecting banks and other Interezen customers. The idea is that a bank will have it easier to detect and block fraud or attacks if it has more information available to it. Fraudsters won’t simply be able to obfuscate their identities by using proxies or VPNs, banks will be able to block them regardless.

In fact, Interezen filed several patents in Korea for their ideas. The first one, patent 10-1005093 is called “Method and Device for Client Identification.” In the patent filing, the reason for the “invention” is the following (automatic translation):

The importance and value of a method for identifying a client in an Internet environment targeting an unspecified majority is increasing. However, due to the development of various camouflage and concealment methods and the limitations of existing identification technologies, proper identification and analysis are very difficult in reality.

It goes on to explain how cookies are insufficient and the user’s real IP address needs to be retrieved.

The patent 10-1088084 titled “Method and system for monitoring and cutting off illegal electronic-commerce transaction” expands further on the reasoning (automatic translation):

The present invention is a technology that enables real-time processing, which was impossible with existing security systems, in the detection/blocking of illegal transactions related to all e-commerce services through the Internet, and e-commerce illegal transactions that cannot but be judged as normal transactions with existing security technologies.

This patent also introduces the idea of forcing the users to install the agent in order to use the website.

But does the approach even work? Is there anything to stop fraudsters from setting up their own web server on localhost:21300 and feeding banking websites bogus data?

Ok, someone would have to reverse engineer the functionality of the IPinside LWS Agent application and reproduce it. I mean, it’s not that simple. It took me … checks notes … one work week, proof of concept creation included. Fraudsters certainly don’t have that kind of time to invest into deciphering all the various obfuscation levels here.

But wait, why even go there? A replay attack is far simpler, giving websites pre-recorded legitimate responses will just do. There is no challenge-handshake scheme here, no timestamp, nothing to prevent this attack. If anything, websites could recognize responses they’ve previously seen. But even that doesn’t really work: ndata and udata obfuscation has no randomness in it, the data is expected to be always identical. And wdata has only one random byte in its obfuscation scheme, that’s not sufficient to reliably distinguish legitimately identical responses from replayed ones.

So it would appear that IPinside is massively invading people’s privacy, exposing way too much of their data to anybody asking, yet falling short of really stopping illegal transactions as they claim. Prove me wrong.

Bitwarden design flaw: Server side iterations

In the aftermath of the LastPass breach it became increasingly clear that LastPass didn’t protect their users as well as they should have. When people started looking for alternatives, two favorites emerged: 1Password and Bitwarden. But do these do a better job at protecting sensitive data?

For 1Password, this question could be answered fairly easily. The secret key functionality decreases usability, requiring the secret key to be moved to each new device used with the account. But the fact that this random value is required to decrypt the data means that the encrypted data on 1Password servers is almost useless to potential attackers. It cannot be decrypted even for weak master passwords.

As to Bitwarden, the media mostly repeated their claim that the data is protected with 200,001 PBKDF2 iterations: 100,001 iterations on the client side and another 100,000 on the server. This being twice the default protection offered by LastPass, it doesn’t sound too bad. Except: as it turns out, the server-side iterations are designed in such a way that they don’t offer any security benefit. What remains are 100,000 iterations performed on the client side, essentially the same protection level as for LastPass.

Mind you, LastPass isn’t only being criticized for using a default iterations count that is three time lower than the current OWASP recommendation. LastPass also failed to encrypt all data, a flaw that Bitwarden doesn’t seem to share. LastPass also kept the iterations count for older accounts dangerously low, something that Bitwarden hopefully didn’t do either (Edit: yes, they did this, some accounts have considerably lower iteration count). LastPass also chose to downplay the breach instead of suggesting meaningful mitigation steps, something that Bitwarden hopefully wouldn’t do in this situation. Still, the protection offered by Bitwarden isn’t exactly optimal either.

Edit (2023-01-23): Bitwarden increased the default client-side iterations to 350,000 a few days ago. So far this change only applies to new accounts, and it is unclear whether they plan to upgrade existing accounts automatically. And today OWASP changed their recommendation to 600,000 iterations, it has been adjusted to current hardware.

Edit (2023-01-24): I realized that some of my concerns were already voiced in Bitwarden’s 2018 Security Assessment. Linked to it in the respective sections.

How Bitwarden protects users’ data

Like most password managers, Bitwarden uses a single master password to protect users’ data. The Bitwarden server isn’t supposed to know this password. So two different values are being derived from it: a master password hash, used to verify that the user is allowed to log in, and a key used to encrypt/decrypt the data.

A schema showing the master password being hashed with PBKDF2-SHA256 and 100,000 iterations into a master key. The master key is further hashed on the server side before being stored in the database. The same master key is turned into a stretched master key used to encrypt the encryption key, here no additional PBKDF2 is applied on the server side.
Bitwarden password hashing, key derivation, and encryption. Source: Bitwarden security whitepaper

If we look at how Bitwarden describes the process in their security whitepaper, there is an obvious flaw: the 100,000 PBKDF2 iterations on the server side are only applied to the master password hash, not to the encryption key. This is pretty much the same flaw that I discovered in LastPass in 2018.

What this means for decrypting the data

So what happens if some malicious actor happens to get a copy of the data, like it happened with LastPass? They will need to decrypt it. And for that, they will have to guess the master password. PBKDF2 is meant to slow down verifying whether a guess is correct.

Testing the guesses against the master password hash would be fairly slow: 200,001 PBKDF2 iterations here. But the attackers wouldn’t waste time doing that of course. Instead, for each guess they would derive an encryption key (100,000 PBKDF2 iterations) and check whether this one can decrypt the data.

This simple tweak removes all the protection granted by the server-side iterations and speeds up master password guessing considerably. Only the client-side iterations really matter as protection.

What this means for you

The default protection level of LastPass and Bitwarden is identical. This means that you need a strong master password. And the only real way to get there is generating your password randomly. For example, you could generate a random passphrase using the diceware approach.

Using a dictionary for 5 dice (7776 dictionary words) and picking out four random words, you get a password with slightly over 50 bits of entropy. I’ve done the calculations for guessing such passwords: approximately 200 years on a single graphics card or $1,500,000.

This should be a security level sufficient for most regular users. If you are guarding valuable secrets or are someone of interest for state-level actors, you might want to consider a stronger password. Adding one more word to your passphrase increases the cost of guessing your password by factor 7776. So a passphrase with five words is already almost unrealistic to guess even for state-level actors.

All of this assumes that your KDF iterations setting is set to the default 100,000. Bitwarden will allow you to set this value as low as 5,000 without even warning you. This was mentioned as BWN-01-009 in Bitwarden’s 2018 Security Assessment, yet there we are five years later. Should your setting be too low, I recommend fixing it immediately. Reminder: current OWASP recommendation is 310,000.

Is Bitwarden as bad as LastPass?

So as it turns out, with the default settings Bitwarden provides exactly the same protection level as LastPass. This is only part of the story however.

One question is how many accounts have a protection level below the default configured. It seems that before 2018 Bitwarden’s default used to be 5,000 iterations. Then the developers increased it to 100,000 in multiple successive steps. When LastPass did that, they failed upgrading existing accounts. I wonder whether Bitwarden also has older accounts stuck on suboptimal security settings.

The other aspect here is that Dmitry Chestnykh wrote about Bitwarden’s server-side iterations being useless in 2020 already, and Bitwarden should have been aware of it even if they didn’t realize how my research applies to them as well. On the other hand, using PBKDF2 with only 100,000 iterations isn’t a great default today. Still, Bitwarden failed to increase it in the past years, apparently copying LastPass as “gold standard” – and they didn’t adjust their PR claims either:

Screenshot of text from the Bitwarden website: The default iteration count used with PBKDF2 is 100,001 iterations on the client (client-side iteration count is configurable from your account settings), and then an additional 100,000 iterations when stored on our servers (for a total of 200,001 iterations by default). The organization key is shared via RSA-2048. The utilized hash functions are one-way hashes, meaning they cannot be reverse engineered by anyone at Bitwarden to reveal your master password. Even if Bitwarden were to be hacked, there would be no method by which your master password could be obtained.

Users have been complaining and asking for better key derivation functions since at least 2018. It was even mentioned as BWN-01-007 in Bitwarden’s 2018 Security Assessment. This change wasn’t considered a priority however. Only after the LastPass breach things started moving, and it wasn’t Bitwarden’s core developers driving the change. Someone contributed the changes required for scrypt support and Argon2 support. The former was rejected in favor of the latter, and Argon2 will hopefully become the default (only?) choice at some point in future.

Adding a secret key like 1Password would have been another option to address this issue. This suggestion has also been around since at least 2018 and accumulated a considerable amount of votes, but so far it hasn’t been implemented either.

On the bright side, Bitwarden clearly states that they encrypt all your vault data, including website addresses. So unlike with LastPass, any data lifted from Bitwarden servers will in fact be useless until the attackers manage to decrypt it.

How server-side iterations could have been designed

In case you are wondering whether it is even possible to implement server-side iterations mechanism correctly: yes, it is. One example is the onepw protocol Mozilla introduced for Firefox Sync in 2014. While the description is fairly complicated, the important part is: the password hash received by the server is not used for anything before it passes through additional scrypt hashing.

Firefox Sync has a different flaw: its client-side password hashing uses merely 1,000 PBKDF2 iterations, a ridiculously low setting. So if someone compromises the production servers rather than merely the stored data, they will be able to intercept password hashes that are barely protected. The corresponding bug report has been open for the past six years and is still unresolved.

The same attack scenario is an issue for Bitwarden as well. Even if you configure your account with 1,000,000 iterations, a compromised Bitwarden server can always tell the client to apply merely 5,000 PBKDF2 iterations to the master password before sending it to the server. The client has to rely on the server to tell it the correct value, and as long as low settings like 5,000 iterations are supported this issue will remain.

TouchEn nxKey: The keylogging anti-keylogger solution

Update (2023-01-16): This article is now available in Korean.

I wrote about South Korea’s mandatory so-called security applications a week ago. My journey here started with TouchEn nxKey by RaonSecure which got my attention because the corresponding browser extension has more than 10 million users – the highest number Chrome Web Store will display. The real number of users is likely considerably higher, the software being installed on pretty much any computer in South Korea.

That’s not because people like it so much: they outright hate it, resulting in an average rating of 1,3 out of 5 stars and lots of calls to abolish it. Yet using it is required if you want to do things like online banking in South Korea.

The banks pushing for the software to be installed claim that it improves security. People call it “malware” and a “keylogger.” I spent some time analyzing the inner workings of the product and determined the latter to be far closer to the truth. The application indeed contains key logging functionality by design, and it fails to sufficiently restrict access to it. In addition, various bugs range from simple denial of service to facilitating remote code execution. Altogether I reported seven security vulnerabilities in the product.

The backdrop

After I gave an overview of South Korea’s situation, people started discussing my article on various Korean websites. One comment in particular provided crucial information that I was missing: two news stories from 2005 on the Korea Exchange Bank hacking incident [1] [2]. These are light on technical details but let me try to explain how I understand this.

This was apparently a big deal in Korea in 2005. A cybercrime gang managed to steal 50 million Won (around $50,000 at the time) from people’s banking accounts by means of a Remote Access Trojan. This way they not only got the user’s login credentials but also information from their security card. From what I can tell, this security card was similar to indexed TANs, a second factor authentication method banished in the European Union in 2012 for the exact reason of being easily compromised by banking trojans.

How did the users’ computers get infected with this malicious application? From the description this sounds like a drive-by download when visiting a malicious website with the browser, a browser vulnerability was likely exploited. It’s also possible however that the user was tricked into installing the application. The browser in question isn’t named, but it is certain to be Internet Explorer as South Korea didn’t use anything else at this point.

Now the news stress the point that the user didn’t lose or give away their online banking credentials, they’ve done nothing wrong. The integrity of online banking in general is being questioned, and the bank is criticized for not implementing sufficient security precautions.

In 2005 there have been plenty of stories like this one in other countries as well. While I cannot claim that the issue has been completely eliminated, today it is far less common. On the one hand, web browsers got way more secure. On the other hand, banks have improved their second factor. At least in Europe you usually need a second device to confirm a transaction. And you see the transaction details when confirming, so you won’t accidentally confirm a transfer to a malicious actor.

South Korea chose a different route, the public outrage demanded quick results. The second news story identifies the culprit: a security application could have stopped the attack, but its use wasn’t mandatory. And the bank complies. It promises to deliver an “anti-hacking” application and to make its use mandatory for all users.

So it’s likely not a coincidence that I can find the first mentions of TouchEn Key around 2006/2007. The application claims to protect your sensitive data when you enter data into a web page. Eventually, TouchEn nxKey was developed to support non-Microsoft browsers, and that’s the one I looked into.

What does TouchEn nxKey actually do?

All the public sources on TouchEn nxKey tell that it is somehow meant to combat keyloggers by encrypting keyboard input. That’s all the technical information I could find. So I had to figure it out on my own.

Websites relying TouchEn nxKey run the nxKey SDK which consists of two parts: a bunch of JavaScript code running on the website and some server-side code. Here is how it works:

  1. You enter a password field on a website that uses the nxKey SDK.
  2. JavaScript code of the nxKey SDK detects it and notifies your local nxKey application.
  3. nxKey application activates its device driver in the Windows kernel.
  4. Device driver now intercepts all keyboard input. Instead of having it processed by the system, keyboard input is sent to the nxKey application.
  5. The nxKey application encrypts the keyboard input and sends it to the JavaScript code of the nxKey SDK.
  6. The JavaScript code puts the encrypted data into a hidden form field. The actual password field receives only dummy text.
  7. You finish entering your login credentials and click “Login.”
  8. The encrypted keyboard input is sent to the server along with other data.
  9. The server-side part of the nxKey SDK decrypts it and retrieves the plain text password from it. Regular login procedure takes over.

So the theory is: a keylogger attempting to record data entered into this website will only see encrypted data. It can see the public key used by the website, but it won’t have the corresponding private key. So no way to decrypt, the password is safe.

Yes, it’s a really nice theory.

How do websites communicate with TouchEn nxKey?

How does a website even know that a particular application is installed on the computer? And how does it communicate with it?

It appears that there is an ongoing paradigm shift here. Originally, TouchEn nxKey required its browser extension to be installed. That browser extension forwarded requests from the website to the application using native messaging. And it delivered responses back to the webpage.

Yet using browser extensions as intermediate is no longer state of the art. The current approach is for the websites to use WebSockets API to communicate with the application directly. Browser extensions are no longer required.

Website busanbank.co.kr is shown communicating with TouchEn browser extension via touchenex_nativecall(). The extension in turn communicates with application CrossEXChrome via Native Messaging. Website citibank.co.kr on the other hand communicates directly with application CrossEXService via WebSocket on 127.0.0.1:34581.

I’m not sure when exactly this paradigm shift started, but it is far from complete yet. While some websites like Citibank Korea use the new WebSocket approach exclusively, other websites like that of the Busan Bank still run older code which relies exclusively on the browser extensions.

This does not merely mean that users still need to have the browser extension installed. It also explains the frequent complains about the software not being recognized despite being installed. These users got the older version of the software installed, one that does not support WebSocket communication. There is no autoupdate. With some banks still offering these older versions for download, it’s a mistake I made myself originally.

Abusing TouchEn extension to attack banking websites

The TouchEn browser extension is really tiny, its functionality being minimal. It should be hard to do much wrong here. Yet looking through its code, we see comments like this one:

result = JSON.parse(result);
var cbfunction = result.callback;

var reply = JSON.stringify(result.reply);
var script_str = cbfunction + "(" + reply + ");";
//eval(script_str);
if(typeof window[cbfunction] == 'function')
{
  window[cbfunction](reply);
}

So somebody designed a horribly bad (meaning: actually dangerous) way of doing something. Then they either realized that it could be done without eval(), or somebody pointed it out to them. Yet rather than removing the bad code, they kept it around just in case. Quite frankly, to me this demonstrates a very bad grasp of JavaScript, security and version control. And maybe it’s just me, but I wouldn’t let this person write code for a security product unsupervised.

Either way, the dangerous eval() calls have already been purged from the browser extension. Not so much in the JavaScript part of the nxKey SDK used by banking websites, but these are no concern so far. Still, with the code quality so bad, there are bound to be more issues.

And I found such an issue in the callback mechanism. A website can send a setcallback request to the application in order to register for some events. When such events occurs, the application will instruct the extension to call the registered callback function on the page. Essentially, any global function on the page can be called, by name.

Could a malicious webpage register a callback for some other web page then? There are two hurdles:

  1. The target webpage needs to have an element with id="setcallback".
  2. Callbacks are delivered to a specific tab.

The first hurdle means that primarily only websites using nxKey SDK can be attacked. When communicating via the browser extensions these will create the necessary element. Communication via WebSockets doesn’t create this element, meaning that websites using newer nxKey SDK aren’t affected.

The second hurdle seems to mean that only pages loaded in the current tab can be attacked, e.g. those loaded in a frame. Unless the nxKey application can be tricked into setting a wrong tabid value in its response.

And this turned out surprisingly easy. While the application uses a proper JSON parser to process incoming data, the responses are generated by means of calling sprintf_s(). No escaping is performed. So manipulating some response properties and adding quotation marks to it allows injecting arbitrary JSON properties.

touchenex_nativecall({
  
  id: 'something","x":"y'
  
});

The id property will be copied into the application’s response, meaning that the response suddenly gets a new JSON property called x. This vulnerability allows injecting any value for tabid into the response.

How does a malicious page know the ID of a banking tab? It could use its own tab ID (which TouchEn extension helpfully exposes) and try guessing other tab IDs. Or it could simply leave this value empty. The extension is being helpful in this case:

tabid = response.response.tabid;
if (tabid == "")
{
  chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
    chrome.tabs.sendMessage(tabs[0].id, response, function(res) {});
  });
}

So if the tabid value is empty it will deliver the message to the currently active tab.

Meaning that one possible attack looks like this:

  1. Open a banking website in a new tab, it becoming the active tab.
  2. Wait for the page to load, so that the element with id="setcallback" is present.
  3. Send a setcallback message via the TouchEn extension to set a callback to some function, while also overwriting JSON response properties with "tabid":"" and "reply":"malicious payload".

The first call to the callback occurs immediately. So the callback function will be called in the banking website, with the malicious payload from the reply property as parameter.

We are almost there. A possible callback function could be eval but there is a final hurdle: TouchEn passes the reply property through JSON.stringify() before giving it to the callback. So we actually get eval("\"malicious payload\"") and this doesn’t do anything.

On the other hand, maybe the target page has jQuery? And calling $('"<img src=x onerror=alert(\'Hi,_this_is_JavaScript_code_running_on_\'+document.domain)>"') will produce the expected result:

gbank.busanbank.co.kr says: Hi,_this_is_JavaScript_code_running_on_busanbank.co.kr

Is expecting jQuery for an attack to succeed cheating? Not quite, the websites using TouchEn nxKey will most likely also use TouchEn Transkey (an on-screen keyboard) as well, and this one relies on jQuery. Altogether, all South Korean banking sites seem heavily dependent on jQuery which is a bad idea.

But update_callback, the designated callback of the nxKey SDK, can also be abused to run arbitrary JavaScript code when passed JSON-stringified data. Calling update_callback('{"FaqMove":"javascript:alert(\'Hi, this is JavaScript code running on \'+document.domain)"}') will attempt to redirect to a javascript: link and run arbitrary code as a side-effect:

gbank.busanbank.co.kr says: Hi, this is JavaScript code running on busanbank.co.kr

So this attack allows a malicious website to compromise any website relying on the TouchEn extension. And none of the “security” applications South Korean banks force users to install detect or prevent this attack.

Side-note: Browser extensions similar to TouchEn

Back when I started my testing there were two TouchEn extensions in the Chrome Web Store. The less popular but largely identical extension has since been removed.

This isn’t the end of the story however. I found three more almost identical extensions: CrossWeb EX and Smart Manager EX by INISAFE as well as CrossWarpEX by iniLINE. CrossWeb EX is the most popular of those and currently listed with more than 4 million users. These extensions similarly expose websites to attacks.

My first thought was that RaonSecure and INISAFE belong to the same company group. That doesn’t appear to be the case.

But then I saw this page by the iniLINE software development company:

A web page featuring Initech and RaonSecure logos among others.

This lists Initech and RaonSecure as partners, so it would appear that iniLINE are the developers of these problematic browser extensions. Another interesting detail: the first entry in the “Major customers” line at the top is the Ministry of National Defense. I just hope that their defense work results in better code than what their other partners get…

Using keylogging functionality from a website

Now let’s say that there is a malicious website. And let’s say that this website tells TouchEn nxKey: “Hi there, the user is on a password field right now, and I want the data they enter.” Will that website get all the keyboard input then?

Yes, it will! It will get whatever the user types, regardless of which browser tab is active right now or whether the browser itself is active at all. The nxKey application simply complies with the request, it won’t check whether it makes any sense at this point. In fact, it will even give websites the administrator password entered into a User Access Control prompt.

But there certainly are hurdles? Yes, there are. First of all, such a website needs a valid license. It needs to communicate that license in the get_versions call prior to using any application functionality:

socket.send(JSON.stringify({
  "tabid": "whatever",
  "init": "get_versions",
  "m": "nxkey",
  "origin": "https://www.example.com",
  "lic": "eyJ2ZXJzaW9uIjoiMS4wIiwiaXNzdWVfZGF0ZSI6IjIwMzAwMTAxMTIwMDAwIiwicHJvdG9jb2xfbmFtZSI6InRvdWNoZW5leCIsInV1aWQiOiIwMTIzNDU2Nzg5YWJjZGVmIiwibGljZW5zZSI6IldlMkVtUDZjajhOUVIvTk81L3VNQXRVd0EwQzB1RXFzRnRsTVQ1Y29FVkJpSTlYdXZCL1VCVVlHWlY2MVBGdnYvVUJlb1N6ZitSY285Q1d6UUZWSFlCcXhOcGxiZDI3Z2d0bFJNOUhETzdzPSJ9"
}));

This particular license is only valid for www.example.com. So it can only be used by the www.example.com website. Or by any other website claiming to be www.example.com.

See that origin property in the code above? Yes, TouchEn nxKey actually believes that rather than looking at the Origin HTTP header. So it is trivial to lift a license from some website using nxKey legitimately and claim to be that website. It’s not even necessary to create a fake license.

Another hurdle: won’t the data received by the malicious website be encrypted? How does one decrypt it? It should be possible to use a different public key, one where the private key is known. Then one would only need to know the algorithm, and then decrypting the data would work.

Except: none of that is necessary. If TouchEn nxKey doesn’t receive any public key at all, it will simply drop the encryption! The website will receive the keyboard input in clear text then.

Behold, my proof of concept page (less than 3 kB with all the HTML boilerplate):

Webpage screenshot: Hey, this page knows what you type into other applications! Type in any application and watch the text appear here: I AM TYPING THIS INTO A UAC PROMPT

There is still a third hurdle, one that considerably reduces the severity of this vulnerability: keyboard input intercepted by a malicious web page no longer reaches its destination. A user is bound to get suspicious when they start typing in a password, yet nothing appears in the text field. My analysis of the nxKey application suggests that it only works this way: the keyboard input reaches either the web page or its actual target, but never both.

Attacking the application itself

We’ve already established that whoever wrote the JavaScript code of this product wasn’t very proficient at it. But maybe that’s because all their experts have a C++ background? We’ve already seen this before, developers trying to leave JavaScript and delegate all tasks to C++ code as soon as possible.

Sadly, this isn’t a suspicion I can confirm. I’m way more used to analyzing JavaScript than binary code, but it seems that the application itself is similarly riddled with issues. In fact, it mostly uses approaches typical to C rather than C++. There is lots of manual memory management here.

I already mentioned their use of sprintf_s(). An interesting fact about functions like sprintf_s() or strcpy_s(): while these are the “memory safe” versions of sprintf() or strcpy() functions which won’t overflow the buffer, these are still tricky to use. If you fail giving them a sufficiently large buffer, these will invoke the invalid parameter handler. And by default this makes the application crash.

Guess what: nxKey application almost never makes sure the buffer is sufficiently large. And it doesn’t change the default behavior either. So sending it an overly large value will in many cases crash the application. A crash is better than a buffer overflow, but a crashed application can no longer do its job. Typical result: your online banking login form appears to work correctly, but it receives your password as clear text now. You only notice something being wrong when submitting the form results in an error message. This vulnerability allows Denial-of-Service attacks.

Another example: out of all JSON parsers, the developers of the nxKey application picked out the one written in C. Not only that, they also took a random repository state from January 2014 and never bothered updating it. That null pointer dereference fixed in June 2014? Yeah, still present. So sending ] (a single closing square bracket) to the application instead of JSON data is sufficient to crash it. Another vulnerability allowing Denial-of-Service attacks.

And that WebSockets server websites connect to? It uses OpenSSL. Which OpenSSL? Actually, OpenSSL 1.0.2c. Yes, I can almost hear the collective sigh of all the security professionals here. OpenSSL 1.0.2c is seven years old. In fact, end of support for the 1.0.2 branch was three years ago: on January 1st, 2020. The last release here was OpenSSL 1.0.2u, meaning 18 more releases fixing bugs and security issues. None of the fixes made it into the nxKey application.

Let’s look at something more interesting than crashes. The application license mentioned above is base64-encoded data. The application needs to decode it. The decoder function looks like this:

size_t base64_decode(char *input, size_t input_len, char **result)
{
  size_t result_len = input_len / 4 * 3;
  if (str[input_len - 1] == '=')
    result_len--;
  if (str[input_len - 2] == '=')
    result_len--;
  *result = malloc(result_len + 1);

  // Decoding input in series of 4 characters here
}

I’m not sure where this function comes from. It has clear similarities with the base64 decoder of the CycloneCRYPTO library. But CycloneCRYPTO writes the result into a pre-allocated buffer. So it might be that the buffer allocation logic was added by nxKey developers themselves.

And that logic is flawed. It clearly assumes that input_len is a multiple of four. But for input like abcd== its calculation will result in a 2 bytes buffer being allocated, despite the actual output being 3 bytes large.

Is a one byte heap overflow exploitable? Yes, it clearly is as this Project Zero blog post or this article by Javier Jimenez explain. Writing such an exploit is beyond my skill level however.

Instead my proof of concept page merely sent the nxKey application randomly generated license strings. This was sufficient to crash the application in a matter of seconds. Connecting the debugger showed clear evidence of memory corruption: the application crashed because it attempted to read or write data using bogus memory locations. In some cases these memory locations came from the data supplied by my website. So clearly someone with sufficient skill and dedication could have abused that vulnerability for remote code execution.

Modern operating systems have mechanisms to make turning buffer overflows like this one into code execution vulnerabilities harder. But these mechanisms only help if they are actually being used. Yet nxKey developers turned Address space layout randomization off on two DLLs loaded by the application, Data Execution Prevention was turned off on four DLLs.

Abusing the helper application

So far this was all about web-based attacks. But what about the scenario where a malware application managed it into the system already and is looking for ways to expand its privileges? For an application meant to help combat such malware, TouchEn nxKey does surprisingly badly at keeping its functionality to itself.

There is for example the CKAgentNXE.exe helper application starting up whenever nxKey is intercepting keyboard input. Its purpose: when nxKey doesn’t want to handle a key, make sure it is delivered to the right target application. The logic in TKAppm.dll library used by the main application looks roughly like this:

if (IsAdmin())
  keybd_event(virtualKey, scanCode, flags, extraInfo);
else
{
  AgentConnector connector;

  // An attempt to open the helper’s IPC objects
  connector.connect();

  if (!connector.connected)
  {
    // Application isn’t running, start it now
    RunApplication("CKAgentNXE.exe");

    while (!connector.connected)
    {
      Sleep(10);
      connector.connect();
    }
  }

  // Some IPC dance involving a mutex, shared memory and events
  connector.sendData(2, virtualKey, scanCode, flags, extraInfo);
}

Since the nxKey application is running with user’s privileges, it will fall back to running CKAgentNXE.exe in every sensible setup. And that helper application, upon receiving command code 2, will call SendInput().

It took me a while to get an idea of what the reason for this approach might be. After all, both nxKey application and CKAgentNXE.exe are running on the same privilege level. Why not just call SendInput()? Why is this indirection necessary?

I noticed however that CKAgentNXE.exe sets a security descriptor for its IPC objects to allow access from processes with integrity level Low. And I also noticed that the setup program creates registry entries under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Low Rights\ElevationPolicy to allow automatic elevation of CKAgentNXE.exe. And that’s where it clicked: this is all because of the Internet Explorer sandbox.

So when TouchEn Key runs as ActiveX in Internet Explorer, its integrity level is Low. Being sandboxed in this way effectively makes it impossible to use SendInput(). This restriction is circumvented by allowing to run and automatically elevate CKAgentNXE.exe from the Internet Explorer sandbox. Once the helper application is running, the sandboxed ActiveX control can connect to it and ask it to do something. Like calling SendInput().

Outside of Internet Explorer this approach makes no sense, yet TouchEn nxKey also delegates work to CKAgentNXE.exe. And this has consequences for security.

Let’s say we have a malware that is running on the integrity level Low. It likely got there by exploiting a browser vulnerability, but now it is stuck in that sandbox. What can it do now? Why, just wait for CKAgentNXE.exe to start up (bound to happen sooner or later) and use it to break out!

My proof of concept application asked CKAgentNXE.exe to generate fake keyboard input for it: Win key, then C, M, D and the Enter key. This resulted in a command line prompt being opened, this one running with the Middle integrity level (the default one). A truly malicious application could then type in an arbitrary command to run code outside the sandbox.

Not that a truly malicious application would do things in such a visible way. CKAgentNXE.exe also accepts command code 5 for example which will load an arbitrary DLL into any process. That’s a much nicer way to infect a system, don’t you think?

At least this time one of the mandatory security applications decided to make itself useful and flag the threat:

AhnLab Safe Transaction application warning about C:\Temp\test.exe being infected with Malware/Win.RealProtect-LS.C5210489

A malware author could probably figure out what triggers this warning and get around it. Or they could initiate a web socket connection to make sure CKAgentNXE.exe starts up without also activating AhnLab application like a real banking website would. But why bother? It’s only a prompt, the attack isn’t being stopped proactively. By the time the user clicks to remove the malicious application, it will be too late – the attack already succeeded.

Accessing the driver’s keylogging functionality directly

As mentioned above, TouchEn nxKey application (the one encrypting keyboard input it receives from the driver) is running with user’s privileges. It isn’t an elevated application, it has no special privileges. How is access to the driver’s functionality being restricted then?

The correct answer of course is: it isn’t. Any application on the system has access to this functionality. It only needs to know how nxKey communicates with its driver. And in case you are wondering: that communication protocol isn’t terribly complicated.

I am not sure what the idea here was. TKAppm.dll, the library doing the driver communication, is obfuscated using Themida. The vendor behind Themida promises:

Themida® uses the SecureEngine® protection technology that, when running in the highest priority level, implements never seen before protection techniques to protect applications against advanced software cracking.

Maybe nxKey developers thought that this would offer sufficient protection against reverse engineering. Yet connecting a debugger at runtime allows saving already decrypted TKAppm.dll memory and load the result into Ghidra for analysis.

Message box titled TouchEn nxKey. The text says: Debugging Program is detected. Please Close Debugging Program and try again. TouchEn nxKey will not work with subsequent key. (If system is virtual PC, try real PC.)

Sorry, too late. I’ve already got what I needed. And it was no use that your application refuses to work when booting in Safe Mode.

Either way, I could write a tiny (70 lines of code) application that would connect to the driver and use it to intercept all keyboard input on the system. It didn’t require elevation, running with user’s privileges was sufficient. And unlike with a web page this application could also make sure this keyboard input is delivered to its destination, so the user doesn’t notice anything. Creating a keylogger was never so easy!

The best part: this keylogger integrated with the nxKey application nicely. So nxKey would receive keyboard input, encrypt it and send encrypted data to the website. And my tiny keylogger would also receive the same keyboard input, as clear text.

Side-note: Driver crashes

There is something you should know when developing kernel drivers: crashing the driver will crash the entire system. This is why you should make extra certain that your driver code never fails.

Can the driver used by nxKey fail? While I didn’t look at it too closely, I accidentally discovered that it can. See, the application will use DeviceIoControl() to ask the driver for a pointer to the input buffer. And the driver creates this pointer by calling MmMapLockedPagesSpecifyCache().

Yes, this means that this input buffer is visible to every single application on the system. But that’s not the main issue. It’s rather: what happens if the application requests the pointer again? Well, the driver will simply do another MmMapLockedPagesSpecifyCache() call.

After around 20 seconds of doing this in a loop the entire virtual address space is exhausted and MmMapLockedPagesSpecifyCache() returns NULL. The driver doesn’t check the return value and crashes. Boom, the operating system reboots automatically.

This issue isn’t exploitable from what I can tell (note: I am no expert when it comes to binary exploitation), but it is still rather nasty.

Will it be fixed?

Usually, when I disclose vulnerabilities they are already fixed. This time that’s unfortunately not the case. As far as I can tell, none of the issues have been addressed so far. I do not know when the vendors plan to fix these issues. I also do not know how they plan to push out the update to the users, particularly given that banks are already distributing builds that are at least three versions behind the current release. You remember: there is no autoupdate functionality.

Even reporting these issues was complicated. Despite specializing in security, RaonSecure doesn’t list any kind of security contact. In fact, RaonSecure doesn’t list any contact whatsoever, except for a phone number in Seoul. No, I’m not going to phone to Korea asking whether anyone speaks English there.

Luckily, KrCERT provides a vulnerability report form specifically for foreign citizens to use. This form will frequently error out and require you to re-enter everything, and some reports get caught up in a web firewall for no apparent reason, but at least the burden of locating the security contact is on someone else.

I reported all the vulnerabilities to KrCERT on October 4th, 2022. I still tried to contact some RaonSecure executives directly but received no response. At least KrCERT confirmed forwarding my reports to RaonSecure roughly two weeks later. They also noted that RaonSecure asked for my email address and wanted to contact me. They never did.

And that’s it. The 90 days disclosure deadline was a week ago. TouchEn nxKey 1.0.0.78 was apparently released on October 4th, 2022, the same day I reported these vulnerabilities. At the time of writing it remains the latest release, and all the vulnerabilities described here are still present in it. The latest version of the TouchEn browser extension used by millions of people is still five years old, released in January 2018.

Update (2023-01-10): In comments to Korean media, RaonSecure claims to have fixed the vulnerabilities and to distribute the update to customers. I cannot currently confirm this claim. The company’s own download server is still distributing TouchEn nxKey 1.0.0.78.

Side-note: The information leak

How do I even know that they are working on a fix? Well, thanks to something that never happened to me before: they leaked my proofs of concept (meaning: almost complete exploits for the vulnerabilities) prior to the deadline.

See, I used to attach files to my reports directly. However, these attachments would frequently end up being removed or otherwise destroyed by overzealous security software. So instead I now upload whatever files are needed to demonstrate the issue to my server. A link to my server always works. Additional benefit: even with companies that don’t communicate I can see in the logs whether the vendor accessed the proof of concept at all, meaning whether my report reached anyone.

A few days ago I checked the logs for accesses to the TouchEn nxKey files. And immediately saw Googlebot. Sure enough: these files ended up being listed in the Google index.

Now I use a random folder name, it cannot be guessed. And I only shared the links with the vendor. So the vendor must have posted a publicly visible link to the exploits somewhere.

And that’s in fact what they did. I found a development server, publicly visible and indexed by Google. It seems that this server was originally linking to my proof of concept pages. By the time I found it, it was instead hosting the vendor’s modified copies of them.

The first request by Googlebot was on October 17th, 2022. So I have to assume that these vulnerabilities could be found via a Google search more than two months prior to the disclosure deadline. They have been accessed many times, hard to tell whether it’s only been the product’s developers.

After reporting this issue the development server immediately disappeared from the public internet. Still, such careless handling of security-sensitive information isn’t something I’ve ever seen before.

Can the nxKey concept even work?

We’ve seen a number of vulnerabilities in the TouchEn nxKey application. By attempting to combat keyloggers, nxKey developers built a perfect keylogging toolset and failed to restrict access to it. But the idea is nice, isn’t it? Maybe it would actually be a useful security tool if built properly?

Question is: the keylogger that is being protected against, what level does it run on? The way I see it, there are four options:

  1. In the browser. So some malicious JavaScript code is running in the online banking page, attempting to capture passwords. That code can trivially stop the page from activating nxKey.
  2. In the system, with user’s privileges. This privilege level is e.g. sufficient to kill the CrossEXService.exe process which is also running with user’s privileges. This achieves the same results as my denial-of-service attacks, protection is effectively disabled.
  3. In the system, with administrator privileges. That’s actually sufficient privileges to unload the nxKey driver and replace it by a trojanized copy.
  4. In the hardware. Game over, good luck trying any software-based solutions against that.

So whatever protection nxKey might provide, it relies on attackers who are unaware of nxKey and its functionality. Generic attacks may be thwarted, but it is unlikely to be effective against any attacks targeting specifically South Korean banks or government organizations.

Out of these four levels, number 2 might be possible to fix. The application CrossEXService.exe could be made to run with administrator’s privileges. This would prevent malware from messing with this process. Effectiveness of this protection would still rely on the malware being unable to get into the user’s browser however.

I cannot see how this concept could be made to work reliably against malware operating on other levels.

Packet Storm

Retrieved title: News ≈ Packet Storm, 6 item(s)
Hackers Demand $10M From Riot Games To Stop Leak Of League Of Legends Source Code

DragonSpark Threat Actor Leverages Open Source RAT

GoTo Warns Customers Of Crypto Key And Backup Heist

Quantum Computers No Threat To Encryption Just Yet

Ransomware Victims Are Refusing To Pay

Microsoft To Invest Billions In Chatbot Maker OpenAI