2022年 11月 9日

python网站安全(一): XSS注入

服务器

例如,我们开发一个显示所有用户的留言的网站:
建立如下的flask项目:

app.py为python程序,代码:

import json
import flask

app = flask.Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def index():
    request = flask.request
    with open('messages.json', 'r', encoding='utf-8') as fp:
        data = json.load(fp)  # 读入用户留言
    data = list(data)  # 建议有这一句,防止JSON被篡改
    if request.method == 'GET':  # GET请求
        return flask.render_template('index.html', messages=data)
    else:   # POST请求
        message = request.form.get('message').strip()
        if not message:
            flask.flash('请填入信息')
            return flask.redirect(flask.url_for('index'))
        data.append(message)
        with open('messages.json', 'w', encoding='utf-8') as fp:
            json.dump(data, fp)  # 写入用户留言
        return flask.redirect(flask.url_for('index'))

app.run(debug=True)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

index.html为网站页面,代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试XSS注入</title>
    <!-- 导入bootstrap -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
    <ul>
        <!-- Flask Jinja2模板-->
        {% for i in messages %}
        <li> {{ i }} </li>
        {% endfor %}
    </ul>
    <br>
    <hr>
    <form method="post" action="/" class="bs-example bs-example-form" role="form">
        <!-- 用户输入框 -->
        <div class="input-group" align="center">
            <span class="input-group-addon">留言:</span>
            <input type="text" class="form-control" name="message">
        </div>
        <hr>
        <input type="submit" value="发送" class="btn btn-primary" align="center">
    </form>
</div>
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
    <a href="#" class="close" data-dismiss="alert">
        &times;
    </a>
    <strong>{{ message }}</strong>
</div>
{% endfor %}
</body>
</html>
  • 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

messages.json为用户信息储存器:

["test", "hello world"]
  • 1

效果如下:
网站效果

攻击者

现在,我们在网站的输入框中输入一句JavaScript代码:

alert("你已被XSS攻击!")
  • 1

输入
刷新网站后:
攻击效果

防御

(这里只提供最简单的思路)

我们加入如下代码:

nopass = ['<script>', 'SB']  # 违法字典,你也可以用它屏蔽脏话
  • 1
for i in nopass:
    if i in message:
        flask.flash('信息不合法')
        return flask.redirect(flask.url_for('index'))
  • 1
  • 2
  • 3
  • 4

改动后的代码:

import json
import flask

app = flask.Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def index():
    request = flask.request
    with open('messages.json', 'r', encoding='utf-8') as fp:
        data = json.load(fp)  # 读入用户留言
    data = list(data)  # 建议有这一句,防止JSON被篡改
    if request.method == 'GET':  # GET请求
        return flask.render_template('index.html', messages=data)
    else:   # POST请求
        nopass = ['<script>', 'SB']  # 违法字典,你也可以用它屏蔽脏话
        message = request.form.get('message').strip()
        if not message:
            flask.flash('请填入信息')
            return flask.redirect(flask.url_for('index'))
        for i in nopass:
            if i in message:
                flask.flash('信息不合法')
                return flask.redirect(flask.url_for('index'))
        data.append(message)
        with open('messages.json', 'w', encoding='utf-8') as fp:
            json.dump(data, fp)  # 写入用户留言
        return flask.redirect(flask.url_for('index'))

app.run(debug=True)
  • 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