高校日本史189857 views
英語607877 views
数学講師2852771 views
中学英語808712 views
りんご192546 views
中学理科1626207 views
LaTeX957300 views
小学社会308636 views
小学理科717236 views
いろは2986023 views
Help
Tools

English

Flask の入力バリデーションとサニタイズ

ユーザーからの入力を信頼せず、適切にバリデーションとサニタイズを行うことは、Flask アプリケーションのセキュリティに不可欠である。

バリデーションとサニタイズの違い

バリデーション入力が期待する形式か検証し、不正なら拒否
サニタイズ入力から危険な部分を除去・エスケープして安全にする

両方を組み合わせることが重要である。

Flask-WTF によるフォームバリデーション

pip install flask-wtf email-validator
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, IntegerField
from wtforms.validators import DataRequired, Email, Length, NumberRange

class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[
        DataRequired(),
        Length(min=3, max=20)
    ])
    email = StringField('Email', validators=[
        DataRequired(),
        Email()
    ])
    password = PasswordField('Password', validators=[
        DataRequired(),
        Length(min=8)
    ])
    age = IntegerField('Age', validators=[
        NumberRange(min=0, max=150)
    ])
@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        # バリデーション済みのデータ
        username = form.username.data
        return redirect(url_for('login'))
    return render_template('register.html', form=form)

カスタムバリデータ

from wtforms.validators import ValidationError

def username_exists(form, field):
    if User.query.filter_by(username=field.data).first():
        raise ValidationError('Username already taken')

class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[
        DataRequired(),
        username_exists
    ])

API のバリデーション(marshmallow)

pip install marshmallow
from marshmallow import Schema, fields, validate, ValidationError

class UserSchema(Schema):
    username = fields.Str(required=True, validate=validate.Length(min=3, max=20))
    email = fields.Email(required=True)
    age = fields.Int(validate=validate.Range(min=0, max=150))

@app.route('/api/users', methods=['POST'])
def create_user():
    schema = UserSchema()
    try:
        data = schema.load(request.json)
    except ValidationError as err:
        return {'errors': err.messages}, 400
    
    # バリデーション済みの data を使用
    user = User(**data)
    db.session.add(user)
    db.session.commit()
    return schema.dump(user), 201

HTML のサニタイズ

ユーザー入力を HTML として表示する場合、XSS を防ぐためにサニタイズが必要である。

pip install bleach
import bleach

# 許可するタグと属性を指定
ALLOWED_TAGS = ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li']
ALLOWED_ATTRS = {'a': ['href', 'title']}

def sanitize_html(dirty_html):
    return bleach.clean(
        dirty_html,
        tags=ALLOWED_TAGS,
        attributes=ALLOWED_ATTRS,
        strip=True
    )

@app.route('/post', methods=['POST'])
def create_post():
    content = request.form['content']
    safe_content = sanitize_html(content)
    post = Post(content=safe_content)
    db.session.add(post)
    db.session.commit()
    return redirect(url_for('index'))

Jinja2 の自動エスケープ

Jinja2 はデフォルトで HTML エスケープを行う。

<!-- 安全: 自動エスケープ -->
<p>{{ user_input }}</p>

<!-- 危険: エスケープを無効化 -->
<p>{{ user_input | safe }}</p>

| safe フィルタは、サニタイズ済みのデータにのみ使用すること。

入力バリデーションのベストプラクティス

ホワイトリスト方式で許可する文字・形式を定義
長さ、範囲、形式を必ずチェック
クライアント側バリデーションは信用しない
エラーメッセージで内部情報を漏らさない

適切なバリデーションとサニタイズにより、多くの攻撃を未然に防ぐことができる。