OWASP Top 10·프롬프트 인젝션·패스키
이 장을 읽기 전에: 백엔드(4장)의 기본 개념, 특히 인증·인가를 파악하고 있어야 한다.
보안은 "나중에 강화하는" 것이 아니라, 처음부터 설계에 포함해야 할 품질이다. 공개된 웹 애플리케이션이나 API는 규모에 관계없이 항상 스캔된다. AI 기능을 통합하는 경우에는 기존의 웹 보안에 더하여 프롬프트 인젝션이나 과도한 툴 실행과 같은 새로운 공격면도 증가한다.
� 한국 시장 맥락: 국내에서는 개인정보보호법(PIPA)·정보통신망법·금융보안원 가이드라인 등 별도 법규가 보안 설계에 직접 영향을 미친다. ISMS-P 인증은 연매출 100억 이상 또는 일평균 이용자 100만 이상인 사업자에게 의무화되어 있으며, 이 장의 보안 원칙 대부분이 ISMS-P 통제 항목과 연결된다.
이 섹션이 답하는 질문: 어떤 위협을 우선하여 생각해야 하는가. AI 기능을 넣으면 무엇이 늘어나는가.
취약점의 대부분은 "어려운 암호화의 실패"가 아니라, 인가 누락, 입력값 취급, 설정 실수, 오래된 의존 관계의 방치와 같은 구현 실수에서 생긴다.
OWASP Top 10 for LLM Applications 2025에서는 Prompt Injection이 LLM01로 취급된다.
⚠️ 프롬프트 인젝션은 "완전 방어"보다 "피해를 작게 하는 설계"가 중요하다. LLM을 신뢰 경계의 내측에 두지 않고, 오동작해도 치명적이 되지 않는 구조로 한다.
LLM에 강한 권한을 주지 않는다
고리스크 조작은 인간 승인으로 한다
외부 문서나 검색 결과를 미신뢰 입력으로 취급한다
툴 호출을 명시적인 허가제로 한다
입출력과 툴 실행을 감사 로그에 남긴다
� 국내 AI 서비스 보안: 개인정보가 포함된 입력을 LLM에 전달하는 경우, 글로벌 LLM API(Claude·GPT 등) 사용 시 개인정보 국외 이전 동의가 필요하다. 금융·의료·공공 데이터는 NCP CLOVA Studio 또는 온프레미스 LLM을 우선 검토한다.
먼저 기존의 웹 위협을 확실하게 잡는다. AI 기능을 넣는 경우는 "LLM이 틀려도 부서지지 않는 구조" 를 추가로 설계한다.
이 섹션이 답하는 질문: 인증과 인가를 어떻게 나누어 설계하는가. 패스키는 언제 사용해야 하는가.
침해 사고에서는 "인증 방식 그 자체"보다, 인가 누락이나 세션 관리의 불비가 원인이 되는 경우가 많다.
// 카카오·네이버 OAuth 서버 측 검증 — 클라이언트 토큰을 그대로 신뢰하지 않는다
async function verifyKakaoToken(accessToken: string): Promise<KakaoUser> {
// 클라이언트에서 받은 accessToken을 카카오 서버에서 직접 검증
const response = await fetch('https://kapi.kakao.com/v2/user/me', {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (!response.ok) {
throw new AuthError('카카오 토큰 검증 실패');
}
const kakaoUser = await response.json();
// DB에서 기존 사용자 조회 또는 신규 생성
const user = await db.user.upsert({
where: { kakaoId: String(kakaoUser.id) },
create: {
kakaoId: String(kakaoUser.id),
email: kakaoUser.kakao_account?.email,
name: kakaoUser.properties?.nickname,
},
update: { lastLoginAt: new Date() },
});
return user;
}
패스키는 FIDO2 / WebAuthn을 사용한 인증 방식으로, 패스워드의 대체로 보급되고 있다.
등록된 패스키의 일람
각각의 이름이나 이용 단말의 단서
최종 이용 시각
백업 가능한지, 이미 백업됐는지
개별 삭제 도선
⚠️ 패스키에서는 "1계정에 1인증기"가 아니라, 복수 패스키를 가질 수 있는 설계가 자연스럽다. 1대의 단말에만 의존시키지 않는다.
최소한 다음을 준비한다.
복수 디바이스 또는 복수 인증기의 등록
이메일 확인이나 본인 확인을 동반하는 복구 플로 (국내: 휴대폰 본인인증 연동 권장)
관리 화면에서의 인증기 일람과 무효화 기능
UI에서 버튼을 숨겨도 인가가 되는 것은 아니다 (서버에서 반드시 체크)
API마다 서버 측에서 권한 판정한다
오브젝트 단위의 인가를 수행한다 (IDOR 방지)
관리자 권한은 역할명이 아닌 조작 단위로 재검토한다
// IDOR(Insecure Direct Object Reference) 방지 예시
// ❌ 잘못된 패턴 — 요청의 orderId를 그대로 신뢰
app.get('/orders/:orderId', async (req, res) => {
const order = await db.order.findById(req.params.orderId);
res.json(order);
});
// ✅ 올바른 패턴 — 인증된 사용자의 주문인지 서버에서 확인
app.get('/orders/:orderId', authenticate, async (req, res) => {
const order = await db.order.findFirst({
where: {
id: req.params.orderId,
userId: req.user.id, // 반드시 인증된 사용자의 것인지 확인
},
});
if (!order) {
return res.status(404).json({ error: '주문을 찾을 수 없습니다' });
}
res.json(order);
});
인증은 가능한 한 기존 기반을 사용하고, 인가는 자신들의 업무 규칙에 맞게 정성껏 구현한다. 패스키는 유력한 선택지이지만, 도입 시는 복구 도선까지 반드시 포함한다.
이 섹션이 답하는 질문: API 키나 비밀키를 어디에 두고, 누출됐을 때 어떻게 행동해야 하는가.
시크릿은 누출된 순간에 피해가 시작된다. 게다가 코드에 내장하면 Git 이력이나 로그, 채팅, CI 출력에까지 퍼지기 쉽다.
.env는 로컬 한정으로 한다
.env.example에는 값을 넣지 않는다
본번에서는 시크릿 관리 서비스를 사용한다
로그, 예외, 화면에 시크릿을 내보내지 않는다
누출 시는 이력 수정보다 먼저 실효와 로테이션을 수행한다
# .env.example — 키 이름만 표시, 값은 비워둔다
DATABASE_URL=
REDIS_URL=
KAKAO_CLIENT_ID=
KAKAO_CLIENT_SECRET=
TOSS_SECRET_KEY=
AWS_ACCESS_KEY_ID= # 실제 코드에서는 OIDC 사용 권장
AWS_SECRET_ACCESS_KEY=
실무에서는 양쪽이 필요하지만, 우선해야 할 것은 push protection과 같은 혼입 전 블록이다.
로컬은 .env, 본번은 시크릿 관리 서비스, 누출 시는 즉시 로테이션. 시크릿은 "어디에 두는가"만이 아닌
"누출되면 몇 분 안에 멈출 수 있는가"로 평가한다.
이 섹션이 답하는 질문: 라이브러리, 빌드, 생성물의 어디를 지켜야 하는가.
현대 애플리케이션은 대량의 의존 관계와 외부 서비스 위에 성립하고 있다. 직접 작성한 코드가 안전해도, 의존 라이브러리나 빌드 경로가 침해되면 같은 피해를 입는다.
# GitHub Actions — Trivy 컨테이너 스캔 + SBOM 생성
- name: Trivy 취약점 스캔
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPO }}:${{ github.sha }}
severity: CRITICAL,HIGH
exit-code: 1
- name: SBOM 생성 (CycloneDX)
uses: anchore/sbom-action@v0
with:
image: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPO }}:${{ github.sha }}
format: cyclonedx-json
output-file: sbom.cyclonedx.json
- name: SBOM 보관
uses: actions/upload-artifact@v4
with:
name: sbom-${{ github.sha }}
path: sbom.cyclonedx.json
retention-days: 365 # 1년 보관
SBOM(Software Bill of Materials)은 "이 앱에 무엇이 들어있는가"를 일람화하는 문서다. 취약성의 특정, 규제 대응, 인시던트 시의 영향 조사에서 도움이 된다. 대표적인 형식은 SPDX와 CycloneDX이 있다.
⚠️ 오래된 자료에 있는 "SLSA Level 1~4의 단순한 일직선 표"는 현재의 이해로서는 대략적이다. 현행 SLSA는 복수 트랙의 요구를 조합하여 평가하는 사고 방식이 되어 있다.
2026년 3월 시점의 현행판은 SLSA v1.2이며, 실무에서는 다음 순서로 정비하는 편이 중요하다.
빌드를 수작업이 아닌 자동화로 한다
생성물의 내력을 남긴다
서명이나 검증을 넣는다 (Cosign)
배포 전에 검증을 자동화한다
빌드 환경의 권한을 분리한다
AI 생성 코드는 "신뢰할 수 없는 초안" 으로 취급한다. 특히 다음은 인간이 반드시 확인한다.
인증·인가 (IDOR 포함)
SQL이나 템플릿 처리 (인젝션 확인)
암호화와 키 관리
외부 API나 과금 처리 (토스페이먼츠·카카오페이 등)
에러 핸들링과 타임아웃
의존 관계, CI/CD, 생성물을 함께 서플라이 체인으로 본다. 업데이트 자동화만으로는 부족하며, 내력, 서명, 검증까지 연결하여 비로소 운용이 된다.
이 섹션이 답하는 질문: 애플리케이션 본체 이외에서 무엇을 지킬 수 있는가.
// Next.js — 보안 헤더 설정 (next.config.ts)
const securityHeaders = [
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
// CSP — 카카오·네이버 SDK 허용 예시
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' https://developers.kakao.com https://static.nid.naver.com",
"connect-src 'self' https://kapi.kakao.com https://openapi.naver.com",
"img-src 'self' data: https:",
"style-src 'self' 'unsafe-inline'", // Tailwind inline style 대응
].join('; '),
},
];
export default {
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }];
},
};
CSP는 "대충 전부 금지하고 끝"이 아니다. 먼저 실제 읽기 원을 정리하고, 불필요한 인라인 스크립트나 야생 스크립트를 줄인다. 국내 서비스는 카카오·네이버·구글 SDK 등 외부 스크립트 허용이 필요한 경우가 많으므로 CSP 설계 시 미리 목록화해둔다.
HTTP 헤더는 저비용으로 효과가 크다. CSP, HSTS, Cookie 속성은 이른 단계에서 정비한다.
이 섹션이 답하는 질문: 보안을 어떻게 지속적으로 확인하는가.
중요 조작과 인증 이벤트를 감사 로그에 남긴다 (ISMS-P 필수)
알림을 누가 받고, 어떻게 초동하는지를 결정한다
누출 시의 로테이션 절차를 문서화한다
AI 기능에는 평가 세트와 레드팀 관점의 확인을 넣는다
⚠️ 스캔 도구를 넣는 것만으로는 안전해지지 않는다. 검출 결과를 누가 보고, 어떤 기한으로 고치는지까지 결정하여 비로소 기능한다.
보안은 단발의 진단이 아닌 지속 운용이다. CI의 자동 검사, 감사 로그, 인시던트 절차를 연결하여 돌린다.
이 섹션은 원문에 없는 내용으로, 한국 개발 현장에 맞게 보완한 내용이다.
## 개인정보처리시스템 기술적 보호 조치 체크리스트
### 접근 통제
- [ ] 개인정보처리시스템에 대한 접근 권한 최소화 (RBAC)
- [ ] 외부망에서 개인정보처리시스템 직접 접근 불가 (VPC 내 격리)
- [ ] 개인정보 취급자 계정 관리 (공용 계정 금지)
### 접속 기록 관리 (§29)
- [ ] 개인정보처리시스템 접속 기록 보관 (최소 6개월, 10만 이상: 2년)
- [ ] 접속 기록 위·변조 방지 (S3 Object Lock, CloudWatch Logs 변경 불가 설정)
- [ ] 접속 기록 정기 점검 (월 1회 이상)
### 암호화 (§29)
- [ ] 비밀번호 암호화 저장 (BCrypt/Argon2, 단방향)
- [ ] 주민등록번호·여권번호 암호화 저장 (AES-256, 양방향)
- [ ] 전송 구간 암호화 (TLS 1.2 이상)
- [ ] 주요 개인정보는 DB 컬럼 레벨 암호화 검토
### 악성 프로그램 방지
- [ ] 개인정보처리시스템 보안 업데이트 정기 적용
- [ ] 의존 라이브러리 취약점 스캔 자동화 (Snyk/Dependabot)
// 개인정보 마스킹 유틸리티 — 로그 출력 전 처리
export function maskPersonalInfo(data: unknown): unknown {
if (typeof data === 'string') {
return data
// 휴대폰 번호 마스킹: 010-1234-5678 → 010-****-5678
.replace(/(\d{3})-(\d{3,4})-(\d{4})/g, '$1-****-$3')
// 이메일 마스킹: test@example.com → t***@example.com
.replace(/([a-zA-Z0-9])[a-zA-Z0-9._%+-]+@/g, '$1***@')
// 주민등록번호 마스킹: 123456-1234567 → 123456-*******
.replace(/(\d{6})-(\d{7})/g, '$1-*******');
}
if (typeof data === 'object' && data !== null) {
return Object.fromEntries(
Object.entries(data as Record<string, unknown>).map(([key, value]) => [
key,
maskPersonalInfo(value),
])
);
}
return data;
}
// 사용 예 — 에러 로그 출력 전 마스킹
logger.error('결제 처리 실패', {
orderId: order.id,
// 개인정보는 마스킹 후 출력
...maskPersonalInfo({
customerPhone: order.customerPhone,
customerEmail: order.customerEmail,
}),
errorCode: error.code,
});
공공·금융 망분리 환경에서는 외부 보안 SaaS(Snyk·Dependabot 등) 직접 연결이 어려운 경우가 많다.
## 망분리 환경 보안 스캔 대안
### 취약점 스캔
- OWASP Dependency Check: 오프라인 NVD DB 다운로드 후 내부망에서 실행
- Trivy: 오프라인 DB 파일 활용 가능
- SonarQube Community Edition: 내부망 자체 호스팅
### 패키지 레지스트리
- Nexus Repository OSS 또는 JFrog Artifactory 자체 호스팅
- npm, Maven, Docker Hub 미러링
- 외부 패키지 사전 승인 후 내부 레지스트리에 등록하는 프로세스 필요
인증과 인가의 차이를 설명할 수 있는가?
인가 누락(IDOR), 인젝션, 설정 실수, 의존 취약성의 차이를 설명할 수 있는가?
프롬프트 인젝션의 직접·간접 패턴을 구별할 수 있는가?
패스키 도입 시에 복구 도선이 필요한 이유를 설명할 수 있는가?
.env를 Git 관리하지 않는 이유와 본번에서의 보관처를 설명할 수 있는가?
SBOM의 역할을 설명할 수 있는가?
SLSA를 단순한 "4단계표"가 아니라 내력과 검증의 구조로서 이해하고 있는가?
CSP, HSTS, Cookie 속성의 역할을 설명할 수 있는가?
SAST, DAST, 의존 스캔, Secret scanning의 역할을 사용 구분할 수 있는가?
개인정보보호법 제29조 기술적 보호 조치 중 접속 기록 보관 요건을 설명할 수 있는가?
국내 서비스에서 AI LLM API 사용 시 개인정보 국외 이전 동의가 필요한 이유를 설명할 수 있는가?
⚠️ 편집 노트: 본 문서는 지속적으로 보완 중입니다. 개인정보보호법 개정 사항(자동화된 결정·제28조의2 등), ISMS-P 통제 항목 변경, OWASP Top 10 LLM Applications 업데이트는 각 공식 문서를 통해 최신 정보를 확인하세요.
©2024-2026 MDRules dev., Hand-crafted & made with Jaewoo Kim.
이메일문의: jaewoo@mdrules.dev
AI강의/개발/기술자문, AI 업무 자동화 컨설팅 문의: https://talk.naver.com/ct/w5umt5
AI 업무 자동화/에이전트/워크플로우설계 컨설팅/AI교육: https://mdrules.dev
이 작가의 멤버십 구독자 전용 콘텐츠입니다.
작가의 명시적 동의 없이 저작물을 공유, 게재 시 법적 제재를 받을 수 있습니다.
오직 멤버십 구독자만 볼 수 있는,
이 작가의 특별 연재 콘텐츠