Skip to content

Commit 7f0de93

Browse files
committed
Validate and Sanitize the SSH input parameters
1 parent f0e3983 commit 7f0de93

File tree

2 files changed

+385
-6
lines changed

2 files changed

+385
-6
lines changed

drivers/SSH/testconnection.php

Lines changed: 337 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,340 @@
11
<?php
2+
3+
/**
4+
* Validates and sanitizes a file path to prevent command injection
5+
* @param string $path The path to validate
6+
* @return string|false Returns sanitized path or false if invalid
7+
*/
8+
function validate_and_sanitize_key($path) {
9+
if (!is_string($path)) {
10+
return false;
11+
}
12+
13+
$path = trim($path);
14+
15+
// Check for empty path
16+
if (empty($path)) {
17+
return false;
18+
}
19+
20+
// Remove any null bytes
21+
$path = str_replace("\0", "", $path);
22+
23+
// Check for command injection patterns
24+
$dangerous_patterns = array(
25+
'/[`$()]/', // Backticks and command substitution
26+
'/[;&|]/', // Command separators
27+
'/[<>]/', // Redirection operators
28+
'/\\\\s/', // Backslash space combinations
29+
'/\beval\b/i', // eval command
30+
'/\bexec\b/i', // exec command (in path context)
31+
'/\bsh\b/', // shell commands
32+
'/\bbash\b/', // bash commands
33+
'/\\\\x[0-9a-fA-F]/', // Hex encoded characters
34+
'/\bcat\b/i', // cat command
35+
'/\brm\b/i', // rm command
36+
'/\bmkdir\b/i', // mkdir command
37+
'/\bchmod\b/i', // chmod command
38+
'/\bchown\b/i', // chown command
39+
'/\bwget\b/i', // wget command
40+
'/\bcurl\b/i', // curl command
41+
'/\bpython\b/i', // python command
42+
'/\bperl\b/i', // perl command
43+
'/\bphp\b/i', // php command
44+
'/\bnode\b/i', // node command
45+
'/\bjava\b/i', // java command
46+
'/^\/bin\//', // /bin/ directory (but allow /bin in other contexts)
47+
'/^\/usr\/bin\//', // /usr/bin/ directory
48+
'/^\/usr\/sbin\//', // /usr/sbin/ directory
49+
'/^\/sbin\//', // /sbin/ directory
50+
'/^\/var\/www\//', // /var/www/ directory
51+
'/^\/var\/log\//', // /var/log/ directory
52+
'/^\/etc\/passwd$/', // /etc/passwd file
53+
'/^\/etc\/shadow$/', // /etc/shadow file
54+
'/^\/proc\//', // /proc/ directory
55+
'/^\/sys\//', // /sys/ directory
56+
'/^\/dev\//', // /dev/ directory
57+
);
58+
59+
foreach ($dangerous_patterns as $pattern) {
60+
if (preg_match($pattern, $path)) {
61+
return false;
62+
}
63+
}
64+
65+
// Allow only safe characters: alphanumeric, forward slash, underscore, dash, dot, tilde
66+
// Note: This allows both relative and absolute paths
67+
if (!preg_match('/^[a-zA-Z0-9\/\._~-]+$/', $path)) {
68+
return false;
69+
}
70+
71+
// Prevent directory traversal
72+
if (strpos($path, '..') !== false) {
73+
return false;
74+
}
75+
76+
// Allow absolute paths but ensure they don't contain dangerous system directories
77+
if (strpos($path, '/') === 0) {
78+
// Check if the absolute path contains dangerous system directories
79+
$dangerous_abs_paths = array(
80+
'/bin/',
81+
'/usr/bin/',
82+
'/usr/sbin/',
83+
'/sbin/',
84+
'/etc/',
85+
'/var/www/',
86+
'/var/log/',
87+
'/proc/',
88+
'/sys/',
89+
'/dev/',
90+
);
91+
92+
foreach ($dangerous_abs_paths as $dangerous_path) {
93+
if (strpos($path, $dangerous_path) === 0) {
94+
return false;
95+
}
96+
}
97+
}
98+
99+
// Limit path length to prevent buffer overflow attacks
100+
if (strlen($path) > 255) {
101+
return false;
102+
}
103+
104+
// Ensure path doesn't contain multiple consecutive slashes
105+
if (strpos($path, '//') !== false) {
106+
return false;
107+
}
108+
109+
return $path;
110+
}
111+
112+
/**
113+
* Validates and sanitizes host parameter
114+
* @param string $host The host to validate
115+
* @return string|false Returns sanitized host or false if invalid
116+
*/
117+
function validate_and_sanitize_host($host) {
118+
if (!is_string($host)) {
119+
return false;
120+
}
121+
122+
$host = trim($host);
123+
124+
if (empty($host)) {
125+
return false;
126+
}
127+
128+
// Remove any null bytes
129+
$host = str_replace("\0", "", $host);
130+
131+
// Check for command injection patterns
132+
$dangerous_patterns = array(
133+
'/[`$()]/', // Backticks and command substitution
134+
'/[;&|]/', // Command separators
135+
'/[<>]/', // Redirection operators
136+
'/\\\\s/', // Backslash space combinations
137+
'/\beval\b/i', // eval command
138+
'/\bexec\b/i', // exec command
139+
'/\bsh\b/', // shell commands
140+
'/\bbash\b/', // bash commands
141+
);
142+
143+
foreach ($dangerous_patterns as $pattern) {
144+
if (preg_match($pattern, $host)) {
145+
return false;
146+
}
147+
}
148+
149+
// Allow only safe characters for hostnames: alphanumeric, dots, dashes, underscores
150+
if (!preg_match('/^[a-zA-Z0-9\._-]+$/', $host)) {
151+
return false;
152+
}
153+
154+
// Limit host length
155+
if (strlen($host) > 253) {
156+
return false;
157+
}
158+
159+
return $host;
160+
}
161+
162+
/**
163+
* Validates and sanitizes user parameter
164+
* @param string $user The user to validate
165+
* @return string|false Returns sanitized user or false if invalid
166+
*/
167+
function validate_and_sanitize_user($user) {
168+
if (!is_string($user)) {
169+
return false;
170+
}
171+
172+
$user = trim($user);
173+
174+
if (empty($user)) {
175+
return false;
176+
}
177+
178+
// Remove any null bytes
179+
$user = str_replace("\0", "", $user);
180+
181+
// Check for command injection patterns
182+
$dangerous_patterns = array(
183+
'/[`$()]/', // Backticks and command substitution
184+
'/[;&|]/', // Command separators
185+
'/[<>]/', // Redirection operators
186+
'/\\\\s/', // Backslash space combinations
187+
'/\beval\b/i', // eval command
188+
'/\bexec\b/i', // exec command
189+
'/\bsh\b/', // shell commands
190+
'/\bbash\b/', // bash commands
191+
);
192+
193+
foreach ($dangerous_patterns as $pattern) {
194+
if (preg_match($pattern, $user)) {
195+
return false;
196+
}
197+
}
198+
199+
// Allow only safe characters for usernames: alphanumeric, underscores, dashes
200+
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $user)) {
201+
return false;
202+
}
203+
204+
// Limit user length
205+
if (strlen($user) > 32) {
206+
return false;
207+
}
208+
209+
return $user;
210+
}
211+
212+
/**
213+
* Validates and sanitizes path parameter
214+
* @param string $path The path to validate
215+
* @return string|false Returns sanitized path or false if invalid
216+
*/
217+
function validate_and_sanitize_path($path) {
218+
if (!is_string($path)) {
219+
return false;
220+
}
221+
222+
$path = trim($path);
223+
224+
if (empty($path)) {
225+
return false;
226+
}
227+
228+
// Remove any null bytes
229+
$path = str_replace("\0", "", $path);
230+
231+
// Check for command injection patterns
232+
$dangerous_patterns = array(
233+
'/[`$()]/', // Backticks and command substitution
234+
'/[;&|]/', // Command separators
235+
'/[<>]/', // Redirection operators
236+
'/\\\\s/', // Backslash space combinations
237+
'/\beval\b/i', // eval command
238+
'/\bexec\b/i', // exec command
239+
'/\bsh\b/', // shell commands
240+
'/\bbash\b/', // bash commands
241+
);
242+
243+
foreach ($dangerous_patterns as $pattern) {
244+
if (preg_match($pattern, $path)) {
245+
return false;
246+
}
247+
}
248+
249+
// Allow only safe characters for paths: alphanumeric, forward slash, underscore, dash, dot
250+
if (!preg_match('/^[a-zA-Z0-9\/\._-]+$/', $path)) {
251+
return false;
252+
}
253+
254+
// Prevent directory traversal
255+
if (strpos($path, '..') !== false) {
256+
return false;
257+
}
258+
259+
// Limit path length
260+
if (strlen($path) > 255) {
261+
return false;
262+
}
263+
264+
return $path;
265+
}
266+
2267
function check_ssh_connect($host, $port, $user, $key, $path) {
3-
$keypath = dirname($key);
268+
// Validate all input parameters
269+
$host = validate_and_sanitize_host($host);
270+
if (!$host) {
271+
return "Invalid host";
272+
}
273+
274+
$user = validate_and_sanitize_user($user);
275+
if (!$user) {
276+
return "Invalid user";
277+
}
278+
279+
$key = validate_and_sanitize_key($key);
280+
if (!$key) {
281+
return "Invalid key";
282+
}
283+
284+
$path = validate_and_sanitize_path($path);
285+
if (!$path) {
286+
return "Invalid path";
287+
}
288+
289+
// Validate port
290+
if (!is_numeric($port) || $port < 1 || $port > 65535) {
291+
return "Invalid port";
292+
}
293+
294+
$keypath = dirname($key);
4295
$publickey = "$key.pub";
296+
297+
// Use PHP's mkdir instead of exec for better security
5298
if(!is_dir($keypath)) {
6-
exec("mkdir -p $keypath");
299+
if (!mkdir($keypath, 0755, true)) {
300+
return "Failed to create key directory";
301+
}
7302
}
303+
304+
// Use PHP's file operations instead of exec for better security
8305
if(!file_exists($key)) {
9-
exec("ssh-keygen -t ecdsa -b 521 -f $key -N \"\" && chown asterisk:asterisk $key && chmod 600 $key");
306+
// Generate SSH key using PHP's openssl functions or safe exec with proper escaping
307+
$escaped_key = escapeshellarg($key);
308+
$escaped_keypath = escapeshellarg($keypath);
309+
310+
// Create the key using ssh-keygen with proper escaping
311+
$cmd = "ssh-keygen -t ecdsa -b 521 -f $escaped_key -N '' 2>/dev/null";
312+
exec($cmd, $output, $return_code);
313+
314+
if ($return_code !== 0) {
315+
return "Failed to generate SSH key";
316+
}
317+
318+
// Set proper ownership and permissions
319+
if (function_exists('chown') && function_exists('chmod')) {
320+
chown($key, 'asterisk');
321+
chmod($key, 0600);
322+
} else {
323+
// Fallback to exec with proper escaping
324+
$cmd = "chown asterisk:asterisk $escaped_key && chmod 600 $escaped_key 2>/dev/null";
325+
exec($cmd, $output, $return_code);
326+
}
10327
}
328+
11329
if(!file_exists($publickey)) {
12-
exec("ssh-keygen -y -f $key > $publickey");
330+
$escaped_key = escapeshellarg($key);
331+
$escaped_publickey = escapeshellarg($publickey);
332+
$cmd = "ssh-keygen -y -f $escaped_key > $escaped_publickey 2>/dev/null";
333+
exec($cmd, $output, $return_code);
334+
335+
if ($return_code !== 0) {
336+
return "Failed to generate public key";
337+
}
13338
}
14339
$connection = @ssh2_connect($host, $port);
15340
if(!$connection) {
@@ -21,7 +346,9 @@ function check_ssh_connect($host, $port, $user, $key, $path) {
21346
return "Login failed";
22347
}
23348
else {
24-
$stream = ssh2_exec($connection,"cd $path");
349+
// Use proper escaping for the path in SSH commands
350+
$escaped_path = escapeshellarg($path);
351+
$stream = ssh2_exec($connection, "cd $escaped_path");
25352
$errorStream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
26353
stream_set_blocking($errorStream, true);
27354
stream_set_blocking($stream, true);
@@ -35,13 +362,17 @@ function check_ssh_connect($host, $port, $user, $key, $path) {
35362
$file = "/tmp/freepbx_test$now.txt";
36363
file_put_contents($file, "FreePBX Filestore Test");
37364
$filename = basename($file);
365+
$escaped_filename = escapeshellarg($filename);
366+
$escaped_full_path = escapeshellarg("$path/$filename");
367+
38368
if(!@ssh2_scp_send($connection, "$file", "$path/$filename", 0644)) {
39369
@ssh2_disconnect($connection);
40370
unlink($file);
41371
return "Write failed";
42372
}
43373
else {
44-
$stream = ssh2_exec($connection,"rm $path/$file");
374+
// Use proper escaping for the rm command
375+
$stream = ssh2_exec($connection, "rm $escaped_full_path");
45376
unlink($file);
46377
return "OK";
47378
}

0 commit comments

Comments
 (0)