如何在Windows上安全地转义cmd.exe shell的命令行参数?

时间:2015-03-23 14:40:32

标签: python windows cmd

好的,我有一个必须在shell=True模式下执行的命令。

os.systemsubprocess.Popen(..., shell=True)

此命令包含字符串替换,如:cmd = "some_secret_command {0}".format(string_from_user)

我想要转义string_from_user变量来阻止任何注射。

简单的错误答案:

  1. 使用shlex.quote - 错误
  2. print(shlex.quote('file.txxt; &ls . #')) - > 'file.txxt; &ls . #'(注射)

    示例:

    > python -c "import sys; print(sys.argv[1])" 'file.txxt; &ls . #'
    secret.txt
    secret2.txt
    
    1. 使用转义^ - 不正确
    2. 示例:

      import os
      
      CMD = '''string with spaces'''.replace('', '^').replace('^"', '')
      os.system('python -c "import sys; print(sys.argv[1])" {0}'.format(CMD))
      

      现在我可以使用(空格)并注入多个参数。

      1. 使用^"' - 不正确
      2. 示例:

        import os
        
        CMD = '''some arg with spaces'''.replace('', '^').replace('^"', '')
        os.system('python -c "import sys; print(sys.argv[1])" "{0}"'.format(CMD))
        

        打印^s^o^m^e^ ^a^r^g^ ^w^i^t^h^ ^s^p^a^c^e^s^

        以及'

        import os
        
        CMD = '''some spaces'''.replace('', '^').replace('^\'', '')
        os.system('python -c "import sys; print(sys.argv[1])" \'{0}\''.format(CMD))
        

        打印'some

        我现在关于shell=False,但这对我来说不正确。

1 个答案:

答案 0 :(得分:10)

引用Windows命令行的问题是有两个分层的解析引擎受到引号的影响。首先,Shell(例如cmd.exe)解释了一些特殊字符。然后,有一个被调用的程序解析命令行。这通常发生在Windows提供的CommandLineToArgvW函数中,但并非总是如此。

那说,对于一般情况,例如,使用cmd.exe将程序解析为CommandLineToArgvW的命令行,您可以使用Daniel Colascione在Everyone quotes command line arguments the wrong way中描述的技术。我原本试图将其改编为Ruby,现在尝试将其转换为python。

import re

def escape_argument(arg):
    # Escape the argument for the cmd.exe shell.
    # See http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
    #
    # First we escape the quote chars to produce a argument suitable for
    # CommandLineToArgvW. We don't need to do this for simple arguments.

    if not arg or re.search(r'(["\s])', arg):
        arg = '"' + arg.replace('"', r'\"') + '"'

    return escape_for_cmd_exe(arg)

def escape_for_cmd_exe(arg):
    # Escape an argument string to be suitable to be passed to
    # cmd.exe on Windows
    #
    # This method takes an argument that is expected to already be properly
    # escaped for the receiving program to be properly parsed. This argument
    # will be further escaped to pass the interpolation performed by cmd.exe
    # unchanged.
    #
    # Any meta-characters will be escaped, removing the ability to e.g. use
    # redirects or variables.
    #
    # @param arg [String] a single command line argument to escape for cmd.exe
    # @return [String] an escaped string suitable to be passed as a program
    #   argument to cmd.exe

    meta_chars = '()%!^"<>&|'
    meta_re = re.compile('(' + '|'.join(re.escape(char) for char in list(meta_chars)) + ')')
    meta_map = { char: "^%s" % char for char in meta_chars }

    def escape_meta_chars(m):
        char = m.group(1)
        return meta_map[char]

    return meta_re.sub(escape_meta_chars, arg)

应用此代码,您应该能够成功转义cmd.exe shell的参数。

print escape_argument('''some arg with spaces''')
# ^"some arg with spaces^"

请注意,该方法应引用一个完整的参数。如果要从多个源收集参数,例如,通过构建一串python代码以传递给python命令,则必须先将其汇总,然后再将其传递给escape_argument

import os

CMD = '''string with spaces and &weird^ charcters!'''
os.system('python -c "import sys; print(sys.argv[1])" {0}'.format(escape_argument(CMD)))
# string with spaces and &weird^ charcters!