jpskill.com
🛠️ 開発・MCP コミュニティ

owasp-security

OWASP Top 10に沿ってセキュリティ脆弱性を予防し、認証やAPI保護、セキュリティレビューを行うための安全なコーディング実践を支援するSkill。

📜 元の英語説明(参考)

遵循 OWASP Top 10 实施安全编码实践。适用于预防安全漏洞、实现认证、保护 API 或进行安全审查。触发关键词:OWASP, security, XSS, SQL injection, CSRF, authentication security, secure coding, vulnerability, 安全编码。

🇯🇵 日本人クリエイター向け解説

一言でいうと

OWASP Top 10に沿ってセキュリティ脆弱性を予防し、認証やAPI保護、セキュリティレビューを行うための安全なコーディング実践を支援するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o owasp-security.zip https://jpskill.com/download/6048.zip && unzip -o owasp-security.zip && rm owasp-security.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/6048.zip -OutFile "$d\owasp-security.zip"; Expand-Archive "$d\owasp-security.zip" -DestinationPath $d -Force; ri "$d\owasp-security.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して owasp-security.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → owasp-security フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-17
取得日時
2026-05-17
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

OWASP セキュリティのベストプラクティス

OWASP Top 10(2021)ガイドラインに従って、セキュアコーディングを実装します。

OWASP Top 10(2021)

# 脆弱性 予防策
A01 アクセス制御の不備 適切な認可チェック
A02 暗号化の失敗 強力な暗号化、セキュアなストレージ
A03 インジェクション 入力検証、パラメータ化クエリ
A04 安全でない設計 脅威モデリング、セキュアなパターン
A05 セキュリティ設定のミス 強化された設定、デフォルト値の不使用
A06 脆弱なコンポーネント 依存関係スキャン、アップデート
A07 認証の失敗 MFA、セキュアなセッション管理
A08 ソフトウェアとデータの整合性の失敗 入力検証、署名付きアップデート
A09 ログと監視の失敗 包括的な監査ログ
A10 サーバサイドリクエストフォージェリ(SSRF) URL検証、許可リスト
A09 Logging Failures Comprehensive audit logs
A10 SSRF URL validation, allowlists

A01: アクセス制御の不備

予防パターン

// ✗ 誤り:認可チェックなし
app.get('/api/users/:id', async (req, res) => {
  const user = await db.users.findById(req.params.id);
  res.json(user);
});

// ✓ 正しい:所有権の検証
app.get('/api/users/:id', authenticate, async (req, res) => {
  const userId = req.params.id;

  // ユーザーは自分のデータのみにアクセスできます
  if (req.user.id !== userId && req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Forbidden' });
  }

  const user = await db.users.findById(userId);
  res.json(user);
});

// ✓ 正しい:ロールベースのアクセス制御 (RBAC)
const requireRole = (...roles: string[]) => {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!roles.includes(req.user?.role)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    next();
  };
};

app.delete('/api/posts/:id', authenticate, requireRole('admin', 'moderator'), deletePost);

不安全な直接オブジェクト参照 (IDOR)

// ✗ 誤り:予測可能なIDの露出
GET /api/invoices/1001
GET /api/invoices/1002  // 他人の請求書を列挙できる

// ✓ 正しい:UUID + 所有権チェックの使用
app.get('/api/invoices/:id', authenticate, async (req, res) => {
  const invoice = await db.invoices.findOne({
    id: req.params.id,
    userId: req.user.id,  // 所有権を強制
  });

  if (!invoice) {
    return res.status(404).json({ error: 'Not found' });
  }

  res.json(invoice);
});

A02: 暗号化の失敗

パスワードハッシュ

import bcrypt from 'bcrypt';
import crypto from 'crypto';

// ✓ bcrypt を使用してパスワードをハッシュ化
const SALT_ROUNDS = 12;

async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, SALT_ROUNDS);
}

async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

// ✓ 安全なトークン生成
function generateSecureToken(length = 32): string {
  return crypto.randomBytes(length).toString('hex');
}

