by zer0x0ne — on


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


Retrieved title: xkcd.com, 3 item(s)
Elementary Physics Paths

==COSMOLOGY==> 'Uhhh ... how sure are we that everything is made of these?'

Driving PSA

This PSA brought to you by several would-be assassins who tried to wave me in front of speeding cars in the last month and who will have to try harder next time.


Certain hybrid events can only happen in certain locations where all the conditions are present; chasers flock to the area in and around Kansas known as tumbleweed-colliding-with-possum alley.

PortSwigger Research

Retrieved title: PortSwigger Research, 6 item(s)
Making desync attacks easy with TRACE

Have you ever found an HTTP desync vulnerability that seemed impossible to exploit due to its complicated constraints? In this blogpost we will explore a new exploitation technique that can be used to

Using form hijacking to bypass CSP

In this post we'll show you how to bypass CSP by using an often overlooked technique that can enable password theft in a seemingly secure configuration. What is form hijacking? Form hijacking isn't re

Top 10 web hacking techniques of 2023

Welcome to the Top 10 Web Hacking Techniques of 2023, the 17th edition of our annual community-powered effort to identify the most innovative must-read web security research published in the last year

Hiding payloads in Java source code strings

In this post we'll show you how Java handles unicode escapes in source code strings in a way you might find surprising - and how you can abuse them to conceal payloads. We recently released a powerful

Top 10 web hacking techniques of 2023 - nominations open

Update: The results are in! Check out the final top ten here or scroll down to view all nominations Over the last year, numerous security researchers have shared their discoveries with the community t

Finding that one weird endpoint, with Bambdas

Security research involves a lot of failure. It's a perpetual balancing act between taking small steps with a predictable but boring outcome, and trying out wild concepts that are so crazy they might

Dark Reading

Retrieved title: darkreading, 6 item(s)
Flaw in Wi-Fi Standard Can Enable SSID Confusion Attacks

Attackers can exploit the issue to trick users into connecting to insecure networks, but it works only under specific conditions.

Palo Alto Networks and IBM to Jointly Provide AI-Powered Security Offerings

Notice of a Data Breach

Alkira Raises $100M in Series C Funding to Simplify, Secure and Scale Critical Network Infrastructure

Scammers Fake DocuSign Templates to Blackmail & Steal From Companies

Cybercriminals are trafficking DocuSign assets that allow for easy extortion and business email compromise.

FBI, DoJ Shut Down BreachForums, Launch Investigation

Instead of online contraband, the website now asks anyone with information that could help with the investigation to contact authorities.

Almost Secure

Retrieved title: Almost Secure, 3 item(s)
Numerous vulnerabilities in Xunlei Accelerator application

Xunlei Accelerator (迅雷客户端) a.k.a. Xunlei Thunder by the China-based Xunlei Ltd. is a wildly popular application. According to the company’s annual report 51.1 million active users were counted in December 2022. The company’s Google Chrome extension 迅雷下载支持, while not mandatory for using the application, had 28 million users at the time of writing.

Screenshot of the application’s main window with Chinese text and two advertising blocks

I’ve found this application to expose a massive attack surface. This attack surface is largely accessible to arbitrary websites that an application user happens to be visiting. Some of it can also be accessed from other computers in the same network or by attackers with the ability to intercept user’s network connections (Man-in-the-Middle attack).

It does not appear like security concerns were considered in the design of this application. Extensive internal interfaces were exposed without adequate protection. Some existing security mechanisms were disabled. The application also contains large amounts of third-party code which didn’t appear to receive any security updates whatsoever.

I’ve reported a number of vulnerabilities to Xunlei, most of which allowed remote code execution. Still, given the size of the attack surface it felt like I barely scratched the surface.

Last time Xunlei made security news, it was due to distributing a malicious software component. Back then it was an inside job, some employees turned rouge. However, the application’s flaws allowed the same effect to be easily achieved from any website a user of the application happened to be visiting.

What is Xunlei Accelerator?

Wikipedia lists Xunlei Limited’s main product as a Bittorrent client, and maybe a decade ago it really was. Today however it’s rather difficult to describe what this application does. Is it a download manager? A web browser? A cloud storage service? A multimedia client? A gaming platform? It appears to be all of these things and more.

It’s probably easier to think of Xunlei as an advertising platform. It’s an application with the goal of maximizing profits through displaying advertising and selling subscriptions. As such, it needs to keep the users on the platform for as long as possible. That’s why it tries to implement every piece of functionality the user might need, while not being particularly good at any of it of course.

So there is a classic download manager that will hijack downloads initiated in the browser, with the promise of speeding them up. There is also a rudimentary web browser (two distinctly different web browsers in fact) so that you don’t need to go back to your regular web browser. You can play whatever you are downloading in the built-in media player, and you can upload it to the built-in storage. And did I mention games? Yes, there are games as well, just to keep you occupied.

Altogether this is a collection of numerous applications, built with a wide variety of different technologies, often implementing competing mechanisms for the same goal, yet trying hard to keep the outward appearance of a single application.

The built-in web browser

The trouble with custom Chromium-based browsers

Companies love bringing out their own web browsers. The reason is not that their browser is any better than the other 812 browsers already on the market. It’s rather that web browsers can monetize your searches (and, if you are less lucky, also your browsing history) which is a very profitable business.

Obviously, profits from that custom-made browser are higher if the company puts as little effort into maintenance as possible. So they take the open source Chromium, slap their branding on it, maybe also a few half-hearted features, and they call it a day.

Trouble is: a browser has a massive attack surface which is exposed to arbitrary web pages (and ad networks) by definition. Companies like Mozilla or Google invest enormous resources into quickly plugging vulnerabilities and bringing out updates every six weeks. And that custom Chromium-based browser also needs updates every six weeks, or it will expose users to known (and often widely exploited) vulnerabilities.

