Skip to content

Bug: OS Command Injection in Monitor ping_check Function Allows Authenticated RCE on Spug Server #741

Description

@geo-chen

Summary

The /monitor/run_test/ API endpoint in spug v3.4.0 passes a user-supplied monitor address directly into a shell command without sanitization. When the monitor type is 5 (ping), the value is interpolated into ping -c 1 -W 3 {addr} and executed with shell=True. An authenticated user holding the monitor.monitor.add or monitor.monitor.edit permission can inject shell metacharacters to execute arbitrary OS commands on the spug server, bypassing all host-level access controls.

Technical detail

Vulnerable function (spug_api/apps/monitor/executors.py):

def ping_check(addr):
    if platform.system().lower() == 'windows':
        command = f'ping -n 1 -w 3000 {addr}'
    else:
        command = f'ping -c 1 -W 3 {addr}'
    task = subprocess.run(command, shell=True, stdout=subprocess.PIPE)
    if task.returncode == 0:
        return True, 'Ping检测正常'
    else:
        return False, 'Ping检测失败'

addr is directly interpolated into the shell command string with no escaping or validation.

Call path (spug_api/apps/monitor/views.py, line 97–107):

@auth('monitor.monitor.add|monitor.monitor.edit')
def run_test(request):
    form, error = JsonParser(
        Argument('type', help='请选择监控类型'),
        Argument('targets', type=list, filter=lambda x: len(x), help='请输入监控地址'),
        Argument('extra', required=False)
    ).parse(request.body)
    if error is None:
        is_success, message = dispatch(form.type, form.targets[0], form.extra)
        return json_response({'is_success': is_success, 'message': message})

form.targets[0] comes directly from the JSON request body. When form.type == '5', dispatch calls ping_check(form.targets[0]) — no validation occurs between the request and the shell invocation.

The monitor.monitor.add permission is routinely granted to operations staff who need to configure health checks, as opposed to the admin superuser role. This means a non-administrator authenticated user with this standard operational permission can achieve OS-level code execution on the spug management server.

Proof of Concept

(available upon request)

Impact

An authenticated user with the monitor.monitor.add or monitor.monitor.edit permission — a common operational permission not restricted to administrators — can execute arbitrary OS commands as the spug process user. The spug server holds:

  • SSH private keys for all managed servers (in the database and AppSetting)
  • Database credentials and application secrets
  • Full access to all deployed application configurations

Exploitation escalates privilege from "operations staff with monitor permissions" to "full control of the spug management server and all servers it manages."

Remediation

Replace the shell-based ping with a safe equivalent that does not use shell=True:

import subprocess, platform, ipaddress, socket

def ping_check(addr):
    # Validate addr is a hostname or IP before constructing the command
    args = ['-n', '1', '-w', '3000'] if platform.system().lower() == 'windows' else ['-c', '1', '-W', '3']
    try:
        task = subprocess.run(
            ['ping'] + args + [addr],  # no shell=True; each arg is a separate element
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            timeout=10
        )
        return task.returncode == 0, 'Ping检测正常' if task.returncode == 0 else 'Ping检测失败'
    except Exception as e:
        return False, f'异常信息:{e}'

Passing addr as a list element to subprocess.run (without shell=True) prevents shell interpretation entirely, regardless of the content of addr.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions