Step 71. HTML/CSS 기초 — 웹페이지의 뼈대와 옷
Level 1 — 프로그래밍과 컴퓨터 내부 | 난이도 ★★☆☆☆ | 예상 소요 시간 3시간
전제: 텍스트 에디터로 파일을 만들고 저장할 수 있다. 웹 브라우저(크롬/엣지 등)가 설치되어 있다. 파이썬 입문 수준의 프로그래밍 경험이 있으면 충분하다.
- 준비물: 텍스트 에디터(메모장이나 VSCode)와 웹 브라우저 하나. 설치도, 인터넷 연결도 필수가 아닙니다.
- 주의: 오늘 실습은 100% 안전합니다. 만드는 모든 파일은 여러분의 컴퓨터 안에만 있고, 브라우저에서의 수정도 여러분의 화면 안에서만 일어납니다.
지금까지 우리는 터미널이라는 검은 창에서 살았습니다. 오늘부터 무대는 브라우저입니다. 여러분이 매일 보는 웹페이지는 사실 전부 "텍스트 파일"입니다. 화려한 쇼핑몰도 뿌리를 따라가면 글자로 된 문서 하나이고, 브라우저가 그것을 예쁘게 그려 낸 것입니다. 그 문서를 쓰는 언어가 HTML이고, 꾸미는 언어가 CSS입니다. 보안 학습자가 이것을 배우는 이유는 단순합니다 — 공격할 대상의 구조를 모르면 공격도 방어도 불가능하기 때문입니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 빈 파일에서 시작해 제목·문단·링크·이미지·목록·입력창이 있는 웹페이지를 손으로 만든다
- form과 input 태그의 구조(action, method, name)를 설명하고 로그인 폼을 만든다
- CSS 선택자(태그, class, id)로 원하는 요소에만 스타일을 입힌다
- 개발자 도구(F12)의 Elements 탭에서 DOM을 보고 직접 수정한다
- "웹 해킹이 왜 HTML 이해에서 시작하는가"를 원리로 설명한다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | HTML(문서 구조 표시 언어) + CSS(스타일 규칙 언어). 실행 환경은 웹 브라우저 |
| 오늘의 도구 | 텍스트 에디터, 브라우저 개발자 도구(F12)의 Elements 탭 |
| 오늘의 태그 | html/head/body, h1, p, a, img, ul/li, form, input, button, div |
| 필요한 개념 | 태그와 속성, DOM(문서 객체 모형), CSS 선택자, form 데이터가 서버로 가는 경로 |
| 오늘의 산출물 | page.html — 소개 페이지, login.html — 로그인 폼 페이지 |
2-1. 태그 — 상자에 이름표 붙이기
HTML의 기본 단위는 태그(tag, 꼬리표)입니다. <p>안녕</p>이라고 쓰면 "여기부터 여기까지가 문단(paragraph)이다"라는 표시가 됩니다. 여는 태그 <p>와 닫는 태그 </p> 사이에 내용이 들어가는 이 구조가 전부입니다. 닫는 태그에는 빗금(/)이 붙습니다. 상자를 열었으면 반드시 닫아야 한다는 것, 이것이 HTML의 첫 번째 규칙입니다.
태그에는 속성(attribute, 부가 정보)을 붙일 수 있습니다. <a href="https://example.com">링크</a>에서 href="..."가 속성입니다. "어디로 연결할지" 같은 부가 정보를 태그 안에 적는 것입니다.
2-2. form과 input — 오늘의 주인공
웹 보안의 관점에서 가장 중요한 태그는 폼(form, 입력 봉투)입니다. 로그인, 검색, 글쓰기 — 사용자가 서버에 데이터를 보내는 모든 장면에 form이 있습니다.
<form action="주소" method="get">
<input type="text" name="nickname">
<button type="submit">전송</button>
</form>
action: 이 봉투를 어느 주소로 보낼까method: 어떤 방식으로 보낼까 (get또는post)name: 이 칸의 데이터에 붙는 이름표 — 이것이 없으면 데이터가 서버에 가지 않습니다
input의 type도 중요합니다. text는 일반 입력창, password는 치는 글자가 점으로 가려지는 입력창, submit은 전송 버튼입니다. 화면의 입력창 하나하나가 "이름표를 달고 서버로 떠나는 데이터의 구멍"이라는 감각을 오늘 익힙니다.
2-3. DOM — 브라우저가 그리는 가계도
브라우저는 HTML 파일을 읽으면 그것을 DOM(Document Object Model, 문서 객체 모형)이라는 나무 구조로 바꿔 내부에 저장합니다. <html>이 뿌리이고 그 아래 <head>와 <body>가 가지로 갈라지고, 그 아래에 제목·문단·목록이 매달리는 식입니다.
DOM이 중요한 이유: 브라우저는 화면을 그릴 때 원본 파일을 직접 보는 것이 아니라 이 DOM을 보고 그립니다. 그래서 DOM을 바꾸면 화면이 바뀝니다. 개발자 도구로 글자를 바꿔치기할 수 있는 것도, XSS(악성 스크립트 심기) 공격이 화면을 조작할 수 있는 것도 전부 이 DOM 때문입니다.
2-4. CSS — 선택자로 옷 입히기
CSS의 문법은 한 줄이면 끝입니다. h1 { color: blue; } — "모든 h1 태그에 대해, 글자색을 파랑으로." 여기서 h1처럼 "누구에게 입힐지"를 고르는 부분을 선택자(selector)라고 합니다.
고르는 방법은 세 가지입니다. 태그 이름(h1), 별명인 class(.강조 — 여러 요소가 공유 가능), 고유 이름인 id(#제목 — 한 페이지에 하나만). 이 선택자 문법은 꾸미기에서만 쓰이는 것이 아니라, 나중에 웹 데이터를 긁어 올 때 "원하는 요소만 집어내는 주소"로 그대로 다시 쓰입니다.
3. 따라 하기
3-1. 첫 페이지 — 다섯 줄의 기적
에디터를 열고 아래 내용을 그대로 친 뒤, page.html이라는 이름으로 저장하세요.
<!DOCTYPE html>
<html>
<head><title>내 첫 페이지</title></head>
<body><h1>Hello, Web!</h1></body>
</html>
입력: 파일 탐색기에서 page.html을 더블클릭 (브라우저로 열립니다).
화면 예시: 브라우저 창에 커다란 "Hello, Web!"이 보이고, 창 위의 탭 이름이 "내 첫 페이지"로 바뀝니다.
읽는 법: <!DOCTYPE html>은 "이건 최신 HTML 문서입니다"라는 선언문이고, <title>은 탭 이름을, <h1>은 가장 큰 제목을 만듭니다. 단 다섯 줄인데 웹페이지의 기본 요소(선언, 뿌리, 머리, 몸통, 제목)가 다 들어 있습니다.
왜: 웹페이지가 결국 텍스트 파일이라는 것을 손으로 확인하는 의식입니다. 서버도 인터넷도 없이 브라우저만으로 열린다는 사실을 기억하세요.
3-2. 태그 추가하기 — 내용물 채우기
<body> 안을 아래처럼 채워 봅니다.
<body>
<h1>나의 소개 페이지</h1>
<p>안녕하세요. 보안을 배우는 학생입니다.</p>
<ul>
<li>파이썬을 할 줄 압니다</li>
<li>웹을 배우는 중입니다</li>
</ul>
<a href="https://example.com">참고 사이트</a><br>
<input type="text" placeholder="이름을 입력하세요">
</body>
입력: 저장 → 브라우저에서 F5(새로고침).
화면 예시: 제목 아래 문단, 점 목록 두 개, 파란 링크, 그리고 입력창이 순서대로 나타납니다.
읽는 법: <ul>은 점 목록 통, <li>는 그 안의 항목 하나입니다. <br>은 닫는 태그가 없는 예외로, 줄바꿈을 합니다. placeholder는 입력창에 흐리게 보이는 안내 글자입니다.
왜: 화면의 모든 요소가 태그와 1대1로 대응한다는 감각을 익히는 단계입니다. 나중에 "이 입력창이 서버에 무엇을 보내는가"를 분석할 때 이 감각이 그대로 쓰입니다.
3-3. 스타일 입히기 — 인라인에서 CSS 블록으로
먼저 제목 하나만 빨갛게 물들여 봅니다. <h1>을 이렇게 바꿉니다.
<h1 style="color: red;">나의 소개 페이지</h1>
저장하고 새로고침하면 제목이 빨간색입니다. 태그 안에 직접 쓰는 방식이라 인라인 스타일이라 부릅니다. 간단하지만 페이지가 커지면 관리가 안 됩니다. 그래서 스타일을 한곳에 모은 CSS 블록을 씁니다. <head> 안에 다음을 추가하고, 방금 넣은 style은 지우세요.
<style>
h1 { color: blue; }
body { background-color: #f4f4f4; font-family: sans-serif; }
.강조 { border-left: 4px solid blue; padding-left: 8px; }
</style>
그리고 문단 하나에만 별명을 붙입니다: <p class="강조">안녕하세요. ...</p>
화면 예시: 제목은 파란색, 배경은 연회색, 별명을 붙인 문단에만 왼쪽 파란 세로줄이 생깁니다.
읽는 법: .강조의 점(.)은 "class라는 별명으로 찾는다"는 뜻입니다. 별명을 붙인 요소에만 규칙이 적용됐습니다. #이름이면 id로 찾는 선택자입니다.
왜: 내용(HTML)과 꾸밈(CSS)의 분리가 웹의 기본 설계입니다. 그리고 이 선택자 문법은 후에 데이터 수집(크롤링)에서 "무엇을 가져올까"를 지정하는 언어로 다시 만나게 됩니다.
3-4. 개발자 도구 — 브라우저의 수술대
이제 오늘 가장 중요한 도구를 엽니다.
입력: 페이지 위에서 F12 (노트북은 Fn+F12). 또는 페이지 빈 곳에서 마우스 오른쪽 → "검사".
화면 예시: 화면 옆이나 아래에 패널이 열리고, Elements(요소) 탭에 방금 쓴 HTML이 나무 구조로 보입니다.
읽는 법: 이 나무가 바로 DOM입니다. <h1> 옆의 삼각형을 눌러 접고 펼 수 있고, 글자 부분을 더블클릭하면 직접 수정할 수 있습니다. "나의 소개 페이지"를 "해킹된 페이지"로 바꿔 보세요. 화면이 즉시 바뀝니다.
왜: 명심할 것 — 이 수정은 내 브라우저 속 사본만 바꿉니다. 새로고침하면 원래대로 돌아오고 다른 사람의 화면은 변하지 않습니다. "개발자 도구로 은행 잔고를 바꾸면 부자가 된다"는 농담이 왜 농담인지 이제 원리로 알 수 있습니다. 반대로 XSS가 무서운 이유는 남의 브라우저 속 DOM을 원격으로 바꾸기 때문입니다.
3-5. 로그인 폼 만들기 — 입력은 어디로 가는가
오늘의 핵심 실습입니다. 새 파일 login.html을 만듭니다.
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>로그인 연습</title></head>
<body>
<h1>로그인</h1>
<form action="login_check" method="get">
<input type="text" name="userid" placeholder="아이디"><br>
<input type="password" name="userpw" placeholder="비밀번호"><br>
<button type="submit">로그인</button>
</form>
</body>
</html>
입력: 브라우저로 열고, 아이디에 admin, 비밀번호에 1234를 치고 로그인 버튼 클릭.
화면 예시: 페이지를 찾을 수 없다는 오류가 뜨지만, 주소창을 보세요. 주소가 이렇게 바뀌어 있습니다:
file:///.../login_check?userid=admin&userpw=1234
읽는 법: 입력창에 친 글자가 name이라는 이름표를 달고 주소 뒤에 붙어 떠났습니다. ? 뒤에 이름=값이 &로 이어지는 이 형태가 GET 방식의 데이터 전송입니다. 받아 줄 서버(action의 주소)가 없어서 오류가 뜬 것이지, 전송 자체는 일어났습니다. 비밀번호 칸의 글자는 점으로 가려졌지만 주소에는 평문으로 실렸습니다 — "가려 보이는 것"과 "안전한 것"은 다릅니다.
왜: 이 한 장면이 웹 보안의 축소판입니다. ① 입력값은 name표를 달고 서버로 간다 ② method가 get이면 주소에 드러난다 ③ 서버는 그 이름표로 데이터를 구분한다. 웹 공격의 거의 전부가 이 봉투의 내용이나 목적지를 조작하는 기술입니다.
3-6. 예측해 보기 — 태그를 닫지 않으면
실험입니다. page.html에서 <p> 태그의 닫는 태그를 일부러 지우고 저장하면 화면이 어떻게 될까요? 페이지가 아예 안 열릴까요, 아니면 이상하게 열릴까요? 예측을 적어 두고 직접 확인해 보세요.
직접 확인: 닫는 태그를 지우고 저장 → 새로고침하면, 놀랍게도 페이지는 열립니다. 다만 그 뒤의 내용이 문단의 일부처럼 처리되는 등 구조가 어긋납니다. 브라우저가 "작성자가 깜빡했겠구나" 하고 임의로 닫아 주기 때문입니다. F12의 Elements 탭에서 확인해 보세요 — 브라우저가 만들어 둔 DOM에는 여러분이 쓰지 않은 </p>가 보입니다.
왜: 브라우저의 이 "관대함"은 양날의 검입니다. 조금 틀린 페이지도 열어 주지만, 공격자가 일부러 뒤틀린 HTML을 심었을 때 브라우저가 "친절하게" 해석해 주며 예상 못 한 동작이 나오기도 합니다. HTML을 엄밀하게 쓰는 습관이 곧 보안 습관입니다.
4. 미션과 연습문제
미션 — 내 소개 페이지 + 가입 폼 완성
page.html을 발전시켜 다음 요구사항을 모두 채우세요.
<title>과<h1>에 서로 다른 문구를 넣고, 각각 화면 어디에 나타나는지 비교해 노트에 적습니다- 문단 두 개, 점 목록(3개 항목 이상), 링크 하나를 포함합니다
- CSS 블록에서 제목 색과 배경색을 바꾸고, class를 하나 만들어 특정 문단에만 적용합니다
- 페이지 아래에 "가입 신청" form을 추가합니다 — 입력창 셋(이름 text, 이메일 text, 소개 글 text)과 전송 버튼, method는 get
- 전송 버튼을 눌러 주소창에 세 개의 이름표가 모두 실리는 것을 확인하고, 그 주소 전체를 노트에 베껴 적습니다
- 개발자 도구에서 문단 하나의 글자를 바꾼 뒤 새로고침해 사라지는 것을 확인하고, "왜 사라지는가"를 한 문장으로 적습니다
연습문제
문제 1. <input type="text" placeholder="아이디">만 있고 name이 없는 입력창은 form을 전송해도 데이터가 서버에 가지 않습니다. 왜일까요?
문제 2. class와 id의 차이 두 가지(붙이는 기호, 페이지 안 개수 제한)를 말해 보세요.
문제 3. 개발자 도구에서 다른 사람의 홈페이지 글자를 바꿔도, 그 사이트의 주인 화면은 바뀌지 않습니다. DOM의 무슨 성질 때문인지 설명해 보세요.
문제 4. 3-5에서 비밀번호 칸의 글자는 점으로 가려져 보였는데, 전송 후 주소창에는 평문으로 실렸습니다. 이 둘이 왜 다르며, "보이는 것"과 "전송되는 것"의 관계를 한 문장으로 말해 보세요.
5. 모범 답안과 완료 기준
미션 모범 답안
미션의 뼈대 예시 (문구는 여러분의 것으로 바꾸세요):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>보안 학습자의 페이지</title>
<style>
h1 { color: darkgreen; }
body { background-color: #f0f8ff; }
.강조 { color: crimson; font-weight: bold; }
</style>
</head>
<body>
<h1>안녕하세요, 예비 보안가입니다</h1>
<p class="강조">이 문단에만 빨간 별명이 붙었습니다.</p>
<p>이 문단은 평범합니다.</p>
<ul>
<li>파이썬</li>
<li>HTML/CSS</li>
<li>호기심</li>
</ul>
<a href="https://example.com">참고 사이트</a>
<h2>가입 신청</h2>
<form action="join" method="get">
<input type="text" name="name" placeholder="이름"><br>
<input type="text" name="email" placeholder="이메일"><br>
<input type="text" name="intro" placeholder="한 줄 소개"><br>
<button type="submit">전송</button>
</form>
</body>
</html>
전송 후 주소창 예시 (화면 예시):
file:///.../join?name=길동&email=hong%40example.com&intro=안녕
이메일의 @가 %40으로 바뀐 것에 주목하세요. 주소에 못 싣는 글자를 바꿔 싣는 규칙(URL 인코딩)이 적용된 것입니다.
검증하는 법: ① 탭 이름과 화면 제목이 서로 다른가. ② 전송 후 주소에 ?name=...&email=...&intro=...가 있는가. ③ 새로고침하면 개발자 도구 수정이 사라지는가. 셋이 전부 ‘예’이면 완성입니다.
연습문제 해답
문제 1 해답. name이 데이터의 이름표이기 때문입니다. 서버는 "이름표=값" 쌍으로 데이터를 받는데, name이 없는 입력창의 값은 이름표가 없어서 봉투에 아예 실리지 않습니다. placeholder는 화면 안내 글자일 뿐 전송과 무관합니다.
문제 2 해답. class는 선택자에서 점(.)으로 찾고 한 페이지에 여러 요소가 같은 class를 공유할 수 있습니다. id는 샵(#)으로 찾고 한 페이지에 같은 id가 하나만 존재해야 합니다. 별명(class)과 주민등록번호(id)의 차이입니다.
문제 3 해답. DOM은 각 브라우저가 자기 안에 만드는 사본이기 때문입니다. 개발자 도구의 수정은 내 브라우저 속 사본만 바꾸며, 서버의 원본 파일도 다른 사람의 브라우저 속 DOM도 그대로입니다. 새로고침하면 서버 원본으로 다시 DOM을 만들어 수정이 사라집니다.
문제 4 해답. type="password"의 점 표시는 화면을 보는 사람을 가리는 장치일 뿐, 데이터 자체를 숨기지 않기 때문입니다. 입력값은 평문 그대로 이름표를 달아 전송됩니다. 한 문장: "화면에 안 보이는 것과 통신에서 보호되는 것은 완전히 다른 문제다."
완료 기준 체크리스트
- [ ] 빈 파일에서 웹페이지를 처음부터 만들 수 있다
- [ ] 자주 쓰는 태그(h1, p, a, img, ul/li, form, input, button, div)의 용도를 안다
- [ ] form의 action, method, name의 역할을 설명할 수 있다
- [ ] CSS 선택자(태그, class, id)의 차이를 설명할 수 있다
- [ ] 개발자 도구 Elements 탭에서 DOM을 보고 수정할 수 있다
- [ ] 입력값이 name표를 달아 주소에 실리는 것을 확인했다
- [ ] 미션: 소개 페이지 + 가입 폼을 완성했다
6. 흔한 실수와 해결
벽 1. 태그를 닫는 것을 깜빡했다
증상: 의도와 다르게 뒤의 내용 전체가 제목처럼 커지거나 목록이 이어집니다.
원인: 닫는 태그(</h1> 등)를 빼먹어서 범위가 뒤까지 번졌습니다.
해결: 여는 태그를 찾아 짝이 있는지 하나씩 확인하세요. F12 Elements 탭에서 브라우저가 임의로 닫아 둔 흔적을 보면 어디가 끊겼는지 보입니다.
벽 2. 이미지가 깨진 상자로 나온다
증상: <img> 자리에 찢어진 그림 아이콘만 보입니다.
원인: src에 적은 파일명과 실제 파일명이 다르거나(대소문자 포함), 파일이 다른 폴더에 있습니다.
해결: 이미지를 html 파일과 같은 폴더에 두고, 파일명을 확장자까지 정확히 적었는지 확인하세요. 개발자 도구에서 해당 img 태그에 마우스를 올리면 브라우저가 찾으려 한 경로를 보여 줍니다.
벽 3. 개발자 도구에서 고쳤는데 새로고침하면 사라진다
증상: 분명 수정했는데 F5를 누르니 원래대로 돌아옵니다.
원인: 개발자 도구의 수정은 브라우저 속 사본(DOM)만 바꿉니다. 원본 파일은 그대로입니다.
해결: 오류가 아니라 정상 동작입니다. 영구적으로 바꾸려면 에디터에서 원본을 고치고 저장하세요. 이 차이의 이해가 오늘의 핵심 중 하나입니다.
벽 4. CSS를 썼는데 하나도 안 먹힌다
증상: 색도 글꼴도 그대로입니다.
원인: <style> 블록을 <body> 안에 넣었거나, 세미콜론·중괄호가 틀렸거나, 선택자 이름이 실제와 다릅니다.
해결: <style>은 <head> 안에, 규칙은 선택자 { 속성: 값; } 형태인지 확인하세요. class 선택자의 점(.), id 선택자의 샵(#)을 빼먹는 것이 최다 실수입니다.
벽 5. form을 전송해도 내 데이터가 주소에 안 보인다
증상: 전송 후 주소에 ?가 없습니다.
원인: 입력창에 name 속성이 없거나, method="post"라서 데이터가 주소가 아닌 본문에 실렸거나, 둘 중 하나입니다.
해결: <input>에 name="..."이 있는지 먼저 보세요. method를 get으로 둔 채 name을 붙이면 주소에 이름표가 나타납니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 태그 | 문서의 구조를 표시하는 꼬리표 — 열었으면 닫는다 |
| 속성 | 태그 안에 적는 부가 정보 (href="...", name="...") |
| form | 입력 데이터를 서버로 보내는 봉투 (action=목적지, method=방식) |
| name | 입력창 데이터의 이름표 — 없으면 전송되지 않는다 |
| DOM | 브라우저가 HTML을 읽어 만드는 나무 구조 — 화면은 이것을 보고 그린다 |
| 선택자 | CSS에서 "누구에게 입힐지" 고르는 문법 (태그 / .class / #id) |
오늘의 태그
| 태그 | 하는 일 |
|---|---|
<h1>~<h6> |
제목 (숫자가 클수록 작은 제목) |
<p> |
문단 |
<a href="주소"> |
링크 |
<img src="파일"> |
이미지 |
<ul> / <li> |
점 목록 통 / 항목 |
<form action method> |
입력 봉투 |
<input type name> |
입력창 (text / password / submit 등) |
<style> |
head 안의 CSS 블록 |
명령어보다 중요한 감각
오늘의 핵심 감각은 세 가지입니다. 첫째, 웹페이지는 텍스트 파일이다 — 그래서 누구나 열어 보고(F12) 흉내 낼 수 있습니다. 둘째, 화면은 DOM의 그림이다 — 사본을 고쳐도 원본은 변하지 않고, 반대로 원본의 구조를 알면 화면이 예측됩니다. 셋째, 입력창은 서버로 가는 구멍이다 — 치는 글자는 name표를 달고 떠나며, get 방식이면 주소에 드러납니다. 보안과의 연결: XSS는 이 DOM에 남의 코드를 끼워 넣는 기술이고, 입력값 검증 문제는 전부 이 "구멍"에서 시작합니다. 구조를 아는 사람만이 망가뜨리는 법도, 지키는 법도 압니다.
전부 체크되면 Step 71 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.