Following my recent advisory on Vim vulnerabilities, here goes a followup: many
more vulnerabile statements in Netrw. Although Netrw has been updated with
the new fnameescape() and shellescape() functions, it doesn't use them
consistently. It is difficult *not* to find vulnerable code in Netrw.
This writeup can be found at:
``http://www.rdancer.org/vulnerablevim-netrw.html''
The archive with code that we're using can be found at:
``http://www.rdancer.org/vulnerablevim-netrw.tar.bz2''.
Best results are achieved by running ``make test'' in the root directory of the abovementioned archive:
$ make test
[...]
-------------------------------------------
-------- Test results below ---------------
-------------------------------------------
filetype.vim
tarplugin.updated: VULNERABLE
zipplugin : VULNERABLE
--> netrw.v2 : VULNERABLE
--> netrw.v3 : VULNERABLE
--> netrw.v4 : VULNERABLE
1. Compression and Decompression (The ``mz'' Command)
Invoking the ``mz'' command upon a file with a crafted file name can lead to
arbitrary code execution.
1.1 Vulnerability
In many places, Netrw ($VIMRUNTIME/autoload/netrw.vim) fails to sanitize file
names used as shell arguments.
In function s:NetrwMarkFileExe() (The ``mx'' command): ``apply command to marked
files. Substitute: filename -> % If no %, then append a space and the filename
to the command'':
4036 for fname in s:netrwmarkfilelist_{curbufnr}
4037 if a:islocal
4038 if g:netrw_keepdir
4039 let fname= s:ComposePath(curdir,fname)
4040 endif
4041 else
4042 let fname= b:netrw_curdir.fname
4043 endif
4044 if cmd =~ '%'
4045 let xcmd= substitute(cmd,'%',fname,'g')
4046 else
4047 let xcmd= cmd.' '.fname
4048 endif
4049 if a:islocal
4050 " call Decho("local: xcmd<".xcmd.">")
--> 4051 let ret= system(xcmd)
4052 else
4053 " call Decho("remote: xcmd<".xcmd.">")
--> 4054 let ret= s:RemoteSystem(xcmd)
Following code in function s:NetrwMarkFileCompress() is run when the ``mz''
(compress/decompress) command is invoked. The variable
``s:netrwmarkfilelist_{curbufnr}'' holds the marked files list.:
159 if !exists("g:netrw_decompress")
160 let g:netrw_decompress= { ".gz" : "gunzip" , ".bz2" : "bunzip2" , ".zip" : "unzip" , ".tar" : "tar -xf"}
161 endif
[...]
3816 for fname in s:netrwmarkfilelist_{curbufnr}
3817 " for every filename in the marked list
3818 for sfx in sort(keys(g:netrw_decompress))
3819 if fname =~ '\'.sfx.'$'
3820 " fname has a suffix indicating that its compressed; apply associated decompression routine
3821 let exe= g:netrw_decompress[sfx]
3822 " call Decho("fname<".fname."> is compressed so decompress with <".exe.">")
3823 if a:islocal
3824 if g:netrw_keepdir
3825 let fname= s:ComposePath(curdir,fname)
3826 endif
3827 else
3828 let fname= b:netrw_curdir.fname
3829 endif
3830 if executable(exe)
3831 if a:islocal
--> 3832 call system(exe." ".fname)
1.2. Exploit
We exploit the statement on line 3832.
Run ``make demo'' or ``make test'' in the netrw.v2 directory. Note: ``make
test'' may hang when run from within vim.
2. Copying Files (The ``mc'' Command)
Invoking the ``mc'' command inside a directory with a crafted directory name
can lead to arbitrary code execution.
2.1. Vulnerability
Netrw inappropriately uses shellescape() in many places to sanitize arguments of the
``execute'' command.
708 exe s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".shellescape(uid_machine.":".escape(b:netrw_fname,' ?&;')." ".tmpfile)
810 exe s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".shellescape(g:netrw_machine.":".escape(b:netrw_fname,g:netrw_fname_escape))." ".tmpfile
831 exe s:netrw_silentxfer."!".g:netrw_http_cmd." ".shellescape(tmpfile)." ".shellescape("http://".g:netrw_machine.netrw_fname)
842 exe s:netrw_silentxfer."!".g:netrw_http_cmd." ".shellescape(tmpfile)." ".shellescape("http://".g:netrw_machine.netrw_html)
882 exe s:netrw_silentxfer."!".g:netrw_rsync_cmd." ".shellescape(g:netrw_machine.":".netrw_fname)." ".tmpfile
907 exe s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".tmpfile." ".shellescape(netrw_option."://".g:netrw_uid.':'.s:netrw_passwd.'@'.g:netrw_machine."/".netrw_fname)
910 exe s:netrw_silentxfer."!".g:netrw_fetch_cmd." ".tmpfile." ".shellescape(netrw_option."://".g:netrw_machine."/".netrw_fname)
923 exe s:netrw_silentxfer."!".g:netrw_sftp_cmd." ".shellescape(g:netrw_machine.":".netrw_fname)." ".tmpfile
1084 exe s:netrw_silentxfer."!".g:netrw_rcp_cmd." ".s:netrw_rcpmode." ".shellescape(tmpfile)." ".shellescape(uid_machine.":".netrw_fname)
1177 exe s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".shellescape(tmpfile)." ".shellescape(g:netrw_machine.":".netrw_fname)
2976 exe "silent !".viewer." ".viewopt.shellescape(fname).redir
2981 exe 'silent !start rundll32 url.dll,FileProtocolHandler '.shellescape(fname)
2987 exe "silent !gnome-open ".shellescape(fname).redir
2992 exe "silent !kfmclient exec ".shellescape(fname)." ".redir
2997 exe "silent !open ".shellescape(fname)." ".redir
3656 exe "silent! !".g:netrw_local_mkdir.' '.shellescape(newdirname)
3680 exe "silent! !".mkdircmd." ".shellescape(newdirname)
3911 exe "silent! !".g:netrw_local_mkdir.' '.shellescape(tmpdir)
4775 exe s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".filelist." ".shellescape(tgtdir)
5058 exe s:netrw_silentxfer."!".g:netrw_scp_cmd.useport." ".args." ".shellescape(machine.":".escape(tgt,g:netrw_fname_escape))
6001 exe "silent r! ".listcmd.shellescape(s:path)
6015 exe "silent r! ".listcmd.' "'.shellescape(s:path).'"'
3888 let args= join(map(copy(s:netrwmarkfilelist_{bufnr('%')}),"b:netrw_curdir.\"/\".shellescape(v:val)"))
3889 " call Decho("system(".g:netrw_localcopycmd." ".args." ".shellescape(s:netrwmftgt).")")
--> 3890 call system(g:netrw_localcopycmd." ".args." ".shellescape(s:netrwmftgt))
2.2. Exploit
Run ``make demo'' or ``make test'' in the netrw.v3 directory. Note: ``make
test'' may hang when run from within vim.
2.3. Patch
--- /usr/local/share/vim/vim72a/autoload/netrw.vim 2008-07-01 18:38:09.000000000 +0100
+++ - 2008-07-03 19:01:50.676582822 +0100
@@ -3885,7 +3885,7 @@
if a:islocal && s:netrwmftgt_islocal
" Copy marked files, local directory to local directory
" call Decho("copy from local to local")
- let args= join(map(copy(s:netrwmarkfilelist_{bufnr('%')}),"b:netrw_curdir.\"/\".shellescape(v:val)"))
+ let args= join(map(copy(s:netrwmarkfilelist_{bufnr('%')}),"shellescape(b:netrw_curdir).\"/\".shellescape(v:val)"))
" call Decho("system(".g:netrw_localcopycmd." ".args." ".shellescape(s:netrwmftgt).")")
call system(g:netrw_localcopycmd." ".args." ".shellescape(s:netrwmftgt))
3. Deleting Files (The ``D'' Command)
Applying the ``D'' to a file with a crafted file name, or inside a directory
with a crafted directory name, can lead to arbitrary code execution.
3.1 Vulnerability
Netrw fails to properly sanitize arguments passed to the s:System() function,
which is a wrapper for the ``execute'' command:
7596 fun! s:System(cmd,path)
[...]
7599 let path = a:path
[...]
7615 exe "let result= ".a:cmd."('".path."')"
In function s:NetrwLocalRmFile():
6724 fun! s:NetrwLocalRmFile(path,fname,all)
[...]
6730 let rmfile= s:ComposePath(a:path,a:fname)
[...]
--> 6754 let ret= s:System("delete",rmfile)
[...]
--> 6777 call s:System("system",g:netrw_local_rmdir.' '.shellescape(rmfile))
[...]
--> 6782 let errcode= s:System("delete",rmfile)
[...]
--> 6788 call s:System("system","rm ".shellescape(rmfile))
In function s:NetrwLocalRmFile():
6730 let rmfile= s:ComposePath(a:path,a:fname)
[...]
--> 6754 let ret= s:System("delete",rmfile)
[...]
--> 6777 call s:System("system",g:netrw_local_rmdir.' '.shellescape(rmfile))
6778 " call Decho("v:shell_error=".v:shell_error)
6779
6780 if v:shell_error != 0
6781 " call Decho("2nd attempt to remove directory<".rmfile.">")
--> 6782 let errcode= s:System("delete",rmfile)
6783 " call Decho("errcode=".errcode)
6784
6785 if errcode != 0
6786 if has("unix")
6787 " call Decho("3rd attempt to remove directory<".rmfile.">")
--> 6788 call s:System("system","rm ".shellescape(rmfile))
3.2 Exploit
We exploit the statement on the line 6754. Run ``make demo'' or ``make test''
in the netrw.v4 directory. Note: ``make test'' may hang when run from within
vim. We use the TIOCSTY ioctl to simulate keyboard input in ``make test'' --
avoid touching the keyboard while ``make test'' is running.