외부 제3자가 회원가입 한 번(약 3초)으로 관리자 권한을 획득하고, 학생 개인정보 · 시험 데이터 · 알림톡 발송 인프라까지 전부 접근할 수 있음이 실증되었습니다.
| # | 취약점 | 심각도 | 실증 |
|---|---|---|---|
| 01 | 회원가입 개방 + 이메일 인증 비활성
disable_signup: false · mailer_autoconfirm: true — 누구나 3초에 관리자 계정 생성 |
CRITICAL | [✓] 계정 생성됨 |
| 02 | RLS 부재 — 전 테이블 무단 열람 학생 실명 · 전화번호 · 학습 기록 · 시험 데이터가 anon key로 공개 | CRITICAL | [✓] 데이터 수신 |
| 03 | RLS 부재 — DELETE / UPDATE 허용 공지 삭제·변조, 학생 메모·제출물 삭제가 인증 없이 가능 | CRITICAL | [✓] HTTP 204 |
| 04 | /api/send-alimtalk ownerId 미검증
학부모·학생에게 피싱 카톡 대량 발송 가능 — Solapi 인프라 연결 확인 |
CRITICAL | [✓] 발송 시도 확인 |
| 05 | Storage 버킷 공개 + 업로드 허용 시험지 433건 다운로드 가능 · 악성 파일 업로드 후 공개 배포 가능 | CRITICAL | [✓] 업로드 200 |
| # | 취약점 | 심각도 | 실증 |
|---|---|---|---|
| 06 | /api/secretary ownerId 미검증
타인 데이터로 AI 호출 · 크레딧 소진 공격 — Claude 응답 수신 확인 |
HIGH | [✓] 응답 수신 |
| 07 | /api/analyze ownerId 미검증
채점 AI 무단 사용 가능 |
HIGH | [✓] 접근 확인 |
| 08 | AI API Rate Limit 없음 스크립트 수천 건 호출 시 수백 달러 과금 (비용: 학원 부담) | HIGH | [✓] 연속 호출 성공 |
| 09 | 보안 헤더 부재 CSP · X-Frame-Options · X-Content-Type-Options 없음 — 클릭재킹/XSS 확대 | HIGH | [✓] 헤더 없음 |
| 10 | CORS * 개방
타 사이트에서 교차 API 호출 가능 |
HIGH | [✓] 확인 |
| # | 취약점 | 심각도 | 실증 |
|---|---|---|---|
| 11 | 학생 로그인 Rate Limit 없음 학번이 숫자라 순차 대입 가능 (10회 연속 401, 차단 없음) | MEDIUM | [✓] 확인 |
| 12 | 스키마 이름 유출 (에러 힌트) "Perhaps you meant the table..." — 테이블명 열거로 공격 표면 파악 | MEDIUM | [✓] 확인 |
| 13 | 관리자 페이지 SSR 200 반환 JS 비활성 환경에서 콘텐츠 노출 가능 | MEDIUM | [✓] 확인 |
| 14 | Storage 삭제 불가 (anon) 업로드된 악성 파일 회수 불가 | MEDIUM | [✓] 403 |
| 15 | 알림톡 응답에 accountId 노출 메시징 인프라 계정 정보 유출 | MEDIUM | [✓] 확인 |
| # | 항목 | 상태 | 비고 |
|---|---|---|---|
| 16 | Anthropic API 키 직접 탈취 Vercel env 격리 · 프롬프트 인젝션 저항 — 외부 추출 불가 | BLOCKED | — |
| 17 | 비밀번호 재설정 사용자 열거 존재/비존재 동일 응답 | SAFE | — |
| 18 | .git / .env / 소스맵 노출
해당 경로 없음 |
SAFE | — |
| 19 | Supabase Admin API user list · delete 등 service_role 필요 — 차단됨 | BLOCKED | — |
mailer_autoconfirm: truetest-images/ · grader/ 공개 버킷/api/secretary → AI 데이터 수집/api/send-alimtalk → 피싱 카톡 발송Disable signup : ON Confirm email : ON
-- 모든 테이블에 owner 격리 ALTER TABLE notices ENABLE ROW LEVEL SECURITY; ALTER TABLE student_memos ENABLE ROW LEVEL SECURITY; ALTER TABLE class_logs ENABLE ROW LEVEL SECURITY; ALTER TABLE assessments ENABLE ROW LEVEL SECURITY; ALTER TABLE schedules ENABLE ROW LEVEL SECURITY; ALTER TABLE submissions ENABLE ROW LEVEL SECURITY; -- 공지: 공개 읽기 + 소유자 쓰기 CREATE POLICY "public_read" ON notices FOR SELECT USING (is_published = true); CREATE POLICY "owner_all" ON notices FOR ALL USING (auth.uid() = owner_id); -- 나머지: 소유자만 CREATE POLICY "owner_only" ON student_memos FOR ALL USING (auth.uid() = owner_id); CREATE POLICY "owner_only" ON class_logs FOR ALL USING (auth.uid() = owner_id); CREATE POLICY "owner_only" ON assessments FOR ALL USING (auth.uid() = owner_id); CREATE POLICY "owner_only" ON schedules FOR ALL USING (auth.uid() = owner_id); CREATE POLICY "owner_only" ON submissions FOR ALL USING (auth.uid() = owner_id);
const { data: { user } } = await supabase.auth.getUser(token) if (!user || user.id !== body.ownerId) { return res.status(403).json({ error: '권한 없음' }) }
async headers() { return [{ source: '/(.*)', headers: [ { key: 'X-Frame-Options', value: 'DENY' }, { key: 'X-Content-Type-Options', value: 'nosniff' }, { key: 'Content-Security-Policy', value: "default-src 'self'" }, ], }] }
학생 로그인 : 5회/분 (IP + 학번 기준) AI API : 계정당 일일 호출 한도 Storage : private 전환 + 파일 타입/크기 제한
sectest_probe_20260729@proton.menotices/sec-test-upload.txt