// ✓ 機密データの暗号化
const ALGORITHM = 'aes-256-gcm';
const KEY = crypto.scryptSync(process.env.ENCRYPTION_KEY!, 'salt', 32);

function encrypt(text: string): { encrypted: string; iv: string; tag: string } {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);

  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');

  return {
    encrypted,
    iv: iv.toString('hex'),
    tag: cipher.getAuthTag().toString('hex'),
  };
}

function decrypt(encrypted: string, iv: string, tag: string): string {
  const decipher = crypto.createDecipheriv(ALGORITHM, KEY, Buffer.from(iv, 'hex'));
  decipher.setAuthTag(Buffer.from(tag, 'hex'));

  let decrypted = decipher.update(encrypted, 'hex', 'utf8');
  decrypted += decipher.final('utf8');

  return decrypted;
}

セキュリティヘッダー

import helmet from 'helmet';

app.use(helmet());
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true }));
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'strict-dynamic'"],
    styleSrc: ["'self'", "'unsafe-inline'"],
    imgSrc: ["'self'", 'data:', 'https:'],
    connectSrc: ["'self'"],
    fontSrc: ["'self'"],
    objectSrc: ["'none'"],
    frameAncestors: ["'none'"],
  },
}));

A03: インジェクション攻撃

SQLインジェクション対策

// ✗ 誤り:文字列連結
const query = `SELECT * FROM users WHERE email = '${email}'`;

// ✓ 正しい:パラメータ化クエリ
// With Prisma
const user = await prisma.user.findUnique({ where: { email } });

// 生のSQLを使用(パラメータ化)
const user = await db.query('SELECT * FROM users WHERE email = $1', [email]);

// With Knex
const user = await knex('users').where({ email }).first();

NoSQLインジェクション対策

// ✗ 誤り:ユーザー入力をクエリに直接使用
const user = await User.findOne({ username: req.body.username });
// 攻撃: { "username": { "$gt": "" } } は最初のユーザーを返す

// ✓ 正しい:入力タイプの検証
import { z } from 'zod';

const loginSchema = z.object({
  username: z.string().min(3).max(50),
  password: z.string().min(8),
});

app.post('/login', async (req, res) => {
  const { username, password } = loginSchema.parse(req.body);
  const user = await User.findOne({ username: String(username) });
  // ...
});

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

import { execFile } from 'child_process';

// ✗ 誤り:シェルインジェクション
exec(`convert ${userInput} output.png`);  // userInput: "; rm -rf /"

// ✓ 正しい:execFile と配列引数を使用
execFile('convert', [userInput, 'output.png'], (error, stdout) => {
  // 安全 - 引数はシェルによって解釈されません
});

// ✓ 正しい:検証とサニタイズ
const allowedFormats = ['png', 'jpg', 'gif'];
if (!allowedFormats.includes(format)) {
  throw new Error('Invalid format');
}

A04: 安全でない設計

レート制限

import rateLimit from 'express-rate-limit';

// 一般的なレート制限
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // 100 requests per window
  standardHeaders: true,
  legacyHeaders: false,
});

// 認証エンドポイントの厳格な制限
const authLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 5, // 5 failed attempts
  skipSuccessfulRequests: true,
});

app.use('/api/', limiter);
app.use('/api/auth/', authLimiter);

入力検証

import { z } from 'zod';

