フリーキーズ | 独学プログラミング

OWASP Top 10とは?2025年版で学ぶWebセキュリティの基本

リンドくん

リンドくん

たなべ先生、Webアプリを作り始めたんですけど、セキュリティって何から勉強すればいいですか?範囲が広すぎて...

たなべ

たなべ

そういうときこそOWASP Top 10だよ!
これは世界中のセキュリティ専門家が「これだけは絶対に知っておくべき」って選んだ、最重要なセキュリティリスクのトップ10なんだ。

リンドくん

リンドくん

トップ10だけでいいんですか?もっとたくさん覚えないと...

たなべ

たなべ

この10個を理解して対策するだけで、主要な攻撃の9割以上は防げるんだよ。セキュリティ学習の最高のスタート地点なんだ。

Webアプリケーションを開発する際、機能の実装に夢中になって、セキュリティ対策を後回しにしてしまう方も多いのではないでしょうか?
しかし、セキュリティの問題は、一度発生すると企業の信頼を大きく損ない、ユーザーに深刻な被害をもたらす可能性があります。

そこで重要になるのがOWASP Top 10です。
これは、Webアプリケーションにおける最も重大なセキュリティリスクをまとめたリストで、開発者がまず押さえるべき基本中の基本として、世界中で参照されています。

この記事では、セキュリティ学習を始めたばかりの方に向けて、OWASP Top 10とは何か、2025年版ではどのようなリスクが挙げられているのか、そして実際にどう対策すればよいのかを、わかりやすく解説していきます。

プログラミング学習でお悩みの方へ

HackATAは、エンジニアを目指す方のためのプログラミング学習コーチングサービスです。 経験豊富な現役エンジニアがあなたの学習をサポートします。

✓ 質問し放題

✓ β版公開中(2025年内の特別割引)

HackATAの詳細を見る

OWASP Top 10とは何か

リンドくん

リンドくん

そもそもOWASPって何の略なんですか?

たなべ

たなべ

Open Web Application Security Projectの略で、Webアプリケーションのセキュリティ向上を目指す国際的な非営利団体なんだ。世界中のセキュリティ専門家が参加していて、その知見を無料で公開しているんだよ。

OWASPという組織

OWASP(オワスプ)は、2001年に設立された国際的な非営利コミュニティで、Webアプリケーションのセキュリティに関する情報やツールを提供しています。
誰でも参加でき、すべての成果物がオープンソースとして公開されているのが大きな特徴です。

OWASPの活動は多岐にわたりますが、中でも最も有名なのが今回紹介するOWASP Top 10です。

OWASP Top 10とは

OWASP Top 10は、Webアプリケーションにおける最も重大なセキュリティリスク10項目をランキング形式でまとめたドキュメントです。

特徴

  • 世界中の企業や開発者から収集された実際の脆弱性データに基づいている
  • セキュリティ専門家の知見が反映されている
  • 数年ごとに更新され、最新の脅威に対応している
  • 無料で誰でもアクセスできる

このリストは単なる脅威の一覧ではありません。各リスクについて、なぜ危険なのかどう対策すべきかまでが具体的に解説されており、開発者がすぐに実践できる内容になっています。

なぜOWASP Top 10が重要なのか

セキュリティの世界は非常に広大で、覚えるべきことは無限にあるように感じられます。しかし、OWASP Top 10に挙げられている10個のリスクを理解し、適切に対策するだけで、実際に発生する攻撃の大半を防ぐことができるのです。

OWASP Top 10が重要な理由

  • 効率的な学習 → 最も重要なポイントから順番に学べる
  • 実践的 → 実際に発生している攻撃に基づいている
  • 業界標準 → 多くの企業がセキュリティ基準として採用している
  • 優先順位が明確 → どこから対策すべきかがわかる

特にセキュリティ学習を始めたばかりの方にとって、「何から学ぶべきか」という明確な指針を与えてくれる点が、OWASP Top 10の大きな価値なのです。

2025年版OWASP Top 10の全体像

リンドくん

リンドくん

2025年版では、どんなリスクが挙げられているんですか?

たなべ

たなべ

全部で10個あるんだけど、今日は特に重要な上位5つを中心に詳しく見ていこう。これだけでもかなりの脅威をカバーできるからね。

2025年版の10項目