Even merely keeping up with Chromium development is tough, which is why it almost never happens. In fact, when I looked at the unnamed web browser built into the Xunlei application (internal name: TBC), it was based on Chromium 83.0.4103.106. Being released in May 2020, this particular browser version was already three and a half years old at that point. For reference: Google fixed eight actively exploited zero-day vulnerabilities in Chromium in the year 2023 alone.

Among others, the browser turned out to be vulnerable to CVE-2021-38003. There is this article which explains how this vulnerability allows JavaScript code on any website to gain read/write access to raw memory. I could reproduce this issue in the Xunlei browser.

Protections disabled

It is hard to tell whether not having a pop-up blocker in this browser was a deliberate choice or merely a consequence of the browser being so basic. Either way, websites are free to open as many tabs as they like. Adding --autoplay-policy=no-user-gesture-required command line flag definitely happened intentionally however, turning off video autoplay protections.

It’s also notable that Xunlei revives Flash Player in their browser. Flash Player support has been disabled in all browsers in December 2020, for various reasons including security. Xunlei didn’t merely decide to ignore this reasoning, they shipped Flash Player (released in April 2018) with their browser. Adobe support website lists numerous Flash Player security fixes published after April 2018 and before end of support.

Censorship included

Interestingly, Xunlei browser won’t let users visit the example.com website (as opposed to example.net). When you try, the browser redirects you to a page on static.xbase.cloud. This is an asynchronous process, so chances are good that you will catch a glimpse of the original page first.

Screenshot of a page showing a WiFi symbol with an overlaid question mark and some Chinese text.

Automated translation of the text: “This webpage contains illegal or illegal content and access has been stopped.”

As it turns out, the application will send every website you visit to an endpoint on api-shoulei-ssl.xunlei.com. That endpoint will either accept your choice of navigation target or instruct to redirect you to a different address. So when to navigate to example.com the following request will be sent:

POST /xlppc.blacklist.api/v1/check HTTP/1.1
Content-Length: 29
Content-Type: application/json
Host: api-shoulei-ssl.xunlei.com


