mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
A compute's hostname and login go unquoted into the libvirt connection URI (qemu+ssh://login@host/system, qemu+tcp://host/system, qemu+tls://...) and, for an SSH compute, into the ssh command line of the DRBD status view. They were barely validated: validate_hostname used re.match with a negated class, so it caught only a leading special character, and login was never checked. A hostname like "192.0.2.1/system?command=/path" injected a libvirt URI parameter (verified: connecting ran the named command through the ssh wrapper), and TLS/SSH parameters such as no_verify, pkipath, socket or netcat could be added the same way; a login like "-oProxyCommand=..." became an ssh option. The DRF ComputeSerializer applied neither check, and validate_name tested a string literal instead of its argument, so it never raised. validate_hostname now accepts only an IPv4 address or a host name with fullmatch (no scheme, port, path, query, whitespace or IPv6 literal; IPv6 is left out until the URI building brackets it). validate_login accepts an empty value (TCP falls back to the server's authentication, the socket transport ignores the login) or a plain name with no leading "-". Both sit on the Compute model fields, so the forms and the DRF serializer (which copies model validators onto its fields) reject the values; a migration records the metadata. validate_name checks its argument. drbd_status validates the login and hostname before building login@host and ends ssh option parsing with "--"; an existing or bypassed bad row is skipped rather than run. Tests: hostname/login validators accept real values and reject URI and option injection; the SSH form and the serializer (create, and an empty TCP login) enforce them; drbd_status skips an injecting target. Checked the real test computes still validate. The new tests fail on the previous code.
53 lines
1.9 KiB
Python
53 lines
1.9 KiB
Python
"""A compute's hostname and login go unquoted into libvirt connection URIs
|
|
and an ssh command line; they are validated so neither can inject a URI
|
|
parameter (e.g. ?command=, ?no_verify=) or an ssh option."""
|
|
|
|
import unittest
|
|
|
|
from django.core.exceptions import ValidationError
|
|
|
|
from computes.validators import validate_hostname, validate_login, validate_name
|
|
|
|
|
|
class HostnameValidatorTestCase(unittest.TestCase):
|
|
def test_accepts_ipv4_and_host_names(self):
|
|
for value in ("192.0.2.10", "localhost", "host.example.com", "a-b.example.", "host1"):
|
|
with self.subTest(value=value):
|
|
validate_hostname(value)
|
|
|
|
def test_rejects_uri_and_option_injection(self):
|
|
for value in (
|
|
"-x",
|
|
"a b;c",
|
|
"host:2222",
|
|
"192.0.2.1/system?command=/evil.sh&a=",
|
|
"host/system?no_verify=1",
|
|
"qemu+ssh://host/system",
|
|
"2001:db8::1",
|
|
"",
|
|
"host name",
|
|
):
|
|
with self.subTest(value=value):
|
|
with self.assertRaises(ValidationError):
|
|
validate_hostname(value)
|
|
|
|
|
|
class LoginValidatorTestCase(unittest.TestCase):
|
|
def test_accepts_plain_logins_and_empty(self):
|
|
# empty: TCP falls back to server auth, the socket transport ignores it
|
|
for value in ("", "root", "libvirt.user", "user-1", "_svc"):
|
|
with self.subTest(value=value):
|
|
validate_login(value)
|
|
|
|
def test_rejects_option_and_delimiter_injection(self):
|
|
for value in ("-oProxyCommand=x", "u@realm", "a/b", "a b", "a\\b", "a%b"):
|
|
with self.subTest(value=value):
|
|
with self.assertRaises(ValidationError):
|
|
validate_login(value)
|
|
|
|
|
|
class NameValidatorTestCase(unittest.TestCase):
|
|
def test_checks_the_value(self):
|
|
validate_name("good-name_1.2")
|
|
with self.assertRaises(ValidationError):
|
|
validate_name("bad name!")
|