t-box를 직접 만들고, 부숴보고, 고친다
AI와 함께 사내 파일 공유 서비스 t-box를 최소 기능부터 만들고 → 커밋하고 → 일부러 심어둔 취약점을 직접 공격해보고 → 서버에서 제대로 방어하는 4시간. 코드 암기가 목적이 아니라,
AI와 요구사항을 정리하고 결과를 검증하는 일하는 방식을 몸에 익히는 시간입니다.
HOW 이 화면 사용법
- 강사 설명을 들으며 위에서 아래로 순서대로 따라갑니다.
- 각 단계의 “지금 할 일”을 체크하면 진행률이 올라가요.
- 프롬프트 카드는 복사 버튼으로 그대로 AI에 붙여넣기.
- 왼쪽 목차로 언제든 이동 · 현재 위치가 표시됩니다.
GOAL 오늘 잡을 감각
- 만들기 전에 PRD·아키텍처를 AI와 먼저 상의한다.
- 작게 만들고 → 되면 커밋한다.
- 화면에서 숨기는 건 보안이 아니다 — 서버가 막아야 한다.
- 보안 점검은 기능마다 함께 하는 질문이다.
실습 안전 수칙. 오늘의 모든 공격 실습은 본인 VM의 교육용 t-box 앱 안에서만 진행합니다. 외부 사이트, 회사 실제 시스템, 다른 참가자 VM을 대상으로 하지 않습니다. 허가되지 않은 스캔·자동화 공격 금지.
그리고 토큰·API 키·비밀번호는 AI 대화창에 절대 입력하지 않습니다.
오프닝 · 오늘의 목표
오늘은 문법을 외우는 시간이 아니라, AI와 함께 실제 업무 도구를 만들어보는 시간이라는 감각을 먼저 맞춥니다.
강사 설명 ›
오늘은 개발 문법을 외우는 시간이 아닙니다. AI와 함께 실제 업무 도구를 만들어보는 시간입니다. 우리가 만들 것은 t-box라는 작은 파일 공유 서비스예요. 파일을 올리고, 보고, 내려받고, 권한을 나누고, 로그를 남기고 — 일부러 보안 사고도 만들어본 뒤 고쳐볼 겁니다.
AI가 코드를 대신 써줄 수는 있지만, 무엇이 안전하고 쓸 만한 서비스인지는 사람이 판단해야 합니다.
브라우저에서 앱까지 · 전체 구조
도메인을 입력하면 요청이 어떤 길을 거쳐 우리 앱에 도달하는지 그림으로 이해합니다.
-
브라우저사용자가 주소를 입력하고 화면을 보는 곳
-
도메인 · DNS도메인을 실제 서버 IP로 바꿔줍니다 · 우리 서버가 아니라 인터넷에 있는 주소록
-
웹 서버 · t-box 앱요청을 받아 처리하는 우리 프로그램 · HTTPS로 암호화되어 오갑니다
-
데이터베이스파일 정보 · 사용자 · 감사 로그를 저장 · 만들고·읽고·고치고·지움(CRUD)
-
파일 저장소실제 업로드된 파일을 보관
강사 설명 ›
주소창에 도메인을 입력해도, 요청이 곧바로 우리 앱에 닿는 건 아닙니다. 먼저 DNS가 도메인(예: study.tla.so)을 실제 서버 주소(IP)로 바꿔줘요.
이 DNS는 우리 서버 안에 있지 않습니다. 인터넷 곳곳에 흩어진 “주소록 서버”예요. 도메인을 등록한 곳(도메인·DNS 서비스 — 예: Cloudflare, AWS Route 53, 가비아 등)이 “이 도메인 = 이 IP”라는 기록을 관리하고, 브라우저는 보통 통신사(ISP)나 공용 DNS(예: 8.8.8.8)에 물어서 그 IP를 받아옵니다.
IP를 알아낸 뒤에야 그 서버의 t-box 앱이 요청을 처리합니다. 이때 앱은 데이터를 만들고(Create)·읽고(Read)·고치고(Update)·지우는데(Delete), 이 네 가지를 묶어 CRUD라고 불러요. 파일 정보·기록은 데이터베이스에, 실제 파일은 파일 저장소에 두고 CRUD 합니다. 통신은 HTTPS로 암호화되고요.
핵심 순서 — 도메인을 IP로 찾고(DNS·외부 서비스) → 그 서버의 앱이 요청을 처리하고 → 데이터를 만들고·읽고·고치고·지운다(CRUD · DB·파일 저장소).
터미널에 Claude Code 설치
오늘의 프롬프트는 모두 Claude Code에 붙여넣습니다. 터미널 창 2개를 열어 하나는 Claude Code용, 하나는 명령어 입력용으로 두고 시작해요.
강사 설명 ›
Claude Code는 터미널(명령어 창)에서 바이브 코딩을 할 수 있는 AI 코딩 에이전트 도구예요. 오늘은 이 도구에 프롬프트를 붙여넣어 t-box를 만들 겁니다. 설치는 명령어 한 줄이면 끝나고, 처음 실행하면 뜨는 로그인 안내는 강사가 알려드립니다.
작업 폴더는 /var/www/html을 쓰는데, 지금 VM엔 웹서버가 없어서 이 폴더가 아직 없어요. 그래서 sudo로 폴더를 만들고 내 계정 소유로 바꾼 다음 그 안에서 작업합니다.
그런데 문제가 하나 있어요. Claude Code가 설치 명령을 대신 실행할 때 sudo가 비밀번호를 물으면, AI는 비밀번호를 입력할 수 없어서 거기서 멈춰버립니다. 그래서 시작 전에 “이 실습 계정은 비밀번호 없이 sudo를 쓸 수 있게” 한 번 설정해 둘 거예요.
이 설정도 직접 외울 필요 없어요. Claude Code에게 명령어를 물어보고, 받은 걸 터미널에 붙여넣는 방식으로 해봅니다.
그래서 오늘은 터미널 창을 2개 띄워두고 씁니다. 하나는 Claude Code 전용, 다른 하나는 명령어 입력용이에요. 한 창에서 번갈아 하면 Claude Code를 껐다 켰다 해야 해서 번거롭거든요.
SSH로 접속했다면 터미널 창을 하나 더 열어 같은 VM에 한 번 더 접속하면 됩니다. 아래 ①②는 터미널 B에서, ③은 터미널 A에서 실행해요.
curl -fsSL https://claude.ai/install.sh | bashsudo mkdir -p /var/www/html
sudo chown -R $USER /var/www/htmlcd /var/www/html
claudeclaude를 처음 실행하면 로그인 안내가 나옵니다. 안내대로 로그인한 뒤(또는 /login), 프롬프트 창에 한글로 요청을 입력하면 돼요. 터미널 B도 cd /var/www/html 해두면 편합니다.
- sudo
- “관리자 권한으로 실행”이라는 뜻이에요. 시스템 폴더를 만들거나 프로그램을 설치할 땐 권한이 필요해서 명령 앞에 붙입니다. 비밀번호를 물으면 강사가 알려준 걸 입력하세요 — 아래 ④에서 안 묻게 설정할 거예요.
- sudo chown -R $USER /var/www/html
/var/www/html폴더와 그 안의 모든 것(-R)의 소유자를 지금 나($USER)로 바꿉니다. 이래야 앞으로sudo없이도 그 폴더에서 자유롭게 파일을 만들고 지울 수 있어요.
첫 번째 바이브 코딩 연습이에요. 터미널 A(Claude Code)에 아래 프롬프트를 붙여넣어 명령어를 받아온 다음, 그 명령어를 터미널 B에 붙여넣어 직접 실행합니다.
지금 이 리눅스 계정이 sudo를 쓸 때 비밀번호를 묻지 않도록 설정하고 싶어.
교육용 실습 VM이고, 내가 관리자 권한을 가지고 있어.
- visudo로 문법 검사를 하면서 /etc/sudoers.d/ 아래에 파일을 만드는 방식으로 알려줘
- 내 계정 이름은 직접 확인해서 넣어줘 (whoami)
- 실행할 명령어를 복사해서 쓸 수 있게 알려주고, 설정이 잘 됐는지 확인하는 방법도 알려줘
명령은 네가 실행하지 말고, 내가 터미널에 붙여넣어 직접 실행할게.
왜 “직접” 실행할까요? sudo 권한 설정은 시스템에서 가장 조심해야 할 작업 중 하나예요. AI가 준 명령을 한 번 읽어보고 내 손으로 실행하는 습관이, 오늘 배울 “AI에게 맡기되 검증한다”의 첫 연습입니다.
받은 명령을 터미널 B에 붙여넣어 실행한 뒤, 아래로 확인해요. OK가 나오면 성공입니다.
sudo -n true && echo "OK" || echo "아직 비밀번호를 묻습니다"Hello World를 내 도메인으로 서빙
내 컴퓨터 안에서만 보이던 프로그램을, 도메인으로 남이 접속할 수 있는 서비스로 바꾸는 첫 순간.
강사 설명 ›
복잡한 앱을 만들기 전에, 가장 단순한 걸로 “도메인 → 내 서버 → 화면” 연결을 먼저 확인해 봅니다. nginx(엔진엑스)라는 웹서버를 깔면, /var/www/html 폴더 안의 파일을 인터넷에 그대로 보여줘요.
그래서 그 폴더의 index.html에 “hello world”만 적어두고 내 도메인으로 접속하면, 그 글자가 뜹니다. 이게 되면 연결이 살아있다는 뜻이에요.
내 도메인은 회사 이메일 앞자리 + .tla.so 예요. 예를 들어 이메일이 hong@company.com이면 hong.tla.so가 내 주소입니다.
nginx 웹서버를 설치하고 실행해줘.
/var/www/html 폴더의 index.html을 기본으로 보여주도록 설정해줘.
설치와 실행 명령을 초보자도 이해할 수 있게 하나씩 알려줘.
nano /var/www/html/index.html- nano 안에서
- 기존 내용이 있으면 싹 지웁니다 (한 줄씩 지우려면 Ctrl+K). 그런 다음 아래 한 줄을 입력해요.
hello world- 저장하고 나가기
- Ctrl+O → Enter (저장) → Ctrl+X (종료).
nano의 저장은 O(WriteOut)예요. 화면 아래 도움말에도^O Write Out으로 표시됩니다. 이 VM의 nano에서는 Ctrl+S로 저장해도 되지만, 어디서나 통하는 건 Ctrl+O예요.
폴더는 아까 내 소유로 바꿔놔서sudo없이 편집돼요.
내 도메인(이메일 앞자리.tla.so)으로 접속했을 때 hello world가 보이나요? 주소창에 https(자물쇠)가 떠 있나요?
Git 저장소 · 첫 커밋과 푸시
Git은 바이브 코딩의 안전벨트. AI가 만든 코드가 문제를 일으켰을 때 돌아올 저장 지점을 만듭니다.
먼저 내 코드를 올려둘 저장소(repository)를 웹에서 직접 만듭니다.
Git 서버 주소https://git2.tla.so
- 위 주소에 내 계정으로 로그인 (계정·비밀번호는 강사가 전달)
- 오른쪽 위 + → New Repository(새 저장소)
- 이름은
tbox로 입력 · 나머지는 기본값 그대로 - README 추가나 초기화 옵션은 체크하지 않기 — 빈 저장소로 만들어야 우리가 올릴 때 충돌이 없어요
- 만들고 나면 보이는 저장소 주소(https://... .git)를 복사 — ③에서 씁니다
Git 서버에 올릴 때는 계정 비밀번호가 아니라 액세스 토큰을 씁니다. 웹에서 한 번 발급받아 복사해 두세요.
- 오른쪽 위 프로필 아이콘 클릭 → 설정
- 왼쪽 메뉴에서 어플리케이션 → 액세스 토큰 관리 → 새 액세스 토큰 생성
- 토큰 이름에 아무 이름이나 입력 (예:
tbox) - 저장소 및 조직 액세스에서 모두(공개, 비공개 및 제한) 선택
- 권한 선택 목록에서 repository 항목만 찾아, 드롭다운을 접근 불가 → 읽기와 쓰기로 변경
나머지 항목(activitypub · issue · misc · user 등)은 접근 불가 그대로 두세요. - 맨 아래 토큰 생성 버튼 클릭
- 화면 위에 뜬 토큰 문자열을 복사 — 이 화면을 벗어나면 다시 못 봐요
권한 선택 부분이 헷갈리기 쉬워요. 아래대로만 맞추면 됩니다.
| 권한 항목 | 설정할 값 |
|---|---|
| activitypub | 접근 불가 (그대로) |
| issue | 접근 불가 (그대로) |
| misc | 접근 불가 (그대로) |
| notification | 접근 불가 (그대로) |
| organization | 접근 불가 (그대로) |
| package | 접근 불가 (그대로) |
| repository | 읽기와 쓰기 ← 이것만 변경 |
| user | 접근 불가 (그대로) |
왜 repository만 켜나요? 토큰은 “내 계정 대신 행동할 수 있는 열쇠”예요. 필요한 권한만 열어두는 게 안전합니다. 우리는 코드를 올리고 내리기만 하면 되니 repository의 읽기와 쓰기면 충분해요.
방금 복사한 토큰은 비밀번호와 같습니다. Claude Code를 포함한 어떤 AI 대화창에도 토큰 값을 직접 입력하지 마세요. 대화 내용은 기록으로 남고, 외부 서버로 전송됩니다. 한 번 노출된 토큰은 즉시 폐기해야 합니다.
- ✕하지 말 것 — 대화창에 “내 토큰은
abc123...인데 설정해줘”라고 입력 - ✕하지 말 것 — 토큰이 들어간 명령어나 주소를 통째로 붙여넣기
- ✓이렇게 — 토큰은
.env파일에만 적고, AI에게는 “.env 파일을 읽어서 설정해줘”라고 파일 이름만 알려주기
같은 원칙이 API 키 · 비밀번호 · 인증서에도 그대로 적용됩니다. 오늘 실습에서 .env를 쓰고 .gitignore에 넣는 이유가 바로 이것 — 비밀값은 코드·대화가 아니라 별도 파일에서 관리합니다.
/var/www/html에서 .env라는 파일을 만들어 내 계정 · 저장소 주소 · 토큰을 적어 둡니다. 이 값은 다음 단계에서 Claude Code가 읽어 자동으로 git을 설정해요.
nano /var/www/html/.envGIT_USERNAME=student01
GIT_EMAIL=you@company.com
GIT_REPOSITORY=https://git2.tla.so/student01/tbox.git
GIT_TOKEN=여기에_발급받은_토큰_붙여넣기네 줄의 오른쪽 값을 내 것으로 바꿔 저장(Ctrl+O → Enter → Ctrl+X로 종료). GIT_REPOSITORY엔 ①에서 복사한 저장소 주소, GIT_TOKEN엔 ②에서 복사한 토큰을 넣어요.
⚠ 토큰은 이 파일에만. 터미널 B에서 nano로 직접 입력하세요. AI 대화창(터미널 A)에는 절대 붙여넣지 않습니다.
/var/www/html/.env 파일에 GIT_USERNAME, GIT_EMAIL, GIT_REPOSITORY, GIT_TOKEN이 들어 있어.
git이 없으면 설치하고, 이 값들을 읽어서 git 초기 설정을 해줘:
- 현재 폴더를 git 저장소로 초기화하고 기본 브랜치를 main으로
- 사용자 이름(GIT_USERNAME)과 이메일(GIT_EMAIL) 설정
- 원격 저장소 origin을 연결하되, 인증이 되도록
GIT_REPOSITORY 주소에 계정과 토큰을 넣은 형태로 설정해줘
- .env 파일은 절대 커밋되지 않게 .gitignore에 추가
앞으로 커밋 메시지를 제안할 때는 항상 한글로 써줘.
설정이 끝나면 커밋·푸시하는 명령만 알려주고, 커밋은 내가 직접 할게.
Claude Code가 설정을 끝내면, 이제 토큰이 이미 연결돼 있어서 push할 때 비밀번호를 안 물어봅니다.
터미널 B(명령어 입력용)에서 /var/www/html로 이동해 직접 커밋해 봅니다. 내 손으로 저장 지점을 만드는 감각을 익히는 단계예요.
git add .
git commit -m "헬로월드 페이지 완성"
git push -u origin main두 번째부터는 git add . → git commit -m "..." → git push 세 줄이면 끝.
강사 설명 ›
Git은 작업 기록을 남기는 도구, Git 서버(git2.tla.so)는 그 기록을 올려 보관·공유하는 도구입니다. 바이브 코딩에서 특히 중요한 이유는, AI가 만든 코드가 마음에 안 들거나 문제가 생겼을 때 돌아갈 수 있는 저장 지점이 필요하기 때문이에요.
기능이 하나 완성될 때마다 커밋합니다. 보안 실험 전에도 커밋합니다. 수정이 끝난 뒤에도 커밋합니다.
remote origin already exists → git remote set-url origin <URL>
authentication failed → 계정·저장소 권한 확인, 필요시 토큰 재발급
만들기 전에 · PRD와 아키텍처를 AI와 상의
좋은 바이브 코딩은 “앱 만들어줘” 이전에 무엇을 만들지 설계 대화를 먼저 합니다.
기술 이름이 낯설어도 괜찮아요. 지금 회사에서 쓰는 t-box가 실제로 이 조합으로 만들어져 있어서, 우리도 같은 재료로 만듭니다. 각각이 무슨 역할인지만 알면 충분해요.
| 재료 | 무슨 역할인가 |
|---|---|
| nginx | 웹서버. 브라우저 요청을 받아 앱에 전달 (앞에서 설치한 그것) |
| PHP | 앱의 본체. 업로드·다운로드·권한 확인 같은 기능을 실제로 처리하는 프로그래밍 언어 |
| PostgreSQL | 데이터베이스. 파일 정보·사용자·감사 로그를 저장 |
| 파일 저장소 | 업로드된 실제 파일이 놓이는 폴더 |
실제 t-box는 여기에 Laravel이라는 도구까지 얹어서 만들어졌지만, 오늘은 순수 PHP로만 갑니다. 중간 도구 없이 코드가 하는 일이 그대로 보여야 뒤에서 “권한을 어디서 검사하는지” 같은 걸 직접 확인할 수 있거든요.
나는 개발 경험이 거의 없어.
PHP, PostgreSQL, nginx가 각각 뭔지
비유를 들어 아주 쉽게 설명해줘. 각각 3줄 이내로.
회사 내부 파일 공유 서비스 t-box의 PRD를 만들어줘.
대상 사용자는 Admin, Manager, Employee, Guest야.
최소 기능부터 시작하고, 보안과 감사 로그, 성능은 이후 단계로 나눠줘.
개발 초보자도 이해할 수 있게 작성해줘.
작성한 내용을 PRD.md 파일로 저장해줘.
t-box를 순수 PHP(프레임워크 없이), PostgreSQL, 파일 저장소로
만든다고 할 때 초보자용 아키텍처를 설명해줘.
내 서비스 도메인은 "내이메일앞자리.tla.so" 야.
(예: 이메일이 hong@company.com 이면 hong.tla.so)
다음 항목을 포함해줘: 브라우저, 도메인/DNS, nginx 웹서버,
PHP 앱, PostgreSQL, 파일 저장소, Git 서버.
각 요소가 무슨 역할을 하는지 개발 초보자도 알 수 있게 쉽게 설명해줘.
작성한 내용을 ARCHITECTURE.md 파일로 저장해줘.
강사 설명 ›
많은 사람이 AI에게 바로 “앱 만들어줘”라고 말합니다. 하지만 실무에서는 먼저 무엇을 만들지 정리해야 해요 — 그 문서가 PRD입니다. 그리고 어떤 구성으로 만들지, 데이터를 어디 저장할지, 권한 확인은 어디서 할지 정하는 것이 아키텍처고요.
그리고 중요한 건, 이 대화를 파일로 남긴다는 거예요. PRD.md와 ARCHITECTURE.md로 저장해두면 다음 단계에서 AI에게 “이 문서대로 만들어줘”라고 할 수 있습니다. 말로만 하면 대화가 길어질수록 AI가 처음 약속을 잊어버려요.
좋은 바이브 코딩은 구현 요청 전에 설계 대화를 먼저 합니다. 그리고 그 결과를 문서로 남겨 계속 참조합니다.
작게 먼저 · 최소 기능 t-box (MVP)
완벽한 서비스가 아니라 작고 돌아가는 버전을 먼저. PRD.md·ARCHITECTURE.md를 근거로 ①기본 기능을 만들어 확인하고 → ②일부러 취약한 권한 구조를 얹습니다.
방금 만든 문서를 씁니다. 프롬프트 첫 줄이 “PRD.md와 ARCHITECTURE.md를 먼저 읽어줘”인 이유예요. 설계를 해놓고 안 쓰면 AI가 제멋대로 만들고, 결과가 마음에 안 들 때 무엇과 다른지 따질 기준도 사라집니다.
앞에서 만든 PRD.md와 ARCHITECTURE.md를 먼저 읽어줘.
그 문서에 맞춰서 /var/www/html 안에 t-box를 순수 PHP로 만들어줘.
프레임워크(Laravel 등) 없이 PHP 파일과 SQL만으로 단순하게.
데이터베이스는 PostgreSQL(호스트 127.0.0.1)을 쓸 거야.
중요: 이건 교육용 MVP야. 완성도 높은 제품이 아니라
동작만 확인할 수 있는 가장 단순한 버전을 원해.
아래 파일 구조를 그대로 만들어줘:
- index.php : 파일 목록 화면 + 업로드 폼
- db.php : DB 연결 함수 하나(PDO), JSON 응답 도우미
- api/files.php : GET=목록(JSON), DELETE=삭제
- api/upload.php : POST=업로드 (multipart/form-data)
- api/download.php : GET ?id= 로 파일 내려주기
- assets/app.js : fetch로 위 API를 불러 화면을 그림
- assets/style.css : 아주 단순한 스타일
- schema.sql : 아래 files 테이블 CREATE 문
- uploads/ : 업로드된 실제 파일이 저장되는 폴더
files 테이블 컬럼(이것만, 복잡한 인덱스·제약조건 없이):
id, original_name, stored_name, size_bytes,
uploaded_by, access_level, created_at
동작 방식:
- 업로드하면 실제 파일은 uploads/에 겹치지 않는 이름으로 저장하고,
원본 이름·크기 등 정보는 files 테이블에 넣어
- 목록은 api/files.php가 JSON으로 주고, app.js가 표로 그려
- 삭제 버튼을 누르면 api/files.php에 DELETE 요청을 보내
- 지금은 로그인 없이 항상 admin으로 동작하면 돼
지금은 넣지 마 (다음 단계에서 직접 할 거야):
- 로그인/인증, 권한 검사
- 입력값 검증, 보안 처리
- 감사 로그, 페이지네이션, 검색
- 에러 처리, 테스트 코드
파일 개수는 위 목록만, 코드도 짧게. 초보자가 한눈에 읽을 수준으로.
다 만들면 각 파일 역할과 테이블 컬럼 뜻을 짧게 설명해줘.
여기서 한 번 멈춥니다. 브라우저에서 업로드·목록·다운로드·삭제가 실제로 되는지 확인한 뒤 다음 프롬프트로 넘어가세요.
t-box에 사용자 역할과 파일 접근 레벨을 추가해줘.
역할: Admin, Manager, Employee, Guest
접근 레벨: public, internal, team, admin-only
중요: 학습 목적으로 일부러 취약하게 만들 거야.
"보안을 위해 서버에서도 검사하겠다"는 식으로 고치지 말고,
아래 3가지를 그대로 지켜서 만들어줘.
1) 역할 전환 기능
- 화면 위쪽에 역할을 고르는 select 박스를 둬
(Admin/Manager/Employee/Guest)
- 진짜 로그인 대신 세션에 역할만 저장하는 switch_role.php를 만들어
2) 삭제 버튼 (취약점 A)
- 삭제 버튼은 모든 줄에 항상 만들어 두되,
Admin이 아니면 style="display:none" 으로 화면에서 숨기기만 해
- api/files.php의 DELETE는 누가 요청했는지 검사하지 말고 그냥 삭제해
3) 목록/출력 (취약점 B, C)
- api/files.php의 목록은 역할과 상관없이 모든 파일을 그대로 내려줘
- 어떤 파일을 보여줄지는 app.js에서 역할에 따라 화면에서만 걸러
- 파일명·설명은 이스케이프하지 말고 innerHTML로 그대로 출력해
각 취약한 부분에는 "교육용 취약 구조"라는 주석을 꼭 남겨줘.
AI가 자꾸 취약점을 없애거나 시간이 너무 오래 걸리면, 강사가 미리 만들어 둔 완성본을 그대로 내려받아 실습해도 됩니다. 위 프롬프트들과 똑같은 구조·똑같은 취약점이라 이후 해킹 타임이 그대로 진행돼요.
cd /var/www
sudo rm -rf html
sudo git clone https://git2.tla.so/hardline7/t-box-sample.git html
sudo chown -R $USER /var/www/html
cd /var/www/html
cat README.mdREADME.md에 DB 만들기 · nginx 설정 · 실행 방법이 정리돼 있어요. 그대로 따라 하면 예시 파일 5개가 든 t-box가 바로 뜹니다.
강사 설명 ›
지금은 완벽한 서비스를 만들지 않습니다. 작고 돌아가는 버전을 먼저 만들 거예요 — 이걸 MVP(최소 기능 제품)라고 부릅니다.
처음부터 보안·성능·디자인·모든 권한을 완벽히 넣으려 하면 길을 잃기 쉽습니다. 먼저 작게 만들고, 그다음 위험을 찾고, 고칩니다.
그래서 프롬프트에 “이건 교육용 MVP다”, “이것들은 지금 넣지 마”라고 못 박아 둡니다. 그냥 “만들어줘”라고 하면 AI는 친절하게 인증·검증·에러 처리까지 다 붙여서 너무 잘 만들어 버려요. 그러면 시간도 오래 걸리고, 뒤에서 우리가 직접 찾아볼 취약점이 남지 않습니다.
AI에게는 무엇을 만들지만큼 무엇을 만들지 말지도 알려줘야 합니다.
해킹 전에 · 저장 지점 만들기
지금 상태는 우리가 돌아올 수 있는 저장 지점. 보안 실험 전에 반드시 커밋합니다.
git add .
git commit -m "보안 실습 전 저장 지점"
git push| 시점 | 커밋 메시지 |
|---|---|
| 최소 t-box 완성 | 기본 파일 공유 기능 완성 |
| 프론트 권한 취약 구조 | 화면에서만 권한 확인하는 구조 추가 |
| 해킹 실습 전 | 보안 실습 전 저장 지점 |
| 서버 권한 검사 추가 | 서버에서 권한 검사하도록 수정 |
| XSS 방어 추가 | 사용자 입력값 XSS 방어 추가 |
| SSRF 방어 추가 | URL 검증으로 SSRF 차단 |
| 감사 로그 추가 | 감사 로그 기록 추가 |
일부러 심는 나쁜 구조 · 프론트 권한 체크
권한 확인을 서버가 아니라 화면에서만 하면 어떤 일이 벌어지는지, 곧 직접 봅니다.
강사 설명 ›
이제 일부러 나쁜 구조를 하나 넣어봅니다. 권한 확인을 DB나 서버에서 하지 않고 화면(프론트엔드)에서만 하게 만들 거예요. 예를 들어 Employee에게는 삭제 버튼을 숨깁니다. 그런데 서버 API는 삭제 요청이 오면 그냥 삭제합니다. 화면에는 안전해 보이지만 실제로는 사고가 날 수 있어요.
잠시 뒤에 직접 해볼 거예요. 숨겨진 삭제 버튼을 개발자도구로 다시 보이게 만든 다음, 그냥 눌러보는 겁니다. 코딩 지식이 없어도 클릭 몇 번이면 됩니다 — 그게 이 구조가 위험한 이유예요.
버튼이 안 보이는 것과, 권한이 없는 것은 완전히 다릅니다.
화면에서 버튼을 숨기는 것은 보안이 아니다. 서버가 권한을 확인해야 보안이다.
해킹 타임 1 · 프론트 권한 우회 & XSS
공격 기술 학습이 아니라, 잘못 만든 서비스가 왜 위험한지 직접 체감하는 시간입니다.
내 VM의 교육용 t-box에서만. 외부·회사 실제 시스템·타 참가자 VM 금지.
공격해볼 대상이 있어야 하니, Admin으로 로그인해 파일을 5개 정도 업로드하세요. 접근 레벨을 섞어서 올리면 뒤 실습이 더 잘 보입니다.
- public 파일 2개 — 누구나 볼 수 있는 자료
- internal 파일 2개 — 사내용 자료
- admin-only 파일 1개 — 관리자만 볼 수 있어야 하는 자료 (이게 뒤에서 “숨겼는데 보이는” 주인공이 됩니다)
파일 내용은 아무거나 좋아요. 메모장에 아무 글이나 적어 저장한 .txt 파일이면 충분합니다.
Employee에게는 삭제 버튼이 화면에서 숨겨져 있을 뿐, 버튼 자체는 브라우저 안에 그대로 있습니다. 숨긴 걸 다시 보이게 만들어 눌러봅니다.
| ID | 파일명 | 접근 레벨 | 동작 |
|---|---|---|---|
| 1 | 회사소개.txt | public | 받기 삭제 |
| 3 | 사내공지.txt | internal | 받기 삭제 |
| 5 | 급여명세_2026.txt | admin-only | 받기 삭제 |
- 1Admin이라 삭제 버튼이 보이고, admin-only 파일(급여명세)도 목록에 있습니다. 정상이에요.
| ID | 파일명 | 접근 레벨 | 동작 |
|---|---|---|---|
| 1 | 회사소개.txt | public | 받기 — 삭제 버튼 없음 |
| 3 | 사내공지.txt | internal | 받기 — 삭제 버튼 없음 |
- 2삭제 버튼이 사라졌고, admin-only 파일도 목록에서 빠졌습니다. 안전해 보이죠? 여기서부터 실습을 시작합니다.
- Employee 계정으로 전환 → 목록에 삭제 버튼이 안 보이는 것 확인
- F12를 눌러 개발자도구 열기 → Elements(요소) 탭 선택
- 왼쪽 위 화살표 아이콘(Ctrl+Shift+C)을 켜고 파일 목록의 한 줄을 클릭 → 그 줄의 HTML로 이동
- 그 근처에서 삭제 버튼 태그를 찾습니다.
style="display:none"이나hidden, 혹은class="hidden"이 붙어 있을 거예요 - 그 부분을 더블클릭해서 지우고 Enter → 화면에 삭제 버튼이 나타납니다
- 나타난 삭제 버튼을 클릭 → 파일이 진짜로 삭제되는지 확인
<td class="actions">
<a class="btn btn-ghost" href="api/download.php?id=3">받기</a>
<button class="btn btn-danger js-delete" data-id="3"
style="display:none">삭제</button>
</td>
- 3버튼은 지워진 게 아니라 그대로 있습니다. 빨갛게 칠한
style="display:none"만 더블클릭해서 지우고 Enter를 누르세요.
| 3 | 사내공지.txt | internal | 받기 삭제 |
// 삭제 버튼을 누르면 콘솔에 찍히는 서버 응답
{
"ok": true,
"deleted": "사내공지.txt",
"by": "employee01 (Employee)",
"note": "서버가 권한을 확인하지 않았습니다"
}
- 4Employee가 눌렀는데 서버가 그대로 삭제했습니다. 서버는 “누가 보냈는지” 확인조차 하지 않았어요.
방금 무슨 일이 일어났나요? 우리는 서버를 해킹한 게 아니라, 내 브라우저에 있는 화면을 고쳤을 뿐입니다. 그런데 서버는 그 요청을 그대로 받아들여 파일을 지웠어요. 화면은 사용자 편의일 뿐, 보안 장치가 아니라는 증거입니다.
버튼을 못 찾겠다면, 콘솔에서 API를 직접 불러도 같은 결과가 나옵니다. 파일 ID(id=3)는 목록에 보이는 번호로 바꾸세요 — Network 탭에서 실제 삭제 요청 주소를 보면 알 수 있어요.
fetch("api/files.php?id=3", { method: "DELETE" })
.then((res) => res.json())
.then(console.log);콘솔에 코드를 처음 붙여넣으면 브라우저가 경고를 띄우고 막습니다. “모르는 코드를 붙여넣게 해서 계정을 뺏는 사기”를 막으려는 장치예요. 아래 순서로 한 번만 풀어주세요.
- F12 → Console(콘솔) 탭으로 이동
- 붙여넣기를 시도하면 “이해하지 못했거나 직접 검토하지 않은 코드는 붙여넣지 마세요…” 경고가 뜹니다
- 콘솔에 아래 문구를 직접 타이핑하고 Enter
한글 브라우저붙여넣기 허용영문 브라우저allow pasting복사·붙여넣기 말고 손으로 쳐야 합니다. 둘 중 어느 걸 쓸지는 화면에 뜬 경고문 마지막 줄에 적혀 있어요. - 이제 위 코드를 붙여넣고 Enter → 파일이 삭제되는지 확인
이 경고가 왜 있는지 기억해 두세요. 실제로 “이 코드를 콘솔에 붙여넣으면 이벤트 당첨”처럼 속여서 계정을 탈취하는 공격(셀프 XSS)이 흔합니다. 오늘처럼 내가 만든 실습 앱이 아니라면, 남이 준 코드를 콘솔에 붙여넣지 마세요.
Employee 화면에는 public/internal만 보이지만, 개발자도구 Network 탭에서 api/files.php 응답을 열면 admin-only 파일 정보가 그대로 내려옵니다.
- F12 → Network(네트워크) 탭 선택 → 페이지 새로고침(F5)
- 목록에서
files.php를 클릭 → Response(응답) 또는 미리보기 탭 - 화면에 없던 admin-only 파일이 응답 안에 있는지 확인
api/files.php 응답 요청자: employee01{
"user": { "name": "employee01", "role": "Employee" },
"files": [
{ "id": 1, "original_name": "회사소개.txt", "access_level": "public" },
{ "id": 3, "original_name": "사내공지.txt", "access_level": "internal" },
{ "id": 5, "original_name": "급여명세_2026.txt", "access_level": "admin-only" }
]
}
- !화면에는 안 보이던 급여명세 파일이 응답에는 그대로 들어 있습니다. 파일명만 봐도 이미 정보가 새어나간 거예요.
api/download.php?id=5로 내려받는 것도 가능합니다.
파일명 또는 설명에 아래 값을 넣고 목록 화면으로 가면, 값이 글자가 아니라 코드로 실행되는지 확인합니다.
<script>alert('xss')</script><img src=x onerror="alert('xss')">- 1설명은 그냥 글자일 뿐인데, 서버가 검사 없이 저장하고 화면이 그대로 HTML로 출력합니다.
- 2내가 올린 파일을 다른 사람이 목록에서 보기만 해도 그 사람 브라우저에서 코드가 실행됩니다. 알림창 대신 쿠키를 훔쳐 보내는 코드였다면 계정이 넘어갔을 거예요.
화면엔 삭제 버튼이 없었는데 왜 삭제됐을까요? 이 문제를 어디서 막아야 할까요?
→ 서버에서 사용자 역할을 확인 · DB의 접근 레벨과 역할을 비교 · 실패한 시도도 감사 로그에 남긴다.
해킹 타임 2 · SSRF와 방어 설계
서버가 사용자를 대신해 URL에 접속하는 기능이 왜 위험한지, URL 검증 함수를 만들며 안전하게 이해합니다.
강사 설명 ›
URL을 넣으면 서버가 파일을 가져와 t-box에 저장해주는 기능을 생각해봅시다. 편리하지만, 사용자가 내부망 주소나 localhost를 넣으면 서버가 대신 접근할 수 있어 위험합니다. 이걸 SSRF라고 해요. 교육용으로는 실제 다운로드 대신 URL 안전성 검사 함수를 만들어 취약한 검사와 방어된 검사를 비교합니다.
t-box 아래쪽 “URL로 파일 가져오기” 칸에 주소를 넣고, 옆의 검사 방식만 바꿔서 결과를 비교합니다.
허용됨 ✅ http 문자열이 있으니 통과 (허술한 검사)
차단됨 ⛔ 내부 주소라서 차단 (169.254.169.254)
- !같은 주소인데 결과가 다릅니다. 취약한 검사는 “http가 들어 있으면 통과” 수준이라 내부망 주소를 그대로 통과시켜요. 이 주소는 클라우드 서버의 비밀 정보가 나오는 곳이라 실제 사고로 자주 이어집니다.
| URL | 판정 |
|---|---|
| https://example.com/report.pdf | 허용 |
| http://localhost/admin | 차단 |
| http://127.0.0.1:3000 | 차단 |
| http://0.0.0.0:3000 | 차단 |
| http://10.0.0.5/secret | 차단 |
| http://172.16.0.10/secret | 차단 |
| http://192.168.0.10/secret | 차단 |
| http://169.254.169.254/latest/meta-data | 차단 |
| file:///etc/passwd | 차단 |
| ftp://example.com/file.txt | 차단 |
t-box에 URL을 입력하면 서버가 파일을 가져오는 기능이 있다고 가정하자.
SSRF 위험을 안전하게 이해하기 위한 테스트 케이스를 만들어줘.
실제 외부 공격은 하지 않고, 내 VM의 교육용 앱에서
URL 검증 함수만 테스트할 거야.
허용해야 할 URL과 차단해야 할 URL을 표로 정리해줘.
- http/https만 허용
- DNS 해석 후 IP 검사 · 사설 IP·localhost·link-local 차단
- 리다이렉트 후 최종 주소 재검사
- 요청 시간 제한 · 응답 크기 제한
- 차단 이벤트를 감사 로그에 기록
제대로 막기 · 서버 권한 검사 & 감사 로그
AI에게 “취약점 고쳐줘”가 아니라 항목별로 점검을 요청하고, 방어를 서버로 옮깁니다.
PRD.md의 "보안 요구사항"을 기준으로 지금 t-box 앱을 점검해줘.
특히 인증, 인가, 프론트엔드 권한 우회, XSS, SSRF, 파일 업로드 검증,
감사 로그, 성능 문제를 항목별로 봐줘.
각 항목마다 위험도, 문제 설명, 수정 방법을 표로 정리해줘.
현재 t-box는 권한 확인을 프론트엔드에서만 하고 있어.
이것을 서버에서 DB의 사용자 역할과 파일 접근 레벨을 확인하도록 수정해줘.
요구사항:
- 권한 없는 파일은 목록 API 응답에 포함하지 않기
- 다운로드 요청에서 서버 권한 검사
- 삭제 요청에서 서버 권한 검사
- 권한 거부 시 403 응답 반환
- 권한 거부 이벤트를 감사 로그에 남기기
t-box에 감사 로그 기능을 추가해줘.
기록할 이벤트: 업로드 성공/실패, 다운로드 성공/권한 거부,
삭제 성공/권한 거부, 위험한 입력값 감지, SSRF 위험 URL 차단.
로그에는 시간, 사용자, 역할, 행동, 파일명, 결과, 이유가 들어가야 해.
Admin만 감사 로그 화면을 볼 수 있게 해줘.
보안 점검은 개발이 끝난 뒤 하는 검사가 아니라, 기능을 만들 때마다 함께 해야 하는 질문이다.
많아져도 버티게 · 성능 개선
파일이 늘어나도 목록이 쓸 만하도록 페이지네이션·검색·정렬·업로드 제한·고유 저장명을 추가.
t-box 파일 목록에 성능 개선을 추가해줘.
요구사항:
- 한 페이지에 10개씩 보여주기
- 파일명 검색
- 최신 업로드 순 정렬
- 업로드 파일 크기 제한
- 파일명이 겹쳐도 덮어쓰지 않도록 저장 파일명은 고유 ID 사용
초보자가 이해할 수 있게 왜 이런 개선이 필요한지도 설명해줘.
마무리 · 최종 커밋과 회고
방어까지 끝난 상태를 저장하고, 오늘 잡은 감각을 한 문장으로 정리합니다.
git add .
git commit -m "서버 권한 검사, XSS·SSRF 방어, 감사 로그 추가"
git push오늘 만든 것 중 가장 위험했던 구조는? 그 문제를 결국 어디서 막았나요? AI에게 무엇을 만들게 했고, 무엇을 검토·의심하게 했나요?
부록 · Git 치트시트 & 문제 해결
막히면 여기. 자주 쓰는 명령과 흔한 에러 해결법.
GIT 자주 쓰는 명령
git status # 변경사항 확인
git add . # 전체 스테이징
git commit -m "메시지" # 저장
git push # 서버로 올리기
git remote -v # 원격 저장소 확인FIX 흔한 에러
- remote origin already exists
git remote set-url origin <URL> - nothing to commit
변경사항 없음 →git status로 확인 - authentication failed
계정·저장소 권한 확인, 토큰 재발급
지금 변경사항에 맞는 Git 커밋 메시지를 추천해줘.
한글로, 짧고 명확하게 3개 제안해줘.
나중에 봐도 무엇을 했는지 알 수 있는 문장으로.