Security advisory: remote-code-execution backdoor in the shared Tales of Pirate (DX9/x64) GameServer source
TL;DR: If your GameServer comes from the "Tales of Pirate 2022 DX9" source (by far the most widely shared server source on this site) or from ANY fork of it, then any logged-in player can run arbitrary Lua, including OS shell commands, on your server host. This affects a huge number of live servers. The patch is one block of code. Details, detection, and fix below.
Who is affected
Let's be direct, because this is one of the most-downloaded server source sets on this forum and a lot of people are running it: the vulnerable code is present in the publicly distributed
Tales of Pirate 2022 DX9 source. To be clear, this is about
where the code is, not who wrote it. It sits in the version distributed via the public repository below, but it may well predate that release (these sources pass through many hands). I'm not attributing authorship to anyone; I'm pointing at the files so people can check and patch. It is present, verbatim, here:
- Tales of Pirate 2022 DX9: github.com/mothannakhzaleh/TalesOfPirateDX9 (branch main), file sources/Server/GameServer/src/CharacterPrl.cpp, the CMD_CM_KITBAGTEMPlocks case (around lines 1133-1137).
- The widely-used x64 continuation: github.com/alexxstst/TalesOfPirate, branch develop (the base most x64 servers build from) is also affected. It is present there in both trees: the legacy sources/Server/GameServer/src/CharacterPrl.cpp (around line 1133) and the x64 src/server/GameServer/Source/CharacterPrl.cpp (around line 1034). (Its main branch does not contain the handler, only the leftover command name; it is the develop branch that ships the backdoor.)
Because that 2022 DX9 source is the base almost everyone forked, the backdoor has been inherited by
every fork and re-release built on it, the x64 continuation above included. If your server descends from the 2022 DX9 source in any way, assume it is present unless you have specifically removed this handler. It is not behind any build flag. If it is in your tree, it is in your binary.
It is
not present in the older 2.0/legacy PKO server sources; this is specific to the modernized DX9 line and its descendants.
What is NOT affected: for comparison, I checked several other widely-shared server source sets (
Spidpex, the
Corsairs Online public source, and
Tales of Insomnia) and they do
not contain this backdoor. Their kitbag handler has only the legitimate
CMD_CM_KITBAGTEMP_SYNC command: no
KITBAGTEMPlocks case, no
7777 gate, no
luaL_dostring in that handler. So this is specific to the 2022 DX9 line and its forks,
not PKO server source in general. (One caveat: those other sources
do still contain the unrelated inter-server
luaL_dostring path described under "Related hardening" below, but that one is not reachable by game clients.)
To be fair: this looks like a
developer "remote Lua console" lefan necessarily a deliberately malicious plant, and (as said above)there's no telling who originally added it. But none of that changes the urgency: the effect is full server compromise, the code is public, and anyone can use it. Whoever wrote it, if it's in your tree, fix it.
What it is
A leftover remote Lua console wired into a normal gameplay pa hardcoded magic number, not by any GM or permission check.
In the GameServer character packet handler (CharacterPrl.cpp), the CMD_CM_KITBAGTEMPlocks case does, in effect: if a client sends this command carrying the value 7777, the server reads the following string from ty as Lua on the main server Lua state.
Two things make this critical:
- No authorization. The only "gate" is the constant 7777, which is sitting in open source. Any authenticated player (a normal account, no GM flag) can trigger it. The official client never even sends this command; it is a pure backdoor, not a feature.
- The Lua state is unrestricted. It is created with luaL_openlibs, so the os and io libraries are loaded. The injected Lua is therefore not limited to game scripting: os.execute(...) gives arbitrary OS command execution on the GameServer host. That host typically also runs your database, and the server process holds its DB credentials, so this is a full server-plus-database compromise: data theft, account/character manipulation, ban and GM escalation, the lot.
There is also a secondary bug in the same block: a missing breakgh into the next case (CMD_CM_STORE_OPEN_ASK), which canmisparse the packet. Fix it at the same time.
Severity: Critical. Network-reachable, low complexity, only host compromise.
Am I vulnerable? (detection)
1. Source check, search your server tree:
Code:
grep -rn "luaL_dostring" src/server/
grep -rn "7777" src/server/GameServer/
grep -rn "KITBAGTEMPlocks" src/
If you find a CMD_CM_KITBAGTEMPlocks case calling luaL_dostring on a packet string, you are vulnerable.
2. Runtime detection, if you cannot rebuild immediately, log it: instrument the luaL_dostring call site to write the calling command and the Lua string to a security log, and alert on any execution. Watch for normal accounts suddenly gaining GM, mass item/gold, or issuing bans.
Fix
- Delete the backdoor. Remove the entire CMD_CM_KITBAGTE_dostring block) from the character packet handler, and add themissing break; so the surrounding switch cannot fall through.
- Remove the command from NetCommand.h if nothing legitimate uses it (the stock client does not).
- Optional defense in depth: neutralise shell/file exec in Lua.ve already closes the client hole, so this is belt-and-suspenders (it mainly limits the inter-server path in the next section). Do NOT blindly strip the os/io libraries: typical PKO server scripts legitimately use os.time/os.date, dofile, io.open and loadstring (shops, timers, serialization), so a full strip will break your server. Instead, after luaL_openlibs, nil out only the shell/file-exec functions your scripts never call, typically os.execute, os.remove, os.rename, os.tmpname, io.popen. Check against your own scripts first.
[*]Rebuild and redeploy all GameServer worlds.
Related hardening (not a client backdoor, but lock it down)
The same dangerous primitive (luaL_dostring on the unrestricted Lua state) also exists on the inter-server channel (CMD_MM_DO_STRING, used by
the GM "lua/luaall" broadcast, and the dynamic-map-entry handler). game clients
: the GateServer only forwards client commands inthe CMD_CM_/CMD_CP_ ranges to the back-end (verified), so they are not an "any player" hole. But they are an RCE if an attacker can reach your internal Group/Game server ports, and that mesh is typically unauthenticated and unencrypted. So:
- Firewall your internal server ports (Group/Gate/Game inter-server) to the server hosts only; never expose them to the internet.
- Consider gating or sandboxing those inter-server dostring paths too, and applying the restricted-libraries fix above so even mesh-side execution cannot reach os/io.
A word on shared source
This is a textbook example of why a server built on widely-shared source cannot rely on secrecy: a "works because nobody looks" backdoor is instantly exploitable the moment many people have the code. If you run a fork, it is worth auditing every client-reachable packet handler for the same class of thing: script-execution sinks, magic-constant "gates," and privileged actions (give item/gold, ban, set-GM) that trust a client-sent flag instead of a server-side GM check.
Responsible disclosure
The flaw is already present in publicly posted source, so this advisory adds detection and a fix rather than new attack capability. Out of courtesy, the author of the original 2022 DX9 release and the maintainers of the major fork alongside this post, so it can be patched at the source foreveryone. No weaponized exploit is published here.
Posted to help fellow operators secure their servers. Verified against the source; patch tested as described. Questions welcome.