[LineCTF2022]Memo Drive

首先查看题目源码

import os
import hashlib
import shutil
import datetime
import uvicorn
import logging

from urllib.parse import unquote
from starlette.applications import Starlette
from starlette.responses import HTMLResponse
from starlette.routing import Route, Mount
from starlette.templating import Jinja2Templates
from starlette.staticfiles import StaticFiles

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

templates = Jinja2Templates(directory='./')
templates.env.autoescape = False

def index(request):
    context = {}
    memoList = []
    
    try:
        clientId = getClientID(request.client.host)
        path = './memo/' + clientId
        
        if os.path.exists(path):
            memoList = os.listdir(path)
        
        context['request'] = request
        context['ip'] = request.client.host
        context['clientId'] = clientId
        context['memoList'] = memoList
        context['count'] = len(memoList)
    
    except:
        pass
    
    return templates.TemplateResponse('/view/index.html', context)

def save(request):
    context = {}
    memoList = []
    
    try:
        context['request'] = request
        context['ip'] = request.client.host
        
        contents = request.query_params['contents']
        path = './memo/' + getClientID(request.client.host) + '/'
        
        if os.path.exists(path) == False:
            os.makedirs(path, exist_ok=True)
        
        memoList = os.listdir(path)
        idx = len(memoList)
        
        if idx >= 3:
            return HTMLResponse('Memo Full')
        elif len(contents) > 100:
            return HTMLResponse('Contents Size Error (MAX:100)')
        
        filename = str(idx) + '_' + datetime.datetime.now().strftime('%Y%m%d%H%M%S')
            
        f = open(path + filename, 'w')
        f.write(contents)
        f.close()
    
    except:
        pass
    
    return HTMLResponse('Save Complete')

def reset(request):
    context = {}
    
    try:
        context['request'] = request
            
        clientId = getClientID(request.client.host)
        path = './memo/' + clientId
            
        if os.path.exists(path) == False:
            return HTMLResponse('Memo Null')
        
        shutil.rmtree(path)
            
    except:
        pass
    
    return HTMLResponse('Reset Complete')

def view(request):
    context = {}

    try:
        context['request'] = request
        clientId = getClientID(request.client.host)

        if '&' in request.url.query or '.' in request.url.query or '.' in unquote(request.query_params[clientId]):
            raise
        
        filename = request.query_params[clientId]
        path = './memo/' + "".join(request.query_params.keys()) + '/' + filename
        
        f = open(path, 'r')
        contents = f.readlines()
        f.close()
        
        context['filename'] = filename
        context['contents'] = contents
    
    except:
        pass
    
    return templates.TemplateResponse('/view/view.html', context)

def getClientID(ip):
    key = ip + '_' + os.getenv('SALT')
    
    return hashlib.md5(key.encode('utf-8')).hexdigest()

routes = [
    Route('/', endpoint=index),
    Route('/view', endpoint=view),
    Route('/reset', endpoint=reset),
    Route('/save', endpoint=save),
    Mount('/static', StaticFiles(directory='./static'), name='static')
]

app = Starlette(debug=False, routes=routes)

if __name__ == "__main__":
    logging.info("Starting Starlette Server")
    uvicorn.run(app, host="0.0.0.0", port=11000)

找到关键代码view(),view可以查看文件。如果能够控制path的值,那就能读取flag。而我们能控制的地方只有 request.query_params.keys()和filename

def view(request):
    context = {}

    try:
        context['request'] = request
        clientId = getClientID(request.client.host)

        if '&' in request.url.query or '.' in request.url.query or '.' in unquote(request.query_params[clientId]):
            raise
        
        filename = request.query_params[clientId]
        path = './memo/' + "".join(request.query_params.keys()) + '/' + filename
        
        f = open(path, 'r')
        contents = f.readlines()
        f.close()
        
        context['filename'] = filename
        context['contents'] = contents
    
    except:
        pass
    
    return templates.TemplateResponse('/view/view.html', context)

这里涉及到CVE-2021-23336:https://github.com/encode/starlette/issues/1325
漏洞的成因是 Starlette 错误地把分号当成参数的分割符,分号后面的字符串将被当成query_params.keys(),因此如果参数设置成view?67eb9cc01b6d566e811945ab5b376ac5=flag;/…/,分号后面的/…/会被当成query_params.keys()拼接到filename之前,最终代码中的path就会变成
./memo//…//flag,从而读取到flag文件
fake_flag
但是这只是一个假的flag,我们需要使用burp爆破目录读取文件,发现在/proc/1/environ中

environ
flag

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值