const userSchema = z.object({
  email: z.string().email(),
  password: z.string()
    .min(8)
    .regex(/[A-Z]/, 'Must contain uppercase')
    .regex(/[a-z]/, 'Must contain lowercase')
    .regex(/[0-9]/, 'Must contain number')
    .regex(/[^A-Za-z0-9]/, 'Must contain special character'),
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

OWASP 安全最佳实践

遵循 OWASP Top 10(2021)指南实施安全编码。

OWASP Top 10(2021)

# Vulnerability Prevention
A01 失效的访问控制 Proper authorization checks
A02 加密失败 Strong encryption, secure storage
A03 注入攻击 Input validation, parameterized queries
A04 不安全设计 Threat modeling, secure patterns
A05 安全配置错误 Hardened configs, no defaults
A06 易受攻击的组件 Dependency scanning, updates
A07 认证失败 MFA, secure session management
A08 软件和数据完整性失败 Input validation, signed updates
A09 日志和监控失败 Comprehensive audit logs
A10 服务器端请求伪造(SSRF) URL validation, allowlists
A09 Logging Failures Comprehensive audit logs
A10 SSRF URL validation, allowlists

A01: 失效的访问控制

预防模式

// ✗ 错误:没有授权检查
app.get('/api/users/:id', async (req, res) => {
  const user = await db.users.findById(req.params.id);
  res.json(user);
});

// ✓ 正确:验证所有权
app.get('/api/users/:id', authenticate, async (req, res) => {
  const userId = req.params.id;

  // 用户只能访问自己的数据
  if (req.user.id !== userId && req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Forbidden' });
  }

  const user = await db.users.findById(userId);
  res.json(user);
});

// ✓ 正确:基于角色的访问控制 (RBAC)
const requireRole = (...roles: string[]) => {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!roles.includes(req.user?.role)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    next();
  };
};

app.delete('/api/posts/:id', authenticate, requireRole('admin', 'moderator'), deletePost);

不安全的直接对象引用 (IDOR)

// ✗ 错误:可预测的 ID 暴露
GET /api/invoices/1001
GET /api/invoices/1002  // 可以枚举他人的发票

// ✓ 正确:使用 UUID + 所有权检查
app.get('/api/invoices/:id', authenticate, async (req, res) => {
  const invoice = await db.invoices.findOne({
    id: req.params.id,
    userId: req.user.id,  // 强制所有权
  });

  if (!invoice) {
    return res.status(404).json({ error: 'Not found' });
  }

  res.json(invoice);
});

A02: 加密失败

密码哈希

import bcrypt from 'bcrypt';
import crypto from 'crypto';

// ✓ 使用 bcrypt 哈希密码
const SALT_ROUNDS = 12;

async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, SALT_ROUNDS);
}

async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

// ✓ 安全令牌生成
function generateSecureToken(length = 32): string {
  return crypto.randomBytes(length).toString('hex');
}

// ✓ 加密敏感数据
const ALGORITHM = 'aes-256-gcm';
const KEY = crypto.scryptSync(process.env.ENCRYPTION_KEY!, 'salt', 32);

function encrypt(text: string): { encrypted: string; iv: string; tag: string } {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);

  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');

  return {
    encrypted,
    iv: iv.toString('hex'),
    tag: cipher.getAuthTag().toString('hex'),
  };
}

function decrypt(encrypted: string, iv: string, tag: string): string {
  const decipher = crypto.createDecipheriv(ALGORITHM, KEY, Buffer.from(iv, 'hex'));
  decipher.setAuthTag(Buffer.from(tag, 'hex'));

  let decrypted = decipher.update(encrypted, 'hex', 'utf8');
  decrypted += decipher.final('utf8');

  return decrypted;
}

安全头

import helmet from 'helmet';

app.use(helmet());
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true }));
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'strict-dynamic'"],
    styleSrc: ["'self'", "'unsafe-inline'"],
    imgSrc: ["'self'", 'data:', 'https:'],
    connectSrc: ["'self'"],
    fontSrc: ["'self'"],
    objectSrc: ["'none'"],
    frameAncestors: ["'none'"],
  },
}));

A03: 注入攻击

SQL 注入防护

// ✗ 错误:字符串拼接
const query = `SELECT * FROM users WHERE email = '${email}'`;

// ✓ 正确:参数化查询
// With Prisma
const user = await prisma.user.findUnique({ where: { email } });

// 使用原始 SQL(参数化)
const user = await db.query('SELECT * FROM users WHERE email = $1', [email]);

// With Knex
const user = await knex('users').where({ email }).first();

NoSQL 注入防护

// ✗ 错误:直接在查询中使用用户输入
const user = await User.findOne({ username: req.body.username });
// 攻击: { "username": { "$gt": "" } } 返回第一个用户

// ✓ 正确:验证输入类型
import { z } from 'zod';

