diff options
| author | zeertzjq <zeertzjq@outlook.com> | 2026-03-02 09:14:42 +0800 |
|---|---|---|
| committer | zeertzjq <zeertzjq@outlook.com> | 2026-04-23 12:51:11 +0800 |
| commit | ab41543f8e35f1345db5c4698f291c9b07980f9c (patch) | |
| tree | d608dbbe436906d4b3446baf96e499edd4c05444 | |
| parent | 84cafb9c97fa4678b45acdc56c8b2f95231fc3e7 (diff) | |
vim-patch:9.2.0073: [security]: possible command injection using netrw
Problem: [security]: Insufficient validation of hostname and port in
netrw URIs allows command injection via shell metacharacters
(ehdgks0627, un3xploitable).
Solution: Implement stricter RFC1123 hostname and IP validation.
Use shellescape() for the provided hostname and port.
Github Advisory:
https://github.com/vim/vim/security/advisories/GHSA-m3xh-9434-g336
https://github.com/vim/vim/commit/79348dbbc09332130f4c86045e1541d68514fcc1
Co-authored-by: Christian Brabandt <cb@256bit.org>
| -rw-r--r-- | runtime/pack/dist/opt/netrw/autoload/netrw.vim | 34 | ||||
| -rw-r--r-- | runtime/pack/dist/opt/netrw/autoload/netrw/msg.vim | 2 | ||||
| -rw-r--r-- | test/old/testdir/test_plugin_netrw.vim | 8 |
3 files changed, 32 insertions, 12 deletions
diff --git a/runtime/pack/dist/opt/netrw/autoload/netrw.vim b/runtime/pack/dist/opt/netrw/autoload/netrw.vim index 670de1cc15..c13c167dcd 100644 --- a/runtime/pack/dist/opt/netrw/autoload/netrw.vim +++ b/runtime/pack/dist/opt/netrw/autoload/netrw.vim @@ -20,6 +20,7 @@ " 2026 Jan 19 by Vim Project do not create swapfiles #18854 " 2026 Feb 15 by Vim Project fix global variable initialization for MS-Windows #19287 " 2026 Feb 21 by Vim Project better absolute path detection on MS-Windows #19477 +" 2026 Feb 27 by Vim Project Make the hostname validation more strict " Copyright: Copyright (C) 2016 Charles E. Campbell {{{1 " Permission is hereby granted to use and distribute this code, " with or without modifications, provided that this copyright @@ -2591,13 +2592,26 @@ endfunction " s:NetrwValidateHostname: Validate that the hostname is valid {{{2 " Input: -" hostname +" hostname, may include an optional username, e.g. user@hostname +" allow a alphanumeric hostname or an IPv(4/6) address " Output: " true if g:netrw_machine is valid according to RFC1123 #Section 2 function s:NetrwValidateHostname(hostname) - " RFC1123#section-2 mandates, a valid hostname starts with letters or digits - " so reject everyhing else - return a:hostname =~? '^[a-z0-9]' + " Username: + let user_pat = '\%([a-zA-Z0-9._-]\+@\)\?' + " Hostname: 1-64 chars, alphanumeric/dots/hyphens. + " No underscores. No leading/trailing dots/hyphens. + let host_pat = '[a-zA-Z0-9]\%([-a-zA-Z0-9.]{,62}[a-zA-Z0-9]\)\?$' + + " IPv4: 1-3 digits separated by dots + let ipv4_pat = '\%(\d\{1,3}\.\)\{3\}\d\{1,3\}$' + + " IPv6: Hex, colons, and optional brackets + let ipv6_pat = '\[\?\%([a-fA-F0-9:]\{2,}\)\+\]\?$' + + return a:hostname =~? '^'.user_pat.host_pat || + \ a:hostname =~? '^'.user_pat.ipv4_pat || + \ a:hostname =~? '^'.user_pat.ipv6_pat endfunction " NetUserPass: set username and password for subsequent ftp transfer {{{2 @@ -8965,15 +8979,15 @@ endfunction " s:MakeSshCmd: transforms input command using USEPORT HOSTNAME into {{{2 " a correct command for use with a system() call function s:MakeSshCmd(sshcmd) - if s:user == "" - let sshcmd = substitute(a:sshcmd,'\<HOSTNAME\>',s:machine,'') - else - let sshcmd = substitute(a:sshcmd,'\<HOSTNAME\>',s:user."@".s:machine,'') + let machine = shellescape(s:machine, 1) + if s:user != '' + let machine = shellescape(s:user, 1).'@'.machine endif + let sshcmd = substitute(a:sshcmd,'\<HOSTNAME\>',machine,'') if exists("g:netrw_port") && g:netrw_port != "" - let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.g:netrw_port,'') + let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.shellescape(g:netrw_port,1),'') elseif exists("s:port") && s:port != "" - let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.s:port,'') + let sshcmd= substitute(sshcmd,"USEPORT",g:netrw_sshport.' '.shellescape(s:port,1),'') else let sshcmd= substitute(sshcmd,"USEPORT ",'','') endif diff --git a/runtime/pack/dist/opt/netrw/autoload/netrw/msg.vim b/runtime/pack/dist/opt/netrw/autoload/netrw/msg.vim index 5f8c13a8d0..f6c21d173c 100644 --- a/runtime/pack/dist/opt/netrw/autoload/netrw/msg.vim +++ b/runtime/pack/dist/opt/netrw/autoload/netrw/msg.vim @@ -32,7 +32,7 @@ endfunction " netrw#msg#Notify('ERROR'|'WARNING'|'NOTE', ["message1","message2",...]) " (this function can optionally take a list of messages) function! netrw#msg#Notify(level, msg) - if has('nvim') + if has('nvim') && !v:testing " Convert string to corresponding vim.log.level value if a:level ==# 'ERROR' let level = 4 diff --git a/test/old/testdir/test_plugin_netrw.vim b/test/old/testdir/test_plugin_netrw.vim index de52692f81..e8eaf64c41 100644 --- a/test/old/testdir/test_plugin_netrw.vim +++ b/test/old/testdir/test_plugin_netrw.vim @@ -298,7 +298,7 @@ func Test_netrw_parse_special_char_user() call assert_equal(result.path, 'test.txt') endfunction -func Test_netrw_wipe_empty_buffer_fastpath() +func Test_netrw_empty_buffer_fastpath_wipe() " SetUp() may have opened some buffers let previous = bufnr('$') let g:netrw_fastbrowse=0 @@ -567,4 +567,10 @@ func Test_netrw_filemove_pwsh() call s:test_netrw_filemove() endfunc +func Test_netrw_reject_evil_hostname() + let msg = execute(':e scp://x;touch RCE;x/dir/') + let msg = split(msg, "\n")[-1] + call assert_match('Rejecting invalid hostname', msg) +endfunction + " vim:ts=8 sts=2 sw=2 et |
