APIセキュリティとは?
APIセキュリティとは、REST API、GraphQL API、WebSocketなどのAPI(Application Programming Interface)を狙った攻撃から、システムを保護するためのセキュリティ対策全般を指します。
現代のWebアプリケーションでは、フロントエンドとバックエンドの分離、マイクロサービス化、モバイルアプリとの連携など、APIが中心的な役割を果たしています。
しかし、APIは以下のような特徴から、従来のWebアプリケーションとは異なるセキュリティリスクを抱えています:
- 認証・認可の複雑さ
- 大量のデータアクセス
- 自動化攻撃の標的になりやすい
- エラー情報の漏洩リスク
- レート制限の重要性
2023年のセキュリティレポートでは、APIを狙った攻撃が300%増加したことが報告されており、今や最も重要なセキュリティ課題の一つとなっています。
APIの脆弱性と攻撃手法
1. 認証・認可の不備
脆弱な実装例:
// 危険なコード例(Express.js)
app.get('/api/users/:id', (req, res) => {
const userId = req.params.id;
// 認証チェックなし!
const user = getUserById(userId);
res.json(user);
});
// 他のユーザーの情報も取得可能
// GET /api/users/123 -> 他人の個人情報が取得される
攻撃例:
# 攻撃者が他のユーザーの情報を取得
curl -X GET "https://api.example.com/api/users/1"
curl -X GET "https://api.example.com/api/users/2"
curl -X GET "https://api.example.com/api/users/3"
# 順番に全ユーザーの情報を取得可能
2. BOLA攻撃(Broken Object Level Authorization)
最も一般的なAPI脆弱性の一つです。
// 脆弱なコード例
app.get('/api/orders/:orderId', authenticateUser, (req, res) => {
const orderId = req.params.orderId;
// ユーザーが認証されているが、注文の所有者確認なし
const order = getOrderById(orderId);
res.json(order);
});
攻撃の実例:
# 正規ユーザーがログイン後、他人の注文情報を取得
curl -H "Authorization: Bearer valid_token" \
-X GET "https://api.example.com/api/orders/12345"
# 攻撃者は順番に注文IDを変更して情報を取得
curl -H "Authorization: Bearer valid_token" \
-X GET "https://api.example.com/api/orders/12346"
3. 過度な情報露出
APIが必要以上の情報を返してしまう脆弱性:
// 危険なコード例
app.get('/api/profile', authenticateUser, (req, res) => {
const user = getUserById(req.user.id);
// 機密情報も含めて全て返してしまう
res.json(user); // password, ssn, internal_notes なども含む
});
安全な実装:
// 安全なコード例
app.get('/api/profile', authenticateUser, (req, res) => {
const user = getUserById(req.user.id);
// 必要な情報のみを返す
const safeUserData = {
id: user.id,
name: user.name,
email: user.email,
avatar: user.avatar
};
res.json(safeUserData);
});
4. レート制限の不備
レート制限がない、または不適切な場合の攻撃:
# ブルートフォース攻撃の例
for i in {1..10000}; do
curl -X POST "https://api.example.com/login" \
-d "username=admin&password=password$i"
done
# データ漏洩攻撃
for i in {1..100000}; do
curl -H "Authorization: Bearer token" \
"https://api.example.com/api/users/$i"
done
5. インジェクション攻撃
APIパラメータを通じたSQLインジェクションやNoSQLインジェクション:
// 脆弱なコード例
app.get('/api/search', (req, res) => {
const query = req.query.q;
// SQLインジェクションの脆弱性
const sql = `SELECT * FROM products WHERE name LIKE '%${query}%'`;
db.query(sql, (results) => {
res.json(results);
});
});
攻撃例:
# SQLインジェクション攻撃
curl "https://api.example.com/api/search?q='; DROP TABLE users; --"
APIセキュリティの対策方法
1. 適切な認証・認可の実装
JWT(JSON Web Token)を使用した認証:
// 安全な認証実装(Express.js + JWT)
const jwt = require('jsonwebtoken');
// JWTミドルウェア
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token == null) {
return res.status(401).json({ error: 'トークンが必要です' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: '無効なトークンです' });
}
req.user = user;
next();
});
}
// 認可チェック付きのエンドポイント
app.get('/api/orders/:orderId', authenticateToken, async (req, res) => {
try {
const orderId = req.params.orderId;
const userId = req.user.id;
// 所有者確認
const order = await getOrderById(orderId);
if (!order) {
return res.status(404).json({ error: '注文が見つかりません' });
}
if (order.userId !== userId) {
return res.status(403).json({ error: 'アクセス権限がありません' });
}
res.json(order);
} catch (error) {
res.status(500).json({ error: 'サーバーエラー' });
}
});
2. レート制限の実装
Express Rate Limitを使用:
const rateLimit = require('express-rate-limit');
// 一般的なAPI用レート制限
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分
max: 100, // 最大100リクエスト
message: {
error: 'レート制限に達しました。しばらく待ってから再試行してください。'
},
standardHeaders: true,
legacyHeaders: false,
});
// ログイン用の厳しいレート制限
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分
max: 5, // 最大5回のログイン試行
skipSuccessfulRequests: true,
message: {
error: 'ログイン試行回数が上限に達しました。15分後に再試行してください。'
}
});
app.use('/api/', apiLimiter);
app.use('/api/login', loginLimiter);
3. 入力値の検証
express-validatorを使用した検証:
const { body, param, validationResult } = require('express-validator');
// バリデーションルール
const userValidationRules = () => {
return [
body('email').isEmail().normalizeEmail(),
body('name').isLength({ min: 2, max: 50 }).trim().escape(),
body('age').isInt({ min: 0, max: 150 }),
param('id').isUUID()
];
}
const validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
error: 'バリデーションエラー',
details: errors.array()
});
}
next();
}
// 使用例
app.post('/api/users', userValidationRules(), validate, (req, res) => {
// バリデーション済みのデータを処理
createUser(req.body);
});
4. セキュアなエラーハンドリング
// 安全なエラーハンドリング
app.use((err, req, res, next) => {
// ログには詳細なエラー情報を記録
console.error('API Error:', {
error: err.message,
stack: err.stack,
url: req.url,
method: req.method,
ip: req.ip,
userAgent: req.get('User-Agent')
});
// クライアントには安全なエラーメッセージのみ返す
const isDevelopment = process.env.NODE_ENV === 'development';
res.status(err.status || 500).json({
error: 'サーバーエラーが発生しました',
...(isDevelopment && { details: err.message })
});
});
5. CORS設定
const cors = require('cors');
// 厳格なCORS設定
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
'https://example.com',
'https://app.example.com'
];
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('CORS ポリシーにより拒否されました'));
}
},
credentials: true,
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
フレームワーク別の実装例
Laravel(PHP)
<?php
// Laravelでの安全なAPI実装
// ミドルウェアによる認証
class ApiAuthMiddleware {
public function handle($request, Closure $next) {
$token = $request->bearerToken();
if (!$token || !$this->validateToken($token)) {
return response()->json(['error' => '認証が必要です'], 401);
}
return $next($request);
}
}
// レート制限
Route::middleware(['auth:api', 'throttle:100,1'])->group(function () {
Route::get('/users/{id}', [UserController::class, 'show']);
});
// リソースによる情報制限
class UserResource extends JsonResource {
public function toArray($request) {
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
// パスワードや内部情報は除外
];
}
}
// コントローラー
class UserController extends Controller {
public function show(Request $request, $id) {
$user = User::findOrFail($id);
// 認可チェック
if ($request->user()->cannot('view', $user)) {
return response()->json(['error' => '権限がありません'], 403);
}
return new UserResource($user);
}
}
?>
Django REST Framework(Python)
# Djangoでの安全なAPI実装
from rest_framework import status, permissions
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.throttling import UserRateThrottle
from django.core.exceptions import PermissionDenied
class APIRateThrottle(UserRateThrottle):
rate = '100/hour'
class LoginRateThrottle(UserRateThrottle):
rate = '5/min'
@api_view(['GET'])
@permission_classes([permissions.IsAuthenticated])
def get_user_orders(request, user_id):
try:
# 認可チェック
if request.user.id != user_id and not request.user.is_staff:
raise PermissionDenied("他のユーザーの注文にはアクセスできません")
orders = Order.objects.filter(user_id=user_id)
serializer = OrderSerializer(orders, many=True)
return Response(serializer.data)
except PermissionDenied as e:
return Response(
{'error': str(e)},
status=status.HTTP_403_FORBIDDEN
)
except Exception as e:
logger.error(f"API Error: {str(e)}")
return Response(
{'error': 'サーバーエラーが発生しました'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
# Serializer で情報を制限
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'first_name', 'last_name']
# password, is_staff などの機密情報は除外
APIセキュリティテストの実施
1. 自動化テスト
// Jest を使用したAPIセキュリティテスト
describe('API Security Tests', () => {
test('未認証リクエストは401を返すべき', async () => {
const response = await request(app)
.get('/api/users/123')
.expect(401);
});
test('他人のリソースへのアクセスは403を返すべき', async () => {
const token = await getValidToken('user1');
const response = await request(app)
.get('/api/users/456') // user2のID
.set('Authorization', `Bearer ${token}`)
.expect(403);
});
test('レート制限が正しく動作するべき', async () => {
const token = await getValidToken();
// 100回リクエストを送信
for (let i = 0; i < 100; i++) {
await request(app)
.get('/api/data')
.set('Authorization', `Bearer ${token}`)
.expect(200);
}
// 101回目は429 (Too Many Requests) を返すべき
await request(app)
.get('/api/data')
.set('Authorization', `Bearer ${token}`)
.expect(429);
});
});
2. 手動ペネトレーションテスト
# APIエンドポイントの探索
curl -X GET "https://api.example.com/api/v1/"
curl -X OPTIONS "https://api.example.com/api/v1/users"
# 認証迂回テスト
curl -X GET "https://api.example.com/api/v1/admin/users"
curl -X GET "https://api.example.com/api/v1/users/1" \
-H "Authorization: Bearer invalid_token"
# SQLインジェクションテスト
curl -X GET "https://api.example.com/api/v1/search?q=' OR '1'='1"
# レート制限テスト
for i in {1..200}; do
curl -X POST "https://api.example.com/api/v1/login" \
-d "username=test&password=test"
done
最新のAPIセキュリティトレンド
1. GraphQL セキュリティ
// GraphQL の深度制限
const depthLimit = require('graphql-depth-limit');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(5)] // 最大深度5に制限
});
// クエリ複雑性の制限
const costAnalysis = require('graphql-cost-analysis');
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
costAnalysis({
maximumCost: 1000,
createError: (max, actual) => {
return new Error(`クエリが複雑すぎます: ${actual} > ${max}`);
}
})
]
});
2. API Gateway の活用
# Kong API Gateway の設定例
services:
- name: user-service
url: http://user-service:3000
plugins:
- name: rate-limiting
config:
minute: 100
hour: 1000
- name: key-auth
config:
key_names: ["apikey"]
- name: cors
config:
origins: ["https://example.com"]
3. OAuth 2.0 / OpenID Connect
// OAuth 2.0 実装例
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/auth/google/callback"
}, (accessToken, refreshToken, profile, done) => {
// ユーザー情報の処理
User.findOrCreate({ googleId: profile.id }, (err, user) => {
return done(err, user);
});
}));
実際の被害事例
事例1:大手SNSサービスのAPI脆弱性(2023年)
某大手SNSで発見されたBOLA脆弱性:
問題のAPI:
GET /api/v2/users/{user_id}/private_messages
脆弱性:
- 認証は必要だが、user_idの所有者確認なし
- 他のユーザーのプライベートメッセージを閲覧可能
被害:
- 約500万ユーザーのプライベートメッセージが潜在的に漏洩
- 1年間発見されずに放置
- GDPR違反で巨額の制裁金
事例2:ECサイトのレート制限不備(2023年)
攻撃手法:
- 商品情報取得APIにレート制限なし
- 攻撃者が1秒間に1000回のリクエスト
- 全商品の在庫情報と価格情報を取得
- 競合他社が価格情報を悪用
被害:
- 商業機密の漏洩
- 競争力の低下
- サーバー負荷によるサービス停止
まとめ
APIセキュリティは、現代のWeb開発において最も重要なセキュリティ課題の一つです。
重要なポイント:
- 適切な認証・認可の実装(JWT、OAuth 2.0)
- レート制限による自動化攻撃の防止
- 入力値の検証とSQLインジェクション対策
- 情報の適切な制限(必要最小限の情報のみ返す)
- セキュアなエラーハンドリング
- 定期的なセキュリティテストの実施
特に重要なのは、セキュリティを設計段階から組み込むことです。後からセキュリティを追加するのではなく、API設計の段階からセキュリティを考慮した実装を行いましょう。
APIは外部からアクセス可能で、自動化攻撃の標的になりやすいため、従来のWebアプリケーション以上に厳格なセキュリティ対策が必要です。この記事で紹介した対策を参考に、安全なAPIを構築してください。
コメント