const loginSchema = z.object({
  username: z.string().min(3).max(50),
  password: z.string().min(8),
});

app.post('/login', async (req, res) => {
  const { username, password } = loginSchema.parse(req.body);
  const user = await User.findOne({ username: String(username) });
  // ...
});

命令注入防护

import { execFile } from 'child_process';

// ✗ 错误:Shell 注入
exec(`convert ${userInput} output.png`);  // userInput: "; rm -rf /"

// ✓ 正确:使用 execFile 和数组参数
execFile('convert', [userInput, 'output.png'], (error, stdout) => {
  // 安全 - 参数不会被 shell 解释
});

// ✓ 正确:验证和清理
const allowedFormats = ['png', 'jpg', 'gif'];
if (!allowedFormats.includes(format)) {
  throw new Error('Invalid format');
}

A04: 不安全设计

速率限制

import rateLimit from 'express-rate-limit';

// 通用速率限制
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // 100 requests per window
  standardHeaders: true,
  legacyHeaders: false,
});

// 认证端点的严格限制
const authLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 5, // 5 failed attempts
  skipSuccessfulRequests: true,
});

app.use('/api/', limiter);
app.use('/api/auth/', authLimiter);

输入验证

import { z } from 'zod';

const userSchema = z.object({
  email: z.string().email(),
  password: z.string()
    .min(8)
    .regex(/[A-Z]/, 'Must contain uppercase')
    .regex(/[a-z]/, 'Must contain lowercase')
    .regex(/[0-9]/, 'Must contain number')
    .regex(/[^A-Za-z0-9]/, 'Must contain special character'),
  age: z.number().int().min(13).max(120),
  role: z.enum(['user', 'admin']).default('user'),
});

app.post('/api/users', async (req, res) => {
  try {
    const data = userSchema.parse(req.body);
    // 验证后的数据可以安全使用
  } catch (error) {
    if (error instanceof z.ZodError) {
      return res.status(400).json({ errors: error.errors });
    }
    throw error;
  }
});

A05: 安全配置错误

环境配置

// ✅ 生产环境不要暴露堆栈跟踪
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(err.stack); // 记录以便调试

  res.status(500).json({
    error: process.env.NODE_ENV === 'production' 
      ? 'Internal server error' 
      : err.message,
  });
});

// ✅ 禁用敏感头
app.disable('x-powered-by');

// ✅ 安全 cookie 配置
app.use(session({
  secret: process.env.SESSION_SECRET!,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    sameSite: 'strict',
    maxAge: 24 * 60 * 60 * 1000, // 24 hours
  },
  resave: false,
  saveUninitialized: false,
}));

A06: 易受攻击的组件

依赖扫描

# 检查漏洞
npm audit
npm audit fix

# 使用 Snyk 进行更深入的扫描
npx snyk test
npx snyk monitor

# 保持依赖更新
npx npm-check-updates -u
// package.json - 使用精确版本或范围
{
  "dependencies": {
    "express": "^4.18.0",  // 允许次版本更新
    "lodash": "4.17.21"    // 精确版本
  },
  "overrides": {
    "vulnerable-package": "^2.0.0"  // 强制安全版本
  }
}

A07: 认证失败

安全会话管理

import jwt from 'jsonwebtoken';

// ✅ JWT 短期过期 + 刷新令牌
function generateTokens(userId: string) {
  const accessToken = jwt.sign(
    { userId },
    process.env.JWT_SECRET!,
    { expiresIn: '15m' }  // 短有效期
  );

  const refreshToken = jwt.sign(
    { userId, type: 'refresh' },
    process.env.JWT_REFRESH_SECRET!,
    { expiresIn: '7d' }
  );

  return { accessToken, refreshToken };
}

// ✅ 安全密码重置
async function initiatePasswordReset(email: string) {
  const user = await db.users.findByEmail(email);
  if (!user) return; // 不要透露邮箱是否存在

  const token = crypto.randomBytes(32).toString('hex');
  const hashedToken = crypto.createHash('sha256').update(token).digest('hex');

  await db.passwordResets.create({
    userId: user.id,
    token: hashedToken,
    expiresAt: new Date(Date.now() + 60 * 60 * 1000), // 1 hour
  });

  await sendEmail(email, `Reset link: /reset?token=${token}`);
}