2025年版のOWASP Top 10は以下の通りです。

  1. A01 - アクセス制御の不備
  2. A02 - セキュリティの設定ミス
  3. A03 - ソフトウェアサプライチェーンの不具合
  4. A04 - 暗号化の失敗
  5. A05 - インジェクション
  6. A06 - 安全が確認されない不安な設計
  7. A07 - 認証の失敗
  8. A08 - ソフトウェアやデータの整合性の不具合
  9. A09 - ログ記録と警告の失敗
  10. A10 - 例外的な状況の誤処理

これらのリスクは、単に「危険なもの」を並べただけではありません。実際の攻撃データと専門家の分析に基づいて、発生頻度検出の難しさ影響の大きさなどを総合的に評価してランク付けされています。

それでは、特に重要な上位5つのリスクについて、詳しく見ていきましょう。

A01 - アクセス制御の不備

リンドくん

リンドくん

アクセス制御って、ログイン機能のことですか?

たなべ

たなべ

ログインも含まれるけど、もっと広い概念なんだ。
「誰が」「何を」「どこまで」できるかをきちんと管理することなんだよ。これができていないと、URLを変えるだけで他人のデータが見られちゃったりするんだ。

アクセス制御の不備とは

アクセス制御の不備は、2025年版でも最も重要なリスクとして1位にランクされています。これは、ユーザーが本来許可されていない操作やデータへのアクセスができてしまう脆弱性のことです。

具体的な問題例

  • URLのIDを変更するだけで他人のアカウント情報が見られる
  • 通常ユーザーが管理者用の機能を実行できてしまう
  • 削除権限がないのにデータを削除できてしまう

危険なコードの例

# 危険な例 URLのIDだけでアクセスできる
@app.route('/user/profile/<user_id>')
def show_profile(user_id):
    # 権限チェックがない!
    user = User.query.get(user_id)
    return render_template('profile.html', user=user)

# 攻撃者は /user/profile/1, /user/profile/2 と
# URLを変えるだけで全ユーザーの情報を見られる

正しい実装方法

# 安全な例 適切な権限チェック
@app.route('/user/profile/<user_id>')
@login_required  # ログイン必須
def show_profile(user_id):
    # リクエストされたユーザー情報を取得
    user = User.query.get_or_404(user_id)
    
    # 本人または管理者のみアクセス可能
    if current_user.id != user.id and not current_user.is_admin:
        abort(403)  # 権限なしエラー
    
    return render_template('profile.html', user=user)

実際の対策

アクセス制御を適切に実装するためのポイントは以下の通りです。

1. デフォルトで拒否する

アクセスは原則として拒否し、明示的に許可されている場合のみ実行できるようにします。

2. すべての機能で権限チェック

「このページは誰も見つけないだろう」という考えは危険です。すべてのエンドポイントで権限チェックを実装しましょう。

# 権限チェック用のデコレーター
from functools import wraps

