reject hostname option injection via bracketed mount source
A source like [-oProxyCommand=CMD]:/path passes the bracket-parsing check in find_base_path() and ends up as -oProxyCommand=CMD in the ssh argv. When sftp_server is a path, ssh gets a destination argument and executes the injected ProxyCommand before connecting. Reject hostnames starting with - after bracket stripping, and add -- before the hostname in the ssh command line so positional args can't be misread as options.
This commit is contained in:
@@ -4019,6 +4019,11 @@ static char *find_base_path(void)
|
||||
*d++ = '\0';
|
||||
s++;
|
||||
|
||||
if (sshfs.host[0] == '-') {
|
||||
fprintf(stderr, "invalid hostname '%s'\n", sshfs.host);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -4410,7 +4415,6 @@ int main(int argc, char *argv[])
|
||||
tmp = g_strdup_printf("-%i", sshfs.ssh_ver);
|
||||
ssh_add_arg(tmp);
|
||||
g_free(tmp);
|
||||
ssh_add_arg(sshfs.host);
|
||||
if (sshfs.sftp_server)
|
||||
sftp_server = sshfs.sftp_server;
|
||||
else if (sshfs.ssh_ver == 1)
|
||||
@@ -4421,6 +4425,8 @@ int main(int argc, char *argv[])
|
||||
if (sshfs.ssh_ver != 1 && strchr(sftp_server, '/') == NULL)
|
||||
ssh_add_arg("-s");
|
||||
|
||||
ssh_add_arg("--");
|
||||
ssh_add_arg(sshfs.host);
|
||||
ssh_add_arg(sftp_server);
|
||||
free(sshfs.sftp_server);
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
test_scripts = [ 'conftest.py', 'pytest.ini', 'test_sshfs.py',
|
||||
'util.py' ]
|
||||
'test_hostname_validation.py', 'util.py' ]
|
||||
custom_target('test_scripts', input: test_scripts,
|
||||
output: test_scripts, build_by_default: true,
|
||||
command: ['cp', '-fPp',
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Tests for hostname validation — no FUSE mount required."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
sys.exit(pytest.main([__file__] + sys.argv[1:]))
|
||||
|
||||
import subprocess
|
||||
from util import base_cmdline, basename
|
||||
from os.path import join as pjoin
|
||||
|
||||
|
||||
def test_reject_option_injection_in_hostname(tmpdir):
|
||||
"""Bracketed source that resolves to a dash-prefixed host must be rejected."""
|
||||
|
||||
mnt_dir = str(tmpdir.mkdir("mnt"))
|
||||
malicious = "[-oProxyCommand=echo pwned]:/path"
|
||||
|
||||
cmdline = base_cmdline + [
|
||||
pjoin(basename, "sshfs"),
|
||||
"-f",
|
||||
malicious,
|
||||
mnt_dir,
|
||||
]
|
||||
res = subprocess.run(
|
||||
cmdline,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=10,
|
||||
text=True,
|
||||
)
|
||||
assert res.returncode != 0
|
||||
assert "invalid hostname" in res.stderr
|
||||
|
||||
|
||||
def test_reject_dash_host_after_doubledash(tmpdir):
|
||||
"""Non-bracketed dash-prefixed source after -- must also be rejected."""
|
||||
|
||||
mnt_dir = str(tmpdir.mkdir("mnt"))
|
||||
|
||||
cmdline = base_cmdline + [
|
||||
pjoin(basename, "sshfs"),
|
||||
"-f",
|
||||
"--",
|
||||
"-oProxyCommand=echo pwned:/path",
|
||||
mnt_dir,
|
||||
]
|
||||
res = subprocess.run(
|
||||
cmdline,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=10,
|
||||
text=True,
|
||||
)
|
||||
assert res.returncode != 0
|
||||
assert "invalid hostname" in res.stderr
|
||||
Reference in New Issue
Block a user