mirror of
https://github.com/neovim/neovim.git
synced 2026-05-24 05:40:08 +00:00
vim-patch:9.2.0383: [security]: runtime(netrw): shell-injection via sftp: and file: URLs
Problem: runtime(netrw): shell-injection via sftp: and file: URLs
(Joshua Rogers)
Solution: Escape temporary file names, harden filename suffix regex,
drop unused g:netrw_tmpfile_escape variable
Supported by AI
405e2fb6d5
Co-authored-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
18
runtime/pack/dist/opt/netrw/autoload/netrw.vim
vendored
18
runtime/pack/dist/opt/netrw/autoload/netrw.vim
vendored
@@ -26,6 +26,8 @@
|
||||
" 2026 Apr 05 by Vim Project Fix netrw#RFC2396() #19913
|
||||
" 2026 Apr 15 by Vim Project Add missing escape()
|
||||
" 2026 Apr 19 by Vim Project expand ~ on Windows #20003
|
||||
" 2026 Apr 21 by Vim Project fix shell-injection via tempfile suffix (sftp://, file://)
|
||||
" 2026 Apr 21 by Vim Project drop unused g:netrw_tmpfile_escape
|
||||
" 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
|
||||
@@ -400,7 +402,6 @@ else
|
||||
call s:NetrwInit("g:netrw_glob_escape",'*[]?`{~$\')
|
||||
endif
|
||||
call s:NetrwInit("g:netrw_menu_escape",'.&? \')
|
||||
call s:NetrwInit("g:netrw_tmpfile_escape",' &;')
|
||||
call s:NetrwInit("s:netrw_map_escape","<|\n\r\\\<C-V>\"")
|
||||
if has("gui_running") && (&enc == 'utf-8' || &enc == 'utf-16' || &enc == 'ucs-4')
|
||||
let s:treedepthstring= "│ "
|
||||
@@ -1821,14 +1822,14 @@ function netrw#NetRead(mode,...)
|
||||
".........................................
|
||||
" NetRead: (sftp) NetRead Method #9 {{{3
|
||||
elseif b:netrw_method == 9
|
||||
call netrw#os#Execute(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".netrw#os#Escape(g:netrw_machine.":".b:netrw_fname,1)." ".tmpfile)
|
||||
call netrw#os#Execute(s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".netrw#os#Escape(g:netrw_machine.":".b:netrw_fname,1)." ".netrw#os#Escape(tmpfile,1))
|
||||
let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
|
||||
let b:netrw_lastfile = choice
|
||||
|
||||
".........................................
|
||||
" NetRead: (file) NetRead Method #10 {{{3
|
||||
elseif b:netrw_method == 10 && exists("g:netrw_file_cmd")
|
||||
call netrw#os#Execute(s:netrw_silentxfer."!".g:netrw_file_cmd." ".netrw#os#Escape(b:netrw_fname,1)." ".tmpfile)
|
||||
call netrw#os#Execute(s:netrw_silentxfer."!".g:netrw_file_cmd." ".netrw#os#Escape(b:netrw_fname,1)." ".netrw#os#Escape(tmpfile,1))
|
||||
let result = s:NetrwGetFile(readcmd, tmpfile, b:netrw_method)
|
||||
let b:netrw_lastfile = choice
|
||||
|
||||
@@ -8969,14 +8970,17 @@ function s:GetTempfile(fname)
|
||||
endif
|
||||
|
||||
" use fname's suffix for the temporary file
|
||||
" Restrict the suffix to word characters so shell metacharacters in a
|
||||
" remote filename (e.g. sftp://host/foo.txt;id) cannot ride along into
|
||||
" the tempfile name and out into a downstream shell command.
|
||||
if a:fname != ""
|
||||
if a:fname =~ '\.[^./]\+$'
|
||||
if a:fname =~ '\.\w\+$'
|
||||
if a:fname =~ '\.tar\.gz$' || a:fname =~ '\.tar\.bz2$' || a:fname =~ '\.tar\.xz$'
|
||||
let suffix = ".tar".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e')
|
||||
let suffix = ".tar".substitute(a:fname,'^.*\(\.\w\+\)$','\1','e')
|
||||
elseif a:fname =~ '.txz$'
|
||||
let suffix = ".txz".substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e')
|
||||
let suffix = ".txz".substitute(a:fname,'^.*\(\.\w\+\)$','\1','e')
|
||||
else
|
||||
let suffix = substitute(a:fname,'^.*\(\.[^./]\+\)$','\1','e')
|
||||
let suffix = substitute(a:fname,'^.*\(\.\w\+\)$','\1','e')
|
||||
endif
|
||||
let tmpfile= substitute(tmpfile,'\.tmp$','','e')
|
||||
let tmpfile .= suffix
|
||||
|
||||
4
runtime/pack/dist/opt/netrw/doc/netrw.txt
vendored
4
runtime/pack/dist/opt/netrw/doc/netrw.txt
vendored
@@ -2851,10 +2851,6 @@ your browsing preferences. (see also: |netrw-settings|)
|
||||
such as listing, file removal, etc.
|
||||
default: ssh
|
||||
|
||||
*g:netrw_tmpfile_escape* =' &;'
|
||||
escape() is applied to all temporary files
|
||||
to escape these characters.
|
||||
|
||||
*g:netrw_timefmt* specify format string to vim's strftime().
|
||||
The default, "%c", is "the preferred date
|
||||
and time representation for the current
|
||||
|
||||
@@ -611,6 +611,36 @@ func Test_netrw_FileUrlEdit_pipe_injection()
|
||||
call assert_false(filereadable(fname), 'Command injection via pipe in file URL')
|
||||
endfunc
|
||||
|
||||
" The remote filename after '.' was allowed to contain shell metacharacters
|
||||
" and rode unescaped into the tempfile name passed to sftp/file_cmd, giving a
|
||||
" shell injection on :e sftp://host/foo.txt;<cmd>.
|
||||
func Test_netrw_tempfile_suffix_injection()
|
||||
CheckUnix
|
||||
CheckExecutable id
|
||||
let save_sftp = g:netrw_sftp_cmd
|
||||
let save_file = exists('g:netrw_file_cmd') ? g:netrw_file_cmd : v:null
|
||||
let g:netrw_sftp_cmd = 'true'
|
||||
let g:netrw_file_cmd = 'true'
|
||||
let fname = 'Xrce_marker'
|
||||
try
|
||||
call delete(fname)
|
||||
sil! call netrw#NetRead(2, 'sftp://localhost/foo.txt;id>'..fname)
|
||||
call assert_false(filereadable(fname), 'Command injection via sftp:// tempfile suffix')
|
||||
|
||||
call delete(fname)
|
||||
sil! call netrw#NetRead(2, 'file://localhost/foo.txt;id>'..fname)
|
||||
call assert_false(filereadable(fname), 'Command injection via file:// tempfile suffix')
|
||||
finally
|
||||
call delete(fname)
|
||||
let g:netrw_sftp_cmd = save_sftp
|
||||
if save_file is v:null
|
||||
unlet! g:netrw_file_cmd
|
||||
else
|
||||
let g:netrw_file_cmd = save_file
|
||||
endif
|
||||
endtry
|
||||
endfunc
|
||||
|
||||
func Test_netrw_RFC2396()
|
||||
let fname = 'a%20b'
|
||||
call assert_equal('a b', netrw#RFC2396(fname))
|
||||
|
||||
Reference in New Issue
Block a user