Python eval 利用技巧

Table of Contents

时间:2022-03-17

朋友分享了一道 Python 的 CTF 题,是关于存在 eval 参数可控,但长度受限的场景。完整代码无需贴出,我截取并修改产生漏洞的关键函数,代码如下:

# coding=utf-8

def multiply_func(x):
    print(f'value 1: {repr(x)}')
    v = input('value 2: ')
    if len(v) > 8:
        return
    return eval(f'{x} * {v}', {}, {})


if __name__ == '__main__':
    print(multiply_func(10))

multiply_func 函数体中,变量 v 的值取决于用户输入,因此是可控的,不过中间做了输入长度的判断限制——字符串长度不可大于 8。因此这里虽可执行任意代码,但代码体积又受限,无法很好地利用。

经过点拨,原来可以通过 Python 内置的 help 函数来获得执行任意系统命令:

1、输入:help(),这里字符串长度只有 6,会进入正常调用 eval 函数;

2、进入 help 交互式,然后输入任意一个模块名获得该模块的帮助文档,如 sys;

3、在 Linux 中,这里呈现帮助文档时,实际上是调用了系统里的 less 或 more 命令,可以利用这俩个命令执行本地命令的特性来获取一个 shell,继续按 #!,再执行外部命令 sh 即可。

1. help() 源码分析

为了弄清原理,在 Python 源码中找到 help 函数的实现,位于 Lib/_sitebuiltins.py 文件中的 _Helper 中类:

class _Helper(object):
    def __repr__(self):
    return "Type help() for interactive help, " \
        "or help(object) for help about object."


def __call__(self, *args, **kwds):
    import pydoc
    return pydoc.help(*args, **kwds)

_Helper 又调用了 pydoc.help 函数,位于 Lib/pydoc.py,其中的调用流程整理如下:

Helper.interact() -> Helper.help() -> doc() -> pager() -> getpager()

最后,getpager() 函数负责执行外部命令:

def getpager():
    # ... 省略 ...

    if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
        return lambda text: pipepager(text, 'less')

    import tempfile
    (fd, filename) = tempfile.mkstemp()
    os.close(fd)
    try:
        if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
            return lambda text: pipepager(text, 'more')
        else:
            return ttypager
    finally:
        os.unlink(filename)