And the server responds with:

  "code": 200,
  "msg": "ok",
  "data": {
    "host": "example.com",
    "redirect": "https://static.xbase.cloud/file/2rvk4e3gkdnl7u1kl0k/xappnotfound/#/unreach",
    "result": "reject"

Interestingly, giving it the address http://example.com./ (note the trailing dot) will result in the response {"code":403,"msg":"params error","data":null}. With the endpoint being unable to handle this address, the browser will allow you to visit it.

Native API

In an interesting twist, the Xunlei browser exposed window.native.CallNativeFunction() method to all web pages. Calls would be forwarded to the main application where any plugin could register its native function handlers. When I checked, there were 179 such handlers registered, though that number might vary depending on the active plugins.

Among the functions exposed were ShellOpen (used Windows shell APIs to open a file), QuerySqlite (query database containing download tasks), SetProxy (configure a proxy server to be used for all downloads) or GetRecentHistorys (retrieve browsing history for the Xunlei browser).

My proof-of-concept exploit would run the following code:

native.CallNativeFunction("ShellOpen", "c:\\windows\\system32\\calc.exe");

This would open the Windows Calculator, just as you’d expect.

Now this API was never meant to be exposed to all websites but only to a selected few very “trusted” ones. The allowlist here is:


And here is how access was being validated:

function isUrlInDomains(url, allowlist, frameUrl) {
  let result = false;
  for (let index = 0; index < allowlist.length; ++index) {
    if (url.includes(allowlist[index]) || frameUrl && frameUrl.includes(allowlist[index])) {
      result = true;
  return result;

As you might have noticed, this doesn’t actually validate the host name against the list but looks for substring matches in the entire address. So https://malicious.com/?www.xunlei.com is also considered a trusted address, allowing for a trivial circumvention of this “protection.”

Getting into the Xunlei browser

Now most users hopefully won’t use Xunlei for their regular browsing. These should be safe, right?

Unfortunately not, as there is a number of ways for webpages to open the Xunlei browser. The simplest way is using a special thunderx:// address. For example, thunderx://eyJvcHQiOiJ3ZWI6b3BlbiIsInBhcmFtcyI6eyJ1cmwiOiJodHRwczovL2V4YW1wbGUuY29tLyJ9fQ== will open the Xunlei browser and load https://example.com/ into it. From the attacker’s point of view, this approach has a downside however: modern browsers ask the user for confirmation before letting external applications handle addresses.

Screenshot of a confirmation dialog titled “Open 迅雷?” The text says: “A website wants to open this application.” There are two buttons labeled “Open 迅雷” and “Cancel.”

There are alternatives however. For example, the Xunlei browser extension (28 million users according to Chrome Web Store) is meant to pass on downloads to the Xunlei application. It could be instrumented into passing on thunderx:// links without any user interaction however, and these would immediately open arbitrary web pages in the Xunlei browser.

More ways to achieve this are exposed by the XLLite application’s API which is introduced later. And that’s likely not even the end of it.

The fixes

While Xunlei never communicated any resolution of these issues to me, as of Xunlei Accelerator (built on February 2, 2024 judging by executable signatures) several changes have been implemented. First of all, the application no longer packages Flash Player. It still activates Flash Player if it is installed on the user’s system, so some users will still be exposed. But chances are good that this Flash Player installation will at least be current (as much as software can be “current” three years after being discontinued).

The isUrlInDomains() function has been rewritten, and the current logic appears reasonable. It will now only check the allowlist against the end of the hostname, matches elsewhere in the address won’t be accepted. So this now leaves “only” all of the xunlei.com domain with access to the application’s internal APIs. Any cross-site scripting vulnerability anywhere on this domain will again put users at risk.

The outdated Chromium base appears to remain unchanged. It still reports as Chromium 83.0.4103.106, and the exploit for CVE-2021-38003 still succeeds.

The browser extension 迅雷下载支持 also received an update, version 3.48 on January 3, 2024. According to automated translation, the changelog entry for this version reads: “Fixed some known issues.” The fix appears to be adding a bunch of checks for the event.isTrusted property, making sure that the extension can no longer be instrumented quite as easily. Given these restrictions, just opening the thunderx:// address directly likely has higher chances of success now, especially when combined with social engineering.

The main application

Outdated Electron framework

The main Xunlei application is based on the Electron framework. This means that its user interface is written in HTML and displayed via the Chromium web browser (renderer process). And here again it’s somewhat of a concern that the Electron version used is 83.0.4103.122 (released in June 2020). It can be expected to share most of the security vulnerabilities with a similarly old Chromium browser.

Granted, an application like that should be less exposed than a web browser as it won’t just load any website. But it does work with remote websites, so vulnerabilities in the way it handles web content are an issue.

Cross-site scripting vulnerabilities

Being HTML-based, the Xunlei application is potentially vulnerable to cross-site scripting vulnerabilities. For most part, this is mitigrated by using the React framework. React doesn’t normally work with raw HTML code, so there is no potential for vulnerabilities here.

Well, normally. Unless dangerouslySetInnerHTML property is being used, which you should normally avoid. But it appears that Xunlei developers used this property in a few places, and now they have code displaying messages like this:

$createElement("div", {
  staticClass: "xly-dialog-prompt__text",
  domProps: { innerHTML: this._s(this.options.content) }

If message content ever happens to be some malicious data, it could create HTML elements that will result in execution of arbitrary JavaScript code.

How would malicious data end up here? Easiest way would be via the browser. There is for example the MessageBoxConfirm native function that could be called like this:

native.CallNativeFunction("MessageBoxConfirm", JSON.stringify({
  title: "Hi",
  content: `<img src="x" onerror="alert(location.href)">`,
  type: "info",
  okText: "Ok",
  cancelVisible: false

When executed on a “trusted” website in the Xunlei browser, this would make the main application display a message and, as a side-effect, run the JavaScript code alert(location.href).

Impact of executing arbitrary code in the renderer process

Electron normally sandboxes renderer processes, making certain that these have only limited privileges and vulnerabilities are harder to exploit. This security mechanism is active in the Xunlei application.

However, Xunlei developers at some point must have considered it rather limiting. After all, their user interface needed to perform lots of operations. And providing a restricted interface for each such operation was too much effort.

So they built a generic interface into the application. By means of messages like AR_BROWSER_REQUIRE or AR_BROWSER_MEMBER_GET, the renderer process can instruct the main (privileged) process of the application to do just about anything.

My proof-of-concept exploit successfully abused this interface by loading Electron’s shell module (not accessible to sandboxed renderers by regular means) and calling one of its methods. In other words, the Xunlei application managed to render this security boundary completely useless.

The (lack of) fixes

Looking at Xunlei Accelerator, I could not recognize any improvements in this area. The application is still based on Electron 83.0.4103.122. The number of potential XSS vulnerabilities in the message rendering code didn’t change either.

It appears that Xunlei called it a day after making certain that triggering messages with arbitrary content became more difficult. I doubt that it is impossible however.

The XLLite application

Overview of the application

The XLLite application is one of the plugins running within the Xunlei framework. Given that I never created a Xunlei account to see this application in action, my understanding of its intended functionality is limited. Its purpose however appears to be integrating the Xunlei cloud storage into the main application.

As it cannot modify the main application’s user interface directly, it exposes its own user interface as a local web server, on a randomly chosen port between 10500 and 10599. That server essentially provides static files embedded in the application, all functionality is implemented in client-side JavaScript.

Privileged operations are provided by a separate local server running on port 21603. Some of the API calls exposed here are handled by the application directly, others are forwarded to the main application via yet another local server.

I originally got confused about how the web interface accesses the API server, with the latter failing to implement CORS correctly – OPTION requests don’t get a correct response, so that only basic requests succeed. It appears that Xunlei developers didn’t manage to resolve this issue and instead resorted to proxying the API server on the user interface server. So any endpoints available on the API server are exposed by the user interface server as well, here correctly (but seemingly unnecessarily) using CORS to allow access from everywhere.

So the communication works like this: the Xunlei application loads in a frame. The page then requests some API on its own port, e.g. When handling the request, the XLLite application requests internally. And the API server handler within the same process responds with the current timestamp.

This approach appears to make little sense. However, it’s my understanding that Xunlei also produces storage appliances which can be installed on the local network. Presumably, these appliances run identical code to expose an API server. This would also explain why the API server is exposed to the network rather than being a localhost-only server.

The “pan authentication”

With quite a few API calls having the potential to do serious damage or at the very least expose private information, these need to be protected somehow. As mentioned above, Xunlei developers chose not to use CORS to restrict access but rather decided to expose the API to all websites. Instead, they implemented their own “pan authentication” mechanism.

Their approach of generating authentication tokens was taking the current timestamp, concatenating it with a long static string (hardcoded in the application) and hashing the result with MD5. Such tokens would expire after 5 minutes, apparently an attempt to thwart replay attacks.

They even went as far as to perform time synchronization, making sure to correct for deviation between the current time as perceived by the web page (running on the user’s computer) and by the API server (running on the user’s computer). Again, this is something that probably makes sense if the API server can under some circumstances be running elsewhere on the network.

Needless to say that this “authentication” mechanism doesn’t provide any value beyond very basic obfuscation.

Achieving code execution via plugin installation

There are quite a few interesting API calls exposed here. For example, the device/v1/xllite/sign endpoint would sign data with one out of three private RSA keys hardcoded in the application. I don’t know what this functionality is used for, but I sincerely hope that it’s as far away from security and privacy topics as somehow possible.

There is also the device/v1/call endpoint which is yet another way to open a page in the Xunlei browser. Both OnThunderxOpt and OpenNewTab calls allow that, the former taking a thunderx:// address to be processed and the latter a raw page address to be opened in the browser.

It’s fairly obvious that the API exposes full access to the user’s cloud storage. I chose to focus my attention on the drive/v1/app/install endpoint however, which looked like it could do even more damage. This endpoint in fact turned out to be a way to install binary plugins.

I couldn’t find any security mechanisms preventing malicious software to be installed this way, apart from the already mentioned useless “pan authentication.” However, I couldn’t find any actual plugins to use as an example. In the end I figured out that a plugin had to be packaged in an archive containing a manifest.yaml file like the following:

ID: Exploit
Title: My exploit
Description: This is an exploit
Version: 1.0.0
  - OS: windows
    ARCH: 386
  ExecStart: Exploit.exe
  ExecStop: Exploit.exe

The plugin would install successfully under Thunder\Profiles\XLLite\plugin\Exploit\1.0.1\Exploit but the binary wouldn’t execute for some reason. Maybe there is a security mechanism that I missed, or maybe the plugin interface simply isn’t working yet.

Either way, I started thinking: what if instead of making XLLite run my “plugin” I would replace an existing binary? It’s easy enough to produce an archive with file paths like ..\..\..\oops.exe. However, the Go package archiver used here has protection against such path traversal attacks.

The XLLite code deciding which folder to put the plugin into didn’t have any such protections on the other hand. The folder is determined by the ID and Version values of the plugin’s manifest. Messing with the former is inconvenient, it being present twice in the path. But setting the “version” to something like ..\..\.. achieved the desired results.

Two complications:

  1. The application to be replaced cannot be running or the Windows file locking mechanism will prevent it from being replaced.
  2. The plugin installation will only replace entire folders.

In the end, I chose to replace Xunlei’s media player for my proof of concept. This one usually won’t be running and it’s contained in a folder of its own. It’s also fairly easy to make Xunlei run the media player by using a thunderx:// link. Behold, installation and execution of a malicious application without any user interaction.

Remember that the API server is exposed to the local network, meaning that any devices on the network can also perform API calls. So this attack could not merely be executed from any website the user happened to be visiting, it could also be launched by someone on the same network, e.g. when the user is connected to a public WiFi.

The fixes

As of version 3.19.4 of the XLLite plugin (built January 25, 2024 according to its digital signature), the “pan authentication” method changed to use JSON Web Tokens. The authentication token is embedded within the main page of the user interface server. Without any CORS headers being produced for this page, the token cannot be extracted by other web pages.

It wasn’t immediately obvious what secret is being used to generate the token. However, authentication tokens aren’t invalidated if the Xunlei application is restarted. This indicates that the secret isn’t being randomly generated on application startup. The remaining possibilities are: a randomly generated secret stored somewhere on the system (okay) or an obfuscated hardcoded secret in the application (very bad).

While calls to other endpoints succeed after adjusting authentication, calls to the drive/v1/app/install endpoint result in a “permission denied” response now. I did not investigate whether the endpoint has been disabled or some additional security mechanism has been added.

Plugin management

The oddities

XLLite’s plugin system is actually only one out of at least five completely different plugin management systems in the Xunlei application. One other is the main application’s plugin system, the XLLite application is installed as one such plugin. There are more, and XLLiveUpdateAgent.dll is tasked with keeping them updated. It will download the list of plugins from an address like http://upgrade.xl9.xunlei.com/plugin?os=10.0.22000&pid=21&v= and make sure that the appropriate plugins are installed.

Note the lack of TLS encryption here which is quite typical. Part of the issue appears to be that Xunlei decided to implement their own HTTP client for their downloads. In fact, they’ve implemented a number of different HTTP clients instead of using any of the options available via the Windows API for example. Some of these HTTP clients are so limited that they cannot even parse uncommon server responses, much less support TLS. Others support TLS but use their own list of CA certificates which happens to be Mozilla’s list from 2016 (yes, that’s almost eight years old).

Another common issue is that almost all these various update mechanisms run as part of the regular application process, meaning that they only have user’s privileges. How do they manage to write to the application directory then? Well, Xunlei solved this issue: they made the application directory writable with user’s privileges! Another security mechanism successfully dismantled. And there is a bonus: they can store application data in the same directory rather than resorting to per-user nonsense like AppData.

Altogether, you better don’t run Xunlei Accelerator on untrusted networks (meaning: any of them?). Anyone on your network or anyone who manages to insert themselves into the path between you and the Xunlei update server will be able to manipulate the server response. As a result, the application will install a malicious plugin without you even noticing anything.

You also better don’t run Xunlei Accelerator on a computer that you share with other people. Anyone on a shared computer will be able to add malicious components to the Xunlei application, so next time you run it your user account will be compromised.

Example scenario: XLServicePlatform

I decided to focus on XLServicePlatform because, unlike all the other plugin management systems, this one runs with system privileges. That’s because it’s a system service and any installed plugins will be loaded as dynamic libraries into this service process. Clearly, injecting a malicious plugin here would result in full system compromise.

The management service downloads the plugin configuration from http://plugin.pc.xunlei.com/config/XLServicePlatform_12.0.3.xml. Yes, no TLS encryption here because the “HTTP client” in question isn’t capable of TLS. So anyone on the same WiFi network as you for example could redirect this request and give you a malicious response.

In fact, that HTTP client was rather badly written, and I found multiple Out-of-Bounds Read vulnerabilities despite not actively looking for them. It was fairly easy to crash the service with an unexpected response.

But it wasn’t just that. The XML response was parsed using libexpat 2.1.0. With that version being released more than ten years ago, there are numerous known vulnerabilities, including a number of critical remote code execution vulnerabilities.

I generally leave binary exploitation to other people however. Continuing with the high-level issues, a malicious plugin configuration will result in a DLL or EXE file being downloaded, yet it won’t run. There is a working security mechanism here: these files need a valid code signature issued to Shenzhen Thunder Networking Technologies Ltd.

But it still downloads. And there is our old friend: a path traversal vulnerability. Choosing the file name ..\XLBugReport.exe for that plugin will overwrite the legitimate bug reporter used by the Xunlei service. And crashing the service with a malicious server response will then run this trojanized bug reporter, with system privileges.

My proof of concept exploit merely created a file in the C:\Windows directory, just to demonstrate that it runs with sufficient privileges to do it. But we are talking about complete system compromise here.

The (lack of?) fixes

At the time of writing, XLServicePlatform still uses its own HTTP client to download plugins which still doesn’t implement TLS support. Server responses are still parsed using libexpat 2.1.0. Presumably, the Out-of-Bounds Read and Path Traversal vulnerabilities have been resolved but verifying that would take more time than I am willing to invest.

The application will still render its directory writable for all users. It will also produce a number of unencrypted HTTP requests, including some that are related to downloading application components.

Outdated components

I’ve already mentioned the browser being based on an outdated Chromium version, the main application being built on top of an outdated Electron platform and a ten years old XML library being widely used throughout the application. This isn’t by any means the end of it however. The application packages lots of third-party components, and the general approach appears to be that none of them are ever updated.

Take for example the media player XMP a.k.a. Thunder Video which is installed as part of the application and can be started via a thunderx:// address from any website. This is also an Electron-based application, but it’s based on an even older Electron 59.0.3071.115 (released in June 2017). The playback functionality seems to be based on the APlayer SDK which Xunlei provides for free for other applications to use.

Now you might know that media codecs are extremely complicated pieces of software that are known for disastrous security issues. That’s why web browsers are very careful about which media codecs they include. Yet APlayer SDK features media codecs that have been discontinued more than a decade ago as well as some so ancient that I cannot even figure out who developed them originally. There is FFmpeg 2021-06-30 (likely a snapshot around version 4.4.4), which has dozens of known vulnerabilities. There is libpng 1.0.56, which was released in July 2011 and is affected by seven known vulnerabilities. Last but not least, there is zlib 1.2.8-4 which was released in 2015 and is affected by at least two critical vulnerabilities. These are only some examples.

So there is a very real threat that Xunlei users might get compromised via a malicious media file, either because they were tricked into opening it with Xunlei’s video player, or because a website used one of several possible ways to open it automatically.

As of Xunlei Accelerator, I could not notice any updates to these components.

Reporting the issues

Reporting security vulnerabilities is usually quite an adventure, and the language barrier doesn’t make it any better. So I was pleasantly surprised to discover XunLei Security Response Center that was even discoverable via an English-language search thanks to the site heading being translated.

Unfortunately, there was a roadblock: submitting a vulnerability is only possible after logging in via WeChat or QQ. While these social networks are immensely popular in China, creating an account from outside China proved close to impossible. I’ve spent way too much time on verifying that.

That’s when I took a closer look and discovered an email address listed on the page as fallback for people who are unable to log in. So I’ve sent altogether five vulnerability reports on 2023-12-06 and 2023-12-07. The number of reported vulnerabilities was actually higher because the reports typically combined multiple vulnerabilities. The reports mentioned 2024-03-06 as publication deadline.

I received a response a day later, on 2023-12-08:

Thank you very much for your vulnerability submission. XunLei Security Response Center has received your report. Once we have successfully reproduced the vulnerability, we will be in contact with you.

Just like most companies, they did not actually contact me again. I saw my proof of concept pages being accessed, so I assumed that the issues are being worked on and did not inquire further. Still, on 2024-02-10 I sent a reminder that the publication deadline was only a month away. I do this because in my experience companies will often “forget” about the deadline otherwise (more likely: they assume that I’m not being serious about it).

I received another laconic reply a week later which read:

XunLei Security Response Center has verified the vulnerabilities, but the vulnerabilities have not been fully repaired.

That was the end of the communication. I don’t really know what Xunlei considers fixed and what they still plan to do. Whatever I could tell about the fixes here has been pieced together from looking at the current software release and might not be entirely correct.

It does not appear that Xunlei released any further updates in the month after this communication. Given the nature of the application with its various plugin systems, I cannot be entirely certain however.

Implementing a “Share on Mastodon” button for a blog

I decided that I would make it easier for people to share my articles on social media, most importantly on Mastodon. However, my Hugo theme didn’t support showing a “Share on Mastodon” button yet. It wasn’t entirely trivial to add support either: unlike with centralized solutions like Facebook where a simple link is sufficient, here one would need to choose their home instance first.

As far as existing solutions go, the only reasonably sophisticated approach appears to be Share₂Fedi. It works nicely, privacy-wise one could do better however. So I ended up implementing my own solution while also generalizing that solution to support a variety of different Fediverse applications in addition to Mastodon.

Screenshot of a web page titled “Share on Fediverse”

Why not Share₂Fedi?

If all you want is quickly adding a “Share on Fediverse” button, Share₂Fedi is really the simplest solution. It only requires a link, just like your typical share button. You link to the Share₂Fedi website, passing the text to be shared as a query parameter. The user will be shown an interstitial page there, allowing them to select a Fediverse instance. After submitting the form they will be redirected to the Fediverse instance in question for the final confirmation.

Unfortunately, the privacy aspect of this solution isn’t quite optimal. Rather than having all the processing happen on the client side, Share₂Fedi relies on server-side processing. This means that your data is being stored in the server logs at the very least. This data being the address and title of the article being shared, it isn’t terribly sensitive. Yet why send any data to a third party when you could send none?

I was told that Share₂Fedi was implemented in this way in order to work even with client-side JavaScript disabled. Which is a fair point but not terribly convincing seeing how your typical social media website won’t work without JavaScript.

But it is possible to self-host Share₂Fedi of course. It is merely something I’d rather avoid. See, this blog is built with the Hugo static site generator, and there is very little server-side functionality here. I’d rather keep it this way.

Share on Mastodon or on Fediverse?

Originally, I meant to add buttons for individual applications. First a “Share on Mastodon” button. Then maybe “Share on Lemmy.” Also “Share on Kbin.” Oh, maybe also Friendica. Wait, Misskey exposes the same sharing endpoint as Mastodon?

I realized that this approach doesn’t really scale, with the Fediverse consisting of many different applications. Supporting the applications beyond Mastodon is trivial, but adding individual buttons for each application would create a mess.

So maybe have a “Share on Fediverse” button instead of “Share on Mastodon”? Users have to select an instance anyway, and the right course of action can be determined based on the type of this instance. There is a Fediverse logo as well.

Only concern: few people know the Fediverse logo so far, way fewer than the people recognizing the Mastodon logo. So I decided to show both “Share on Mastodon” and “Share on Fediverse” buttons. When clicked, both lead to the exact same page.

A black-and-white version of the Mastodon logo next to a similarly black-and-white version of the Fediverse logo

And that page would choose the right endpoint based on the selected instance. Here are the endpoints for individual Fediverse applications (mostly taken over from Share₂Fedi, some additions by me):

  "calckey": "share?text={text}",
  "diaspora": "bookmarklet?title={title}&notes={description}&url={url}",
  "fedibird": "share?text={text}",
  "firefish": "share?text={text}",
  "foundkey": "share?text={text}",
  "friendica": "compose?title={title}&body={description}%0A{url}",
  "glitchcafe": "share?text={text}",
  "gnusocial": "notice/new?status_textarea={text}",
  "hometown": "share?text={text}",
  "hubzilla": "rpost?title={title}&body={description}%0A{url}",
  "kbin": "new/link?url={url}",
  "mastodon": "share?text={text}",
  "meisskey": "share?text={text}",
  "microdotblog": "post?text=[{title}]({url})%0A%0A{description}",
  "misskey": "share?text={text}"

Note: From what I can tell, Lemmy and Pleroma don’t have an endpoint which could be used.

What to share?

Share₂Fedi assumes that all Fediverse applications accept unstructured text. So that’s the default for my solution as well: a text consisting of the article’s title, description and address.

When it comes to the Fediverse, one size does not fit all however. Some applications like Diaspora expect more structured input. Micro.blog on the other hand expects Markdown input, special markup is required for a link to be displayed. And Kbin has the most exotic solution: it accepts only the article’s address, all other article metadata is then retrieved automatically.

So I resorted to displaying all the individual fields on the intermediate sharing page:

A form titled “Share on Fediverse” with pre-filled fields Post title, Description and Link. The “Fediverse instance” field is focused and empty.

These fields are pre-filled and cannot be edited. After all, what good would editing these fields do if some of them might be thrown away or mashed together in the next step? So editing the text is delegated to the Fediverse instance, and this page is only about choosing an instance.

Trouble determining the Fediverse application

So, in order to choose the right endpoint, one has to know what Fediverse application powers the selected instance. Luckily, that’s easy. First, one downloads the .well-known/nodeinfo file of the instance. Here is the one for infosec.exchange:

  "links": [
      "rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
      "href": "https://infosec.exchange/nodeinfo/2.0"

We need the link marked with rel value http://nodeinfo.diaspora.software/ns/schema/2.0. Next we download this one and get:

  "version": "2.0",
  "software": {
    "name": "mastodon",
    "version": "4.3.0-alpha.0+glitch"
  "protocols": ["activitypub"],
  "services": {
    "outbound": [],
    "inbound": []
  "usage": {
    "users": {
      "total": 60802,
      "activeMonth": 17803,
      "activeHalfyear": 33565
    "localPosts": 2081420
  "openRegistrations": true,
  "metadata": {}

There it is: software name is identified as mastodon, so we know to use the share?text= endpoint.

The catch is: when I tried implementing this check, most Fediverse applications didn’t have consistent CORS headers on their node info responses. And this means that third-party websites (like my blog) would be allowed to request these endpoints but wouldn’t get access to the response. So no software name for me.

Now obviously it shouldn’t be like this, allowing third-party websites to access the node info is very much desirable. And most Fediverse applications being open source software, I fixed this issue for Mastodon, Diaspora, Friendica and Kbin. GNU Social, Misskey, Lemmy, Pleroma, Pixelfed and Peertube already had it working correctly.

But the issue remains: it will take quite some time until we can expect node info downloads to work reliably. One could use a CORS proxy of course, but it would run contrary to the goal of not relying on third parties. Or use a self-hosted CORS proxy, but that’s again adding server-side functionality.

I went with another solution. The Fediverse Observer website offers an API that allows querying its list of Fediverse instances. For example, the following query downloads information on all instances it knows.

{nodes(softwarename: ""){

Unfortunately, it doesn’t have meaningful filtering capabilities. So I have to filter it after downloading: I only keep the servers with an uptime score above 90 and at least 10 active users in the past month. This results in a list of roughly 2200 instances, meaning 160 KiB uncompressed – a reasonable size IMHO, especially compared to the 5.5 MiB of the unfiltered list.

For my blog, Hugo will download this list when building the static site and incorporate it into the sharing page. So for most Fediverse instances, the page will already know what software is running on it. And if it doesn’t know an instance? Fall back to downloading the node info. And if that fails as well, just assume that it’s Mastodon.

Is this a perfect solution? Certainly not. Is it good enough? Yes, I think so. And we need that list of Fediverse instances anyway, for autocomplete functionality on the instance field.

The complete code

This solution is now part of the MemE theme for Hugo, see the corresponding commit. components/post-share.html partial is where the buttons are being displayed. These link to the fedishare.html page and pass various parameters via the anchor part of the URL (not the query string so that these aren’t being saved to server logs).

The fedishare.html page is stored under assets. That’s because having a template turned into a static page would otherwise not happen by default and require additional changes to the configuration file. But that asset loads the fedishare.html partial where the actual logic is located.

Building that page involves querying the Fediverse Observer API and filtering the response. Websites that are built too frequently can set up Hugo’s cache to avoid hitting the network every time.

The resulting list is put into a <datalist> element, used for autocomplete on the instance field. The same list is also being used by the getSoftwareName() function in the fedishare.js asset, the script powering the page. Fallback for unknown instances is retrieving node info, and fallback here is just assuming Mastodon.

Once this chooses some Fediverse application, the script will take the corresponding endpoint, replace the placeholders by actual values and trigger navigation to that address.

A year after the disastrous breach, LastPass has not improved

In September last year, a breach at LastPass’ parent company GoTo (formerly LogMeIn) culminated in attackers siphoning out all data from their servers. The criticism from the security community has been massive. This was not so much because of the breach itself, such things happen, but because of the many obvious ways in which LastPass made matters worse: taking months to notify users, failing to provide useful mitigation instructions, downplaying the severity of the attack, ignoring technical issues which have been publicized years ago and made the attackers’ job much easier. The list goes on.

Now this has been almost a year ago. LastPass promised to improve, both as far as their communication goes and on the technical side of things. So let’s take a look at whether they managed to deliver.

TL;DR: They didn’t. So far I failed to find evidence of any improvements whatsoever.

Update (2023-09-26): It looks like at least the issues listed under “Secure settings” are finally going to be addressed.

A very battered ship with torn sails in a stormy sea, on its side the ship’s name: LastPass

The communication

The initial advisory

LastPass’ initial communication around the breach has been nothing short of a disaster. It happened more than three months after the users’ data was extracted from LastPass servers. Yet rather than taking responsibility and helping affected users, their PR statement was designed to downplay and to shift blame. For example, it talked a lot about LastPass’ secure default settings but failed to mention that LastPass never really enforced those. In fact, people who created their accounts a while ago and used very outdated (insecure) settings never saw as much as a warning.

The statement concluded with “There are no recommended actions that you need to take at this time.” I called this phrase “gross negligence” back when I initially wrote about it, and I still stand by this assessment.

The detailed advisory

It took LastPass another two months of strict radio silence to publish a more detailed advisory. That’s where we finally learned some more about the breach. We also learned that business customers using Federated Login are very much affected by the breach, the previous advisory explicitly denied that.

But even now, we learn that indirectly, in recommendation 9 out of 10 for LastPass’ business customers. It seems that LastPass considered generic stuff like advice on protecting against phishing attacks more important than mitigation of their breach. And then the recommendation didn’t actually say “You are in danger. Rotate K2 ASAP.” Instead, it said “If, based on your security posture or risk tolerance, you decide to rotate the K1 and K2 split knowledge components…” That’s the conclusion of a large pile of text essentially claiming that there is no risk.

At least the advisory for individual users got the priorities right. It was master password first, iterations count after that, and all the generic advice at the end.

Except: they still failed to admit the scope of the breach. The advice was:

Depending on the length and complexity of your master password and iteration count setting, you may want to reset your master password.

And this is just wrong. The breach already happened. Resetting the master password will help protect against future breaches, but it won’t help with the passwords already compromised. This advice should have really been:

Depending on the length and complexity of your master password and iteration count setting, you may want to reset all your passwords.

But this would amount to saying “we screwed up big time.” Which they definitely did. But they still wouldn’t admit it.


A blog post by the LastPass CEO Karin Toubba said:

I acknowledge our customers’ frustration with our inability to communicate more immediately, more clearly, and more comprehensively throughout this event. I accept the criticism and take full responsibility. We have learned a great deal and are committed to communicating more effectively going forward.

As I’ve outlined above, the detailed advisory published simultaneously with this blog post still left a lot to be desired. But this sounds like a commitment to improve. So maybe some better advice has been published in the six months which passed since then?

No, this doesn’t appear to be the case. Instead, the detailed advisory moved to the “Get Started – About LastPass” section of their support page. So it’s now considered generic advice for LastPass users. Any specific advice on mitigating the fallout of the breach, assuming that it isn’t too late already? There doesn’t seem to be any.

The LastPass blog has been publishing lots of articles again, often multiple per week. There doesn’t appear to be any useful information at all here however, only PR. To add insult to injury, LastPass published an article in July titled “How Zero Knowledge Keeps Passwords Safe.” It gives a generic overview of zero knowledge which largely doesn’t apply to LastPass. It concludes with:

For example, zero-knowledge means that no one has access to your master password for LastPass or the data stored in your LastPass vault, except you (not even LastPass).

This is bullshit. That’s not how LastPass has been designed, and I wrote about it five years ago. Other people did as well. LastPass didn’t care, otherwise this breach wouldn’t have been such a disaster.

Secure settings

The issue

LastPass likes to boast how their default settings are perfectly secure. But even assuming that this is true, what about the people who do not use their defaults? For example the people who created their LastPass account a long time ago, back when the defaults were different?

The iterations count is particularly important. Few people have heard about it, it being hidden under “Advanced Settings.” Yet when someone tries to decrypt your passwords, this value is an extremely important factor. A high value makes successful decryption much less likely.

As of 2023, the current default value is 600,000 iterations. Before the breach the default used to be 100,100 iterations, making decryption of passwords six times faster. And before 2018 it was 5,000 iterations. Before 2013 it was 500. And before 2012 the default was 1 iteration.

What happened to all the accounts which were created with the old defaults? It appears that for most of these LastPass failed to fix the settings automatically. People didn’t even receive a warning. So when the breach happened, quite a few users reported having their account configured with 1 iteration, massively weakening the protection provided by the encryption.

It’s the same with the master password. In 2018 LastPass introduced much stricter master password rules, requiring at least 12 characters. While I don’t consider length-based criteria very effective to guide users towards secure passwords, LastPass failed to enforce even this rule for existing accounts. Quite a few people first learned about the new password complexity requirement when reading about the breach.


I originally asked LastPass about enforcing a secure iterations count setting for existing accounts in February 2018. LastPass kept stalling until I published my research without making certain that all users are secure. And they ignored this issue another four years until the breach happened.

And while the breach prompted LastPass to increase the default iterations count, they appear to be still ignoring existing accounts. I just logged into my test account and checked the settings:

Screenshot of LastPass settings. “Password Iterations” setting is set to 5000.

There is no warning whatsoever. Only if I try to change this setting, a message pops up:

For your security, your master password iteration value must meet the LastPass minimum requirement: 600000

But people who are unaware of this setting will not be warned. And while LastPass definitely could update this setting automatically when people log in, they still choose not to do it for some reason.

It’s the same with the master password. The password of my test account is weak because this account has been created years ago. If I try to change it, I will be forced to choose a password that is at least 12 characters long. But as long as I just keep using the same password, LastPass won’t force me to change it – even though it definitely could.

There isn’t even a definitive warning when I log in. There is only this notification in the menu:

Screenshot of the LastPass menu. Security Dashboard has a red dot on its icon.

Only after clicking “Security Dashboard” will a warning message show up:

Screenshot of a LastPass message titled “Master password alert.” The message text says: “Master password strength: Weak (50%). For your protection, change your master password immediately.” Below it a red button titled “Change password.”

If this is such a critical issue that I need to change my master password immediately, why won’t LastPass just tell me to do it when I log in?

This alert message apparently pre-dates the breach, so there don’t seem to be any improvements in this area either.

Update (2023-09-26): Last week LastPass sent out an email to all users:

New master password requirements. LastPass is changing master password requirements for all users: all master passwords must meet a 12-character minimum. If your master password is less than 12-characters, you will be required to update it.

According to this email, LastPass will start enforcing stronger master passwords at some unspecified point in future. Currently, this requirement is still not being enforced, and the email does not list a timeline for this change.

More importantly, when I logged into my LastPass account after receiving this email, the iterations count finally got automatically updated to 600,000. The email does not mention any changes in this area, so it’s unclear whether this change is being applied to all LastPass accounts this time.

Brian Krebs is quoting LastPass CEO with the statement: “We have been able to determine that a small percentage of customers have items in their vaults that are corrupt and when we previously utilized automated scripts designed to re-encrypt vaults when the master password or iteration count is changed, they did not complete.” Quite frankly, I find this explanation rather doubtful.

First of all, reactions to my articles indicate that the percentage of old LastPass accounts which weren’t updated is far from small. There are lots of users finding an outdated iterations count configured in their accounts, yet only two reported their accounts being automatically updated so far.

Second: my test account in particular is unlikely to contain “corrupted items” which previously prevented the update. Back in 2018 I changed the iterations count to 100,000 and back to 5,000 manually. This worked correctly, so no corruption was present at that point. The account was virtually unused after that except for occasional logins, no data changes.

Unencrypted data

The issue

LastPass PR likes to use “secure vault” as a description of LastPass data storage. This implies that all data is secured (encrypted) and cannot be retrieved without the knowledge of the master password. But that’s not the case with LastPass.

LastPass encrypts passwords, user names and a few other pieces of data. Everything else is unencrypted, in particular website addresses and metadata. That’s a very bad idea, as security researchers kept pointing out again and again. In November 2015 (page 67). In January 2017. In July 2018. And there are probably more.

LastPass kept ignoring this issue. So when last year their data leaked, the attackers gained not only encrypted passwords but also plenty of plaintext data. Which LastPass was forced to admit but once again downplayed by claiming website addresses not to be sensitive data. And users were rightfully outraged.


Today I logged into my LastPass account and then opened https://lastpass.com/getaccts.php. This gives you the XML representation of your LastPass data as it is stored on the server. And I fail to see any improvements compared to this data one year ago. I gave LastPass the benefit of the doubt and created a new account record. Still:

<account url="68747470733a2f2f746573742e6465" last_modified="1693940903" >

The data in the url field is merely hex-encoded which can be easily translated back into https://test.de. And the last_modified field is a Unix timestamp, no encryption here either.


A year after the breach, LastPass still hasn’t provided their customers with useful instructions on mitigating the breach, nor has it admitted the full scope of the breach. They also failed to address any of the long-standing issues that security researchers have been warning about for years. At the time of writing, owners of older LastPass accounts still have to become active on their own in order to fix outdated security settings. And much of LastPass data isn’t being encrypted.

I honestly cannot explain LastPass’ denial to fix security settings of existing accounts. Back when I was nagging them about it, they straight out lied to me. Don’t they have any senior engineers on staff, so that nobody can implement this change? Do they really not care as long as they can blame the users for not updating their settings? Beats me.

As to not encrypting all the data, I am starting to suspect that LastPass actually wants visibility into your data. Do they need to know which websites you have accounts on in order to guide some business decisions? Or are they making additional income by selling this data? I don’t know, but LastPass persistently ignoring this issue makes me suspicious.

Either way, it seems that LastPass considers the matter of their breach closed. They published their advisory in March this year, and that’s it. Supposedly, they improved the security of their infrastructure, which nobody can verify of course. There is nothing else coming, no more communication and no technical improvements. Now they will only be publishing more lies about “zero knowledge architecture.”

Packet Storm

Retrieved title: News ≈ Packet Storm, 6 item(s)
Slovakia's Prime Minister Fico Shot After Government Meeting

Cerebral Valley Hackers Build $20 Open Source Smart Glasses

Santander Data Breach Impacts Customers, Employees

Microsoft Warns Of Active Zero Day Exploitation, Patches 60 Windows Vulns

LockBit Ransomware Spread In Millions Of Emails Via Phorpiex Botnet

FCC Names And Shames Royal Tiger AI Robocall Crew