def require_admin(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_admin:
            abort(403)
        return f(*args, **kwargs)
    return decorated_function

@app.route('/admin/dashboard')
@login_required
@require_admin
def admin_dashboard():
    return render_template('admin/dashboard.html')

3. リソースレベルでの確認

ユーザーがログインしているだけでなく、アクセスしようとしている特定のリソースに対して権限があるかを確認します。

A02 - セキュリティの設定ミス

リンドくん

リンドくん

設定ミスって、具体的にどういうことですか?

たなべ

たなべ

例えば、開発中は便利だからってデバッグモードをオンにしたまま本番環境で公開しちゃうとか、デフォルトのパスワードを変更し忘れるとかだね。こういう「うっかり」が大きな事故につながるんだ。

セキュリティの設定ミスとは

アプリケーションやサーバーの設定が不適切で、攻撃者に悪用される可能性がある状態のことです。コードには問題がなくても、設定が間違っているだけでセキュリティホールになってしまいます。

よくある設定ミス

  • デバッグモードが本番環境で有効になっている
  • デフォルトのアカウント・パスワードを使っている
  • エラーメッセージで詳細な情報を表示している
  • 不要なサービスやポートが開いている
  • セキュリティアップデートが適用されていない

危険な設定の例

# 危険な例 Flaskのデバッグモード
from flask import Flask

app = Flask(__name__)
app.debug = True  # 本番環境でこれは危険!

@app.route('/')
def index():
    return "Hello World"

if __name__ == '__main__':
    app.run(host='0.0.0.0')  # 全てのIPからアクセス可能

デバッグモードをオンにすると、エラーが発生した際に詳細なスタックトレースがブラウザに表示されます。これは開発中は便利ですが、攻撃者にシステムの内部構造を教えてしまうことになります。

正しい設定方法

# 安全な例 環境に応じた設定
import os
from flask import Flask

app = Flask(__name__)

# 環境変数で設定を切り替える
app.config['DEBUG'] = os.getenv('FLASK_ENV') == 'development'
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')

@app.errorhandler(500)
def internal_error(error):
    # 本番環境では詳細を表示しない
    return "An error occurred", 500

if __name__ == '__main__':
    # 本番環境では適切なWebサーバー(Gunicorn等)を使用
    app.run()

セキュリティヘッダの設定

Webアプリケーションでは、HTTPレスポンスヘッダを適切に設定することで、様々な攻撃を防ぐことができます。

@app.after_request
def set_security_headers(response):
    # ブラウザにHTTPSを強制
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    
    # クリックジャッキング対策
    response.headers['X-Frame-Options'] = 'DENY'
    
    # XSS対策
    response.headers['X-Content-Type-Options'] = 'nosniff'
    
    # コンテンツセキュリティポリシー
    response.headers['Content-Security-Policy'] = "default-src 'self'"
    
    return response

チェックリスト

本番環境デプロイ前のチェック

  • デバッグモードは無効化されている
  • デフォルトのアカウント・パスワードは変更されている
  • 不要なエンドポイントは削除されている
  • エラーメッセージは一般的な内容になっている
  • セキュリティヘッダが設定されている
  • HTTPSが有効になっている

A03 - ソフトウェアサプライチェーンの不具合

リンドくん

リンドくん

「サプライチェーン」って製造業の言葉じゃないんですか?

たなべ

たなべ

いい着眼点だね!ソフトウェアにも同じ概念があるんだ。
今のアプリ開発では、たくさんのライブラリやツールに依存しているよね。それらのどこかに脆弱性があると、自分のアプリも危険にさらされるんだよ。

ソフトウェアサプライチェーンとは

現代のアプリケーション開発では、ゼロからすべてを作ることはほとんどありません。多くの外部ライブラリやフレームワークを使用しています。

依存している例

  • npmやpipからインストールするパッケージ
  • React、Django、Expressなどのフレームワーク
  • データベースドライバー
  • 画像処理ライブラリ

これらすべてがソフトウェアサプライチェーンの一部です。一つでも脆弱性があれば、あなたのアプリケーション全体が危険にさらされる可能性があります。

実際に起きた深刻な事例

2021年12月に発見されたLog4Shellという脆弱性は、Javaの広く使われているログライブラリに存在していました。この一つのライブラリの脆弱性により、世界中の数百万のアプリケーションが影響を受けました。

これは本当に衝撃的でしたよね。自分のコードには何の問題もなくても、使っているライブラリの脆弱性によって攻撃を受けてしまうのです。

危険な依存関係管理

# 危険な例 バージョンを指定しない
# requirements.txt
flask
requests
pillow
sqlalchemy

# 問題点 
# - 最新版が自動的にインストールされる
# - 互換性のない変更が入る可能性
# - 脆弱性があるバージョンがインストールされるリスク

安全な依存関係管理

# 安全な例 バージョンを明確に指定
# requirements.txt
flask==3.0.0
requests==2.31.0
pillow==10.1.0
sqlalchemy==2.0.23

# さらに安全 ハッシュ値も記録
# requirements.txt
flask==3.0.0 \
    --hash=sha256:1234567890abcdef...
requests==2.31.0 \
    --hash=sha256:abcdef1234567890...

実践的な対策

1. 依存関係を記録する

# Python
pip freeze > requirements.txt

# Node.js
npm install  # package-lock.json が自動生成される

2. 定期的な脆弱性チェック

# Python: pip-audit を使用
pip install pip-audit
pip-audit

# Node.js: npm audit を使用
npm audit

# 自動修正
npm audit fix

3. 不要な依存関係を削除

使っていないライブラリは、定期的に削除しましょう。依存関係が少ないほど、管理も楽になり、脆弱性のリスクも減ります。

# 未使用のパッケージを確認(Node.js)
npx depcheck

A04 - 暗号化の失敗

リンドくん

リンドくん

暗号化って難しそうですね...数学とか必要ですか?

たなべ

たなべ

安心して!暗号化のアルゴリズムを自分で作る必要はないんだ。
既存の安全な暗号化ライブラリを正しく使うことが重要なんだよ。間違った使い方をしないための基本を押さえればOKなんだ。

暗号化の失敗とは

機密データ(パスワード、クレジットカード情報、個人情報など)が適切に保護されていない状態のことです。

よくある問題

  • パスワードを暗号化せずにデータベースに保存
  • 古くて脆弱な暗号化アルゴリズムの使用
  • HTTPで機密情報を送信(HTTPSを使わない)
  • 暗号化キーの不適切な管理

絶対にやってはいけない例

# 危険な例 パスワードを平文で保存
def register_user(username, password):
    user = User(
        username=username,
        password=password  # これは絶対にダメ!
    )
    db.session.add(user)
    db.session.commit()

# データベースには「password123」のように平文で保存される
# データベースが漏洩したら全ユーザーのパスワードが漏れる

正しいパスワードの保存方法

# 安全な例 bcryptでハッシュ化
from flask_bcrypt import Bcrypt

bcrypt = Bcrypt(app)

def register_user(username, password):
    # パスワードをハッシュ化
    hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
    
    user = User(
        username=username,
        password=hashed_password  # ハッシュ化されたパスワードを保存
    )
    db.session.add(user)
    db.session.commit()

def login_user(username, password):
    user = User.query.filter_by(username=username).first()
    
    if user and bcrypt.check_password_hash(user.password, password):
        # パスワードが一致
        return True
    return False

HTTPSの重要性

どんなに暗号化しても、データの送信中に盗まれては意味がありません。必ずHTTPSを使用しましょう。

# Flaskで本番環境でHTTPSを強制
from flask_talisman import Talisman

app = Flask(__name__)
Talisman(app, force_https=True)

機密データの暗号化

クレジットカード情報など、特に機密性の高いデータは、データベースに保存する前に暗号化します。

from cryptography.fernet import Fernet

# 暗号化キー(環境変数で管理)
key = os.getenv('ENCRYPTION_KEY').encode()
cipher = Fernet(key)

def save_credit_card(card_number):
    # 暗号化して保存
    encrypted = cipher.encrypt(card_number.encode())
    return encrypted

def get_credit_card(encrypted_data):
    # 復号化して取得
    decrypted = cipher.decrypt(encrypted_data)
    return decrypted.decode()

覚えておくべきポイント

  • パスワードは必ずハッシュ化(bcryptやargon2を使用)
  • HTTPSを必ず使用(Let's Encryptで無料取得可能)
  • 暗号化キーは環境変数で管理(コードに直接書かない)
  • 古いアルゴリズム(MD5、SHA1)は使わない

A05 - インジェクション

リンドくん

リンドくん

インジェクション攻撃って、よく聞くんですけど、どういう攻撃なんですか?

たなべ

たなべ

簡単に言うと、悪意のあるコードを入力欄に入れて、システムに実行させる攻撃なんだ。SQLインジェクションが特に有名で、データベースを丸ごと盗まれたり、削除されたりする危険性があるんだよ。

インジェクション攻撃とは

攻撃者がアプリケーションの入力フィールドに悪意のあるコード(SQLコマンド、シェルコマンドなど)を注入し、意図しない動作を引き起こす攻撃です。

主な種類

  • SQLインジェクション → データベース操作の不正実行
  • コマンドインジェクション → システムコマンドの不正実行
  • LDAPインジェクション → ディレクトリサービスへの不正アクセス

SQLインジェクションの危険な例

# 危険な例 ユーザー入力を直接SQLに結合
@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']
    
    # これは非常に危険!
    query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
    user = db.execute(query).fetchone()
    
    if user:
        return "Login successful"
    return "Login failed"

攻撃例

攻撃者がユーザー名に以下を入力すると...

admin' --

実行されるSQLは以下のようになります。

SELECT * FROM users WHERE username='admin' --' AND password='xxx'

--はSQLのコメントなので、パスワードチェックがスキップされ、adminとしてログインできてしまいます!

正しい実装方法

# 安全な例 プレースホルダを使用
@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']
    
    # プレースホルダ(?)を使用
    query = "SELECT * FROM users WHERE username=? AND password=?"
    user = db.execute(query, (username, password)).fetchone()
    
    if user:
        return "Login successful"
    return "Login failed"

プレースホルダを使用することで、ユーザー入力は常に「データ」として扱われ、SQLコマンドとして解釈されることはありません。

ORMを使った安全な実装

# SQLAlchemyなどのORMを使用
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)
    password = db.Column(db.String(120))

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']
    
    # ORMが自動的に安全なクエリを生成
    user = User.query.filter_by(username=username).first()
    
    if user and check_password(user.password, password):
        return "Login successful"
    return "Login failed"

コマンドインジェクションの対策

# 危険な例 ユーザー入力を直接コマンドに使用
import subprocess

@app.route('/ping')
def ping():
    host = request.args.get('host')
    # これは危険!
    result = subprocess.run(f'ping -c 4 {host}', shell=True, capture_output=True)
    return result.stdout

# 安全な例 入力を検証し、shellを使わない
import re

@app.route('/ping')
def ping():
    host = request.args.get('host')
    
    # ホスト名の検証(英数字とドット、ハイフンのみ)
    if not re.match(r'^[a-zA-Z0-9.-]+$', host):
        return "Invalid host", 400
    
    # shell=Falseで安全に実行
    result = subprocess.run(['ping', '-c', '4', host], capture_output=True)
    return result.stdout

対策まとめ

  • SQLクエリは必ずプレースホルダを使用
  • ORMを適切に活用する
  • ユーザー入力は信頼しない(必ず検証・サニタイズ)
  • shell=Trueは極力使わない

初心者が今日から始められる3つの対策

リンドくん

リンドくん

たくさん学びましたけど、何から手をつければいいですか?

たなべ

たなべ

いきなり全部は無理だから、今日から実践できる3つの基本から始めよう。これだけでもかなりセキュリティは向上するよ!

1. 環境変数で機密情報を管理する

コードに直接パスワードやAPIキーを書かないようにしましょう。

# 悪い例
DATABASE_URL = "postgresql://user:password123@localhost/mydb"
API_KEY = "sk-1234567890abcdef"

# 良い例
import os

DATABASE_URL = os.getenv('DATABASE_URL')
API_KEY = os.getenv('API_KEY')

.envファイルを作成して管理します。

# .env
DATABASE_URL=postgresql://user:password123@localhost/mydb
API_KEY=sk-1234567890abcdef

2. 依存パッケージのバージョンを固定する

# Python
pip freeze > requirements.txt

# Node.js
npm install  # package-lock.json を必ずコミット

定期的に脆弱性をチェックします。

# Python
pip-audit

# Node.js
npm audit

3. 基本的な入力検証を実装する

すべてのユーザー入力に対して、基本的な検証を行います。

from flask import request, abort

@app.route('/user/<int:user_id>')
def get_user(user_id):
    # 数値以外は自動的に404になる(int型指定のおかげ)
    
    # さらに範囲をチェック
    if user_id < 1 or user_id > 1000000:
        abort(400, "Invalid user ID")
    
    user = User.query.get_or_404(user_id)
    return jsonify(user.to_dict())

@app.route('/search')
def search():
    query = request.args.get('q', '')
    
    # 長さをチェック
    if len(query) > 100:
        abort(400, "Query too long")
    
    # 特殊文字をチェック
    if any(char in query for char in ['<', '>', ';', '&']):
        abort(400, "Invalid characters")
    
    results = perform_search(query)
    return jsonify(results)

まとめ

リンドくん

リンドくん

OWASP Top 10、だいぶ理解できました!でも実際のプロジェクトで全部実装するのは大変そうです...

たなべ

たなべ

最初から完璧を目指す必要はないよ!
まずは今日学んだ基本から実践して、少しずつセキュリティ意識を高めていけばいいんだ。継続的に改善していく姿勢が一番大切なんだよ。

OWASP Top 10は、Webアプリケーションセキュリティの基本中の基本です。
この記事で学んだ内容を実践するだけで、主要なセキュリティリスクの大半を防ぐことができます。

今日学んだ重要ポイントのおさらい

  • OWASP Top 10は世界標準のセキュリティリスクリスト
  • アクセス制御は必ずすべての機能で実装する
  • 設定ミスに注意し、本番環境では適切な設定を行う
  • 依存パッケージのバージョンを管理し、定期的にチェックする
  • 暗号化は既存の安全なライブラリを正しく使う
  • インジェクション対策はプレースホルダやORMを使用する

セキュリティ対策は、確かに最初は難しく感じるかもしれません。
しかし、基本を押さえて一つずつ実践していけば、必ず安全なアプリケーションを作ることができます。

セキュリティは、ユーザーの大切なデータと信頼を守るための、開発者にとって最も重要な責任です。
OWASP Top 10を理解し、適切な対策を実施することで、より安全で信頼されるアプリケーションを一緒に作っていきましょう!

この記事をシェア

関連するコンテンツ