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+
2267function 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