Files
2nd/10_Wiki/Topics/Coding/Security_OWASP_API_Top10.md
T
2026-05-10 22:08:15 +09:00

7.7 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
security-owasp-api-top10 OWASP API Top 10 — modern API security Coding draft B conceptual 2026-05-09 2026-05-09
security
api
vibe-coding
language applicable_to
TS
Backend
Security
OWASP API
BOLA
broken authorization
mass assignment
API security
2023

OWASP API Top 10 (2023)

API 의 가장 흔한 vulnerability. BOLA, broken auth, mass assignment, ....

📖 핵심 개념

  • 매 endpoint 가 attack surface.
  • Web OWASP 와 다름 (API-specific).
  • Access control 가 가장 흔한 problem.

💻 코드 패턴

API1: BOLA (Broken Object Level Authorization)

// ❌ User A 가 User B 의 data 가져옴
app.get('/api/users/:id', (req, res) => {
  const user = await db.users.findById(req.params.id);
  res.json(user);
});

// ✅ Authorization check
app.get('/api/users/:id', (req, res) => {
  const user = await db.users.findById(req.params.id);
  if (user.id !== req.user.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'forbidden' });
  }
  res.json(user);
});

→ 가장 흔한 API bug. Object 의 ownership 검증.

API2: Broken Authentication

// ❌ Weak password / no rate limit
app.post('/login', async (req, res) => {
  const user = await db.users.findOne({ email: req.body.email });
  if (user && user.password === req.body.password) {
    return res.json({ token: '...' });
  }
});

// ✅
app.post('/login', rateLimit({ max: 5 }), async (req, res) => {
  const user = await db.users.findOne({ email: req.body.email });
  if (user && await bcrypt.compare(req.body.password, user.passwordHash)) {
    return res.json({ token: signJWT(user) });
  }
  await sleep(1000);  // timing attack 방지
  res.status(401).json({ error: 'invalid' });
});

→ Bcrypt + rate limit + 시간 const.

API3: BOPLA (Broken Object Property Level Authorization)

// ❌ User 가 자기 role 변경
app.patch('/api/users/:id', async (req, res) => {
  await db.users.update(req.params.id, req.body);
});
// → req.body 가 { role: 'admin' } 일 수.

// ✅ Whitelist
const ALLOWED = ['name', 'email', 'avatar'];
const update = pick(req.body, ALLOWED);
await db.users.update(req.params.id, update);

→ Mass assignment 방지.

API4: Unrestricted Resource Consumption

// ❌ 무한 result
app.get('/api/items', async (req, res) => {
  const items = await db.items.find({});  // 매 item.
  res.json(items);
});

// ✅ Pagination + limit
app.get('/api/items', async (req, res) => {
  const limit = Math.min(Number(req.query.limit) || 50, 100);
  const items = await db.items.find({}).limit(limit);
  res.json(items);
});

API5: Broken Function Level Authorization

// ❌ Admin endpoint 가 admin check 안
app.delete('/api/users/:id', (req, res) => {
  await db.users.delete(req.params.id);
});

// ✅
app.delete('/api/users/:id', requireAdmin, (req, res) => {
  await db.users.delete(req.params.id);
});

function requireAdmin(req, res, next) {
  if (!req.user?.isAdmin) return res.status(403).end();
  next();
}

API6: Unrestricted Access to Sensitive Business Flows

// ❌ 1 user 가 매 product 의 1 차 buy.
// → Reseller bot 가 모두 buy.

// ✅ Per-user limit + CAPTCHA + behavior detect
if (await purchaseCount(req.user.id, productId, '1h') > 1) {
  return res.status(429).end();
}

→ Business logic abuse.

API7: Server-Side Request Forgery (SSRF)

// ❌ User 가 임의 URL fetch
app.post('/api/preview', async (req, res) => {
  const r = await fetch(req.body.url);
  res.json(await r.text());
});
// → http://169.254.169.254/ (AWS metadata).
// → http://localhost/admin.