多因素认证

import { authenticator } from 'otplib';
import QRCode from 'qrcode';

// 设置 TOTP
async function setupMFA(userId: string) {
  const secret = authenticator.generateSecret();
  const otpauth = authenticator.keyuri(userId, 'MyApp', secret);
  const qrCode = await QRCode.toDataURL(otpauth);

  await db.users.update(userId, { mfaSecret: encrypt(secret) });

  return { qrCode, secret };
}

// 验证 TOTP
function verifyMFA(token: string, secret: string): boolean {
  return authenticator.verify({ token, secret });
}

A08: XSS 防护

// ✅ React 默认自动转义
const UserProfile = ({ user }) => (
  <div>{user.name}</div>  // 安全 - 自动转义
);

// ⚠️ 危险 - 尽量避免
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />

// ✅ 如需要则清理 HTML
import DOMPurify from 'dompurify';

const sanitizedHtml = DOMPurify.sanitize(userHtml, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
  ALLOWED_ATTR: ['href'],
});

// ✅ 内容安全策略
app.use(helmet.contentSecurityPolicy({
  directives: {
    scriptSrc: ["'self'"],  // 禁止内联脚本
    styleSrc: ["'self'", "'unsafe-inline'"],
  },
}));

A09: 日志与监控

import winston from 'winston';

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
  ],
});

// ✅ 记录安全事件
function logSecurityEvent(event: string, details: object) {
  logger.warn({
    type: 'security',
    event,
    ...details,
    timestamp: new Date().toISOString(),
  });
}

// 使用示例
logSecurityEvent('failed_login', { email, ip: req.ip, userAgent: req.headers['user-agent'] });
logSecurityEvent('access_denied', { userId, resource, action });
logSecurityEvent('suspicious_activity', { userId, pattern: 'rapid_requests' });

A10: SSRF 防护

import { URL } from 'url';

// ✅ 根据白名单验证 URL
const ALLOWED_HOSTS = ['api.example.com', 'cdn.example.com'];

function isAllowedUrl(urlString: string): boolean {
  try {
    const url = new URL(urlString);

    // 阻止私有 IP
    const privatePatterns = [
      /^localhost$/i,
      /^127\./,
      /^10\./,
      /^172\.(1[6-9]|2[0-9]|3[01])\./,
      /^192\.168\./,
      /^0\./,
      /^169\.254\./,  // 链路本地
    ];

    if (privatePatterns.some(p => p.test(url.hostname))) {
      return false;
    }

    // 检查白名单
    return ALLOWED_HOSTS.includes(url.hostname);
  } catch {
    return false;
  }
}

app.post('/api/fetch-url', async (req, res) => {
  const { url } = req.body;

  if (!isAllowedUrl(url)) {
    return res.status(400).json({ error: 'URL not allowed' });
  }

  const response = await fetch(url);
  // ...
});

安全检查清单

## 部署前检查清单

### 认证
- [ ] 密码使用 bcrypt 哈希 (cost ≥ 12)
- [ ] JWT 令牌有短有效期
- [ ] 会话 cookie 设置 httpOnly, secure, sameSite
- [ ] 认证端点有速率限制

### 授权
- [ ] 所有端点有认证检查
- [ ] RBAC 正确实现
- [ ] 无 IDOR 漏洞

### 输入/输出
- [ ] 所有输入使用 Zod/Joi 验证
- [ ] SQL 查询参数化
- [ ] XSS 防护 (CSP, 转义)
- [ ] 文件上传验证和沙箱化

### 基础设施
- [ ] 强制 HTTPS
- [ ] 配置安全头
- [ ] 依赖已审计
- [ ] 密钥在环境变量中

### 监控
- [ ] 安全事件已记录
- [ ] 错误监控已启用
- [ ] 警报已配置

相关资源