Introduction
Many cybersecurity experts tend to rely on command-line tools despite the existence of graphical alternatives. In this article, we will analyze the behavior of a plugin for VIM when it comes to saving a payload for XXE (XML eXternal Entity).
VIM and its plugins
If you choose VIM editor, you will surely rely on some plugins to improve its appearance or add features. Two good examples of plugin managers for VIM are Vundle or Pathogen.
Personally, when I configured VIM at the time, I chose Vundle. I don't use VIM as my main editor, so I kept a rather simple configuration:
~/.vimrcset rtp+=~/.vim/bundle/Vundle.vim call vundle#begin() " let Vundle manage Vundle, required Plugin 'VundleVim/Vundle.vim' Plugin 'scrooloose/nerdtree' " filesystem Plugin 'scrooloose/syntastic' " syntax checker Plugin 'vim-airline/vim-airline' " status/tabline Plugin 'vim-airline/vim-airline-themes' " themes for airline Plugin 'majutsushi/tagbar' " ctags Plugin 'yggdroot/indentline' " Indentation lines Plugin 'junegunn/fzf' " fzf for vim Plugin 'sjl/badwolf' " color scheme Plugin 'airblade/vim-gitgutter' " git diff in sign column Plugin 'ryanoasis/vim-devicons' " file type icons " All of your Plugins must be added before the following line call vundle#end() " required
So, why this preamble? We will get to it soon.
XML eXternal Entity (XXE)
While performing a penetration test we are hired for, we find a potential XXE and decide to test the possibility to do external HTTP requests with the following payload:
test.xml<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE r [ <!ELEMENT r ANY > <!ENTITY % sp SYSTEM "http://10.10.24.1:8000/external.txt"> %sp; ]>
We use nc -nlvp 8000
to catch any requests to port 8000 and launch VIM to write down the XXE payload. As soon as we save the document, even before launching our attack, we receive an HTTP request from our own IP address, requesting right the source stated in the XML file:
Connection from 10.10.24.1:46940
GET /external.txt HTTP/1.0
Host: 10.10.24.1:8000
Accept-Encoding: gzip
So, something has decoded our file and run the query, but what? It is unlikely to be VIM itself, so it must have been one of its plugins.
Analysis
Among the installed plugins, the only ones involved in this specific case are Syntastic (for file syntax analysis) and Indentline (for code indentation). We discard the second option straight away and focus on Syntastic.
Analyzing the plugin code to understand the reason behind this behavior, we notice that in the "syntastic/syntax_checkers/xml/" folder there are two files: plutil.vim and xmllint.vim. This means that the plugin invokes some external executables to run the syntax analysis. To understand which one of the two files is being used, we keep analyzing the code until we reach this extract of the registry.vim file:
plugin/syntastic/registry.vimif exists('g:loaded_syntastic_registry') || !exists('g:loaded_syntastic_plugin') finish endif let g:loaded_syntastic_registry = 1 " Initialisation {{{1 let s:_DEFAULT_CHECKERS = { " ... \ 'xml': ['xmllint'], " ... \ } lockvar! s:_DEFAULT_CHECKERS " ...
So, the default one between the two is xmllint.vim. Let’s check the file contents:
syntax_checkers/xml/xmllint.vim"============================================================================ "File: xml.vim "Description: Syntax checking plugin for syntastic "Maintainer: Sebastian Kusnier <sebastian at kusnier dot net> "License: This program is free software. It comes without any warranty, " to the extent permitted by applicable law. You can redistribute " it and/or modify it under the terms of the Do What The Fuck You " Want To Public License, Version 2, as published by Sam Hocevar. " See http://sam.zoy.org/wtfpl/COPYING for more details. " "============================================================================ if exists('g:loaded_syntastic_xml_xmllint_checker') finish endif let g:loaded_syntastic_xml_xmllint_checker = 1 let s:save_cpo = &cpo set cpo&vim " You can use a local installation of DTDs to significantly speed up validation " and allow you to validate XML data without network access, see xmlcatalog(1) " and http://www.xmlsoft.org/catalog.html for more information. function! SyntaxCheckers_xml_xmllint_GetLocList() dict let makeprg = self.makeprgBuild({ \ 'args': '--xinclude --postvalid', \ 'args_after': '--noout' }) let errorformat= \ '%E%f:%l: error : %m,' . \ '%-G%f:%l: validity error : Validation failed: no DTD found %m,' . \ '%W%f:%l: warning : %m,' . \ '%W%f:%l: validity warning : %m,' . \ '%E%f:%l: validity error : %m,' . \ '%E%f:%l: parser error : %m,' . \ '%E%f:%l: %m,' . \ '%-Z%p^,' . \ '%-G%.%#' return SyntasticMake({ \ 'makeprg': makeprg, \ 'errorformat': errorformat, \ 'returns': [0, 1, 2, 3, 4, 5] }) endfunction call g:SyntasticRegistry.CreateAndRegisterChecker({ \ 'filetype': 'xml', \ 'name': 'xmllint'}) let &cpo = s:save_cpo unlet s:save_cpo " vim: set sw=4 sts=4 et fdm=marker:
The plugin grants the possibility to use external DTD files to validate an XML file, and it makes it by invoking xmllint in this way:
$ xmllint --xinclude --postvalid YOUR-FILE-NAME.xml --noout
As a matter of fact, running the same command again, we receive the HTTP request another time.
Conclusions
Unfortunately, further analysis demonstrated that it is not possible to create a payload able to execute commands or exfiltrate data. We are only allowed to read the file contents and do HTTP requests to arbitrary addresses — and no, it seems that we cannot do both altogether.
Although it seems that we cannot take full advantage of the vulnerability, it is indeed very interesting to think that a victim could run a request on our behalf, just by saving a file — an operation that, sometimes, may not be as harmless as it seems.