// ✅ Whitelist + DNS resolve check
const url = new URL(req.body.url);
if (!ALLOWED_HOSTS.includes(url.host)) return res.status(400).end();
const ip = await dns.resolve(url.host);
if (isPrivateIP(ip)) return res.status(400).end();

const r = await fetch(req.body.url);

API8: Security Misconfiguration

- Debug mode in production.
- Default credentials.
- CORS '*' (production).
- TLS 약 (1.0, 1.1).
- Verbose error message.
- No security header.

→ Helmet (Node), Rails defaults, etc.
import helmet from 'helmet';
app.use(helmet());

API9: Improper Inventory Management

- v1 API 가 deprecated 가 still up.
- Test endpoint 가 production.
- 매 endpoint 의 schema doc.

→ OpenAPI spec + audit.

API10: Unsafe Consumption of APIs

// ❌ External API 의 response 가 trust
const user = await externalAPI.getUser(id);
const html = `<div>Welcome ${user.name}</div>`;  // XSS.

// ✅ Sanitize / validate
const user = await externalAPI.getUser(id);
const safe = sanitizeHTML(user.name);

→ External API 도 input.

Common patterns

JWT 함정

// ❌ Algorithm none
jwt.verify(token, '');  // 어떤 token 도 valid.

// ✅
jwt.verify(token, SECRET, { algorithms: ['RS256'] });

Rate limit

import rateLimit from 'express-rate-limit';

app.use('/api/', rateLimit({
  windowMs: 60 * 1000,
  max: 100,  // 100 req / min / IP
}));

// Stricter for sensitive
app.post('/api/login', rateLimit({ max: 5, windowMs: 15 * 60 * 1000 }), ...);

Input validation (Zod)

const schema = z.object({
  email: z.string().email(),
  age: z.number().int().min(18),
});

app.post('/api/users', (req, res) => {
  const result = schema.safeParse(req.body);
  if (!result.success) return res.status(400).json(result.error);
  
  // result.data 가 typed + sanitized.
});

→ Strong typing + validation.

SQL injection (Prisma / Drizzle)

// ❌ String concat
const r = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);

// ✅ Parameterized
const r = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);

// Prisma 가 자동 safe
const user = await prisma.user.findUnique({ where: { id: req.params.id } });

CORS

import cors from 'cors';

app.use(cors({
  origin: ['https://app.example.com'],
  credentials: true,
}));

→ '*' 가 production X.

Audit

# OWASP ZAP
docker run owasp/zap2docker-stable zap-baseline.py -t https://api.example.com

# Burp Suite
# Postman + automated test

Bug bounty

HackerOne, Bugcrowd.
- Researcher 가 vulnerability 발견.
- Reward 가 severity 따라.
- Responsible disclosure.

→ Critical security fence.

Security_Bug_Bounty.

Pen testing

Annual / quarterly:
- 외부 firm 가 attack 시도.
- Report.
- Fix + retest.

Security_Pen_Testing.

Monitoring

- Failed login (brute force).
- Unusual request pattern.
- 4xx / 5xx spike.
- Geo anomaly.

→ Datadog / Sentry / SIEM.

Common 함정

- Test in production: skip security check.
- "내부 API 만" 가정: not.
- Auth 가 client 만: server 도.
- Rate limit per IP only: per user 도.
- CORS '*' + cookie: CSRF.
- Verbose error: leak info.

🤔 의사결정 기준

위협 방어
BOLA Authorization check
Mass assignment Whitelist
Brute force Rate limit + bcrypt
SSRF URL whitelist + IP filter
SQL injection Parameterized / ORM
XSS Sanitize + output encode
CSRF Token + SameSite cookie
Verbose error Generic message

안티패턴

  • No authorization (BOLA): data leak.
  • Bcrypt 없음: rainbow table.
  • String concat SQL: injection.
  • CORS '*': CSRF.
  • No rate limit: brute force.
  • Verbose error: info leak.
  • Production debug: stack trace leak.

🤖 LLM 활용 힌트

  • BOLA 가 #1 흔한 bug.
  • Whitelist > blacklist.
  • Bcrypt + rate limit + JWT (algorithm).
  • ZAP / Burp 가 audit.

🔗 관련 문서