+ 확정 사항과 문맥 의존 위험을 가장 정확히 구분했다.
- 인가 누락을 확정 항목에 묶은 부분은 다소 모호하다.
외부 입력이 들어오는 경로를 따라가며 확인합니다. 발견 못 한 영역도 말합니다.
| 분류 | 개발 › 코딩 |
|---|---|
| 태그 | 검토코드개발자 |
Review this code for security issues. Work by following untrusted input, not by scanning for patterns: 1. **Entry points** — every place external input enters. Request bodies, query strings, headers, file uploads, webhooks, message queues, environment. 2. For each, trace where it flows: into queries, into commands, into file paths, into templates, into deserialization, into logs, back into a response. 3. At each destination, say whether it is neutralized and how. Name the specific mechanism, not "it is validated". 4. **Authorization** — for every operation, is the check present, and is it checking the right subject against the right object. Missing object-level checks are the most common real finding and the least likely to be caught by scanners. 5. Secrets — hardcoded, logged, returned in errors, committed. 6. What errors reveal to the caller. Report per finding: the path from entry to impact, what an attacker gets, and the fix. Rules: - *Never conclude that the code is secure.* Report what you checked and what you could not. Absence of a finding is not evidence of safety. - **State explicitly what the code alone cannot tell you** — framework defaults, middleware applied elsewhere, infrastructure controls, what the caller already authenticated. - Separate confirmed issues from things that depend on context you cannot see. - Do not pad with generic advice. If there is nothing at a given step, say so.
보안 점검은 "안전합니다"가 제일 위험한 답이다. 이 프롬프트는 입력 경로를 따라가며 확인하고, 코드만으로는 판단 못 하는 부분을 분명히 남긴다.
ChatGPT는 흐름 추적과 불확실성 구분이 가장 충실하다. Gemini는 구체적이나 과도한 확정과 장황함이 있고, [C]는 답이 없다.
+ 확정 사항과 문맥 의존 위험을 가장 정확히 구분했다.
- 인가 누락을 확정 항목에 묶은 부분은 다소 모호하다.
+ 입력에서 SQL·응답까지 흐름과 수정안을 구체화했다.
- 추정 위험을 확정 취약점·공격 결과처럼 과장했다.
| 기준 | ChatGPT | Gemini | 앞선 모델 |
|---|---|---|---|
| 지시 준수 | 9 | 7 | ChatGPT +29% |
| 정확성 | 9 | 6 | ChatGPT +50% |
| 구체성 | 9 | 9 | 동점 |
| 구성·가독성 | 9 | 8 | ChatGPT +13% |
| 분량 적정성 | 8 | 6 | ChatGPT +33% |
gpt-5.6-sol가 모델 이름을 가린 채 1~10점으로 채점했습니다 (2026-09-24). 측정값이 아니라 AI 심사 결과입니다.
같은 입력을 세 모델에 넣어 받은 답을 그대로 옮겼습니다. 각 CLI(에이전트 하네스)에서 실행했습니다. ChatGPT·Claude 앱이나 웹에서는 답이 다를 수 있습니다.
코드: // routes/admin.js
router.get('/api/admin/users/:id/orders', (req, res) => {
const userId = req.params.id;
const query = `SELECT * FROM orders WHERE user_id = ${userId}`;
db.query(query, (err, rows) => {
if (err) return res.status(500).json({ error: err.message });
res.json(rows);
});
});
router.post('/api/admin/refund', (req, res) => {
const { orderId, amount } = req.body;
refundService.refund(orderId, amount);
res.json({ ok: true });
});
실행 환경·신뢰 경계: 내부 관리자용 Node.js/Express API, AWS EC2 단일 인스턴스에 배포. 프론트는 React 어드민 페이지에서 호출. 로그인 시 발급한 JWT를 헤더에 실어 보내고 미들웨어에서 검증은 하지만, role(super_admin/staff) 체크는 일부 라우트에만 걸려 있습니다.
다루는 데이터: 고객 이름, 전화번호, 배송지 주소, 주문 금액, 환불 내역 등 결제·개인정보를 다룹니다.경로:
req.params.id → 문자열 템플릿 삽입 → SQL 쿼리 → 주문 목록 반환
const query = `SELECT * FROM orders WHERE user_id = ${userId}`;
userId를 숫자로 검증하거나 이스케이프하지 않고 SQL 문에 직접 삽입합니다. 공격자는 다음처럼 쿼리 구조를 변경할 수 있습니다.
/api/admin/users/1 OR 1=1/orders
영향:
SELECT *로 주문에 포함된 불필요한 민감 필드까지 노출 가능또한 JWT 검증만으로는 충분하지 않습니다. 이 코드에는 현재 요청자가 super_admin 또는 허용된 staff인지, 그리고 해당 userId 객체에 접근할 권한이 있는지 확인하는 코드가 없습니다. 내부 관리자 API라도 직원 계정 간 접근 범위가 다르다면 IDOR/BOLA입니다.
수정:
router.get('/api/admin/users/:id/orders', requireJwt, requireAdminRole,
async (req, res) => {
const userId = Number(req.params.id);
if (!Number.isSafeInteger(userId) || userId <= 0) {
return res.status(400).json({ error: 'invalid user id' });
}
// 정책에 따라 객체 접근 권한 확인
if (!canViewUser(req.user, userId)) {
return res.status(403).json({ error: 'forbidden' });
}
const [rows] = await db.query(
'SELECT id, user_id, amount, status, created_at FROM orders WHERE user_id = ?',
[userId]
);
res.json(rows);
}
);
숫자 변환만으로 SQL 인젝션 방어를 대체하지 말고, 반드시 DB 드라이버의 파라미터 바인딩을 사용해야 합니다.
경로:
DB 오류 → err.message → HTTP 응답
res.status(500).json({ error: err.message });
공격자는 SQL 문, 테이블명, 컬럼명, DB 호스트 정보 또는 드라이버 내부 세부사항을 얻을 수 있습니다. 특히 위 SQL 인젝션을 시도할 때 오류 기반 탐색에도 사용됩니다.
수정:
logger.error({ err, requestId: req.id }, 'order lookup failed');
return res.status(500).json({ error: 'internal server error' });
경로:
req.body.orderId, req.body.amount → refundService.refund(orderId, amount) → 환불 처리
이 라우트에는 다음 검사가 보이지 않습니다.
super_admin인지, 환불 가능한 staff인지orderId가 실제 주문인지refundService 내부에서 모두 처리한다면 일부 문제는 없을 수 있지만, 제공된 코드만으로는 확인할 수 없습니다. 서비스에 검사가 없다면 공격자는 유효한 JWT를 가진 낮은 권한 직원으로 다른 고객의 주문을 환불하거나, 금액 조작·중복 환불을 발생시킬 수 있습니다.
수정 방향:
orderId, amount 타입·범위 검증refundService.refund(orderId, amount);
res.json({ ok: true });
refund()가 비동기 함수라면 완료를 기다리지 않습니다.
영향:
수정:
try {
const result = await refundService.refund(orderId, amount);
return res.json({ ok: true, refundId: result.id });
} catch (err) {
logger.error({ err, requestId: req.id }, 'refund failed');
return res.status(502).json({ error: 'refund failed' });
}
단, 서비스가 동기 함수인지와 내부 트랜잭션 처리는 코드 밖의 정보입니다.
id: SQL에 직접 삽입되므로 확인된 SQL 인젝션입니다.orderId, amount: 서비스 내부 구현과 검증이 보이지 않아 잠재적 권한 우회·금액 조작 문제입니다.refundService가 권한, 금액, 상태, 중복 환불을 검증하는지따라서 이 코드만으로 가장 확정적인 취약점은 SQL 인젝션과 DB 오류 정보 노출입니다. 환불 API의 권한 우회 및 금액 조작은 refundService와 실제 라우터 미들웨어를 추가로 확인해야 확정할 수 있습니다.
| 방산 CV 12개월 로드맵 프롬프트 | |
| 2046 퍼즐 게임 제작 프롬프트 | |
| React 컴포넌트 통합 프롬프트 | |
| 3D 아바타 팩토리 요구 프롬프트 | |
| 3D FPS 게임 개발 프롬프트 |