해킹스터디

[Normaltic 웹해킹 입문] 1강 주요 과제-APM 환경에서 로그인 페이지 만들기(php 이용, DB 연동X)

herini0829 2025. 10. 28. 00:03

 

회사에서는 우분투, 집에서는 칼리로 계속 실습 중.
첫번째 주부터 여행으로 놓치니 초보인 내가 따라가기가 쉽지 않다.. 기록용으로 정리!
이 글은 “내가 왜 이렇게 썼는지”까지 다 적은 복습노트의 개념이다

만들 기능은 기본 로그인 페이지

  1. login.php
    • 아이디/비밀번호를 입력할 수 있는 화면(폼)
    • 사용자가 로그인 버튼을 누르면 데이터를 login_proc.php로 보냄
    • 로그인 실패 시 에러 메시지를 화면에 띄움 (동적)
  2. login_proc.php
    • 실제로 아이디/비밀번호를 받아서 검사하는 PHP 로직
    • 정상 접근인지 확인하고 (POST만 허용)
    • 성공이면 main.php로 보내고, 실패면 다시 login.php로 돌려보냄
  3. main.php
    • 로그인 성공했을 때 가는 페이지 (힘들어서 예쁘게 안꾸밈...)
  4. style.css
    • 화면을 예쁘게(배경 어둡게, 박스 가운데 놓고, 빨간 에러 메시지 등)

이 네 개 파일이 /var/www/html/weblogin 폴더 안에 있다는 전제

Apache가 돌아가고 있다면 브라우저에서
http://localhost/weblogin/login.php
로 접속해서 테스트할 수 있음

(https는 아직 안 된다. 그 얘기는 뒤에서 다시 다룰 것)

  • 앞으로 작업 위치: /var/www/html
  • 그 아래에 weblogin이라는 디렉토리를 하나 만들었다!
  • 왜 sudo/root?
    /var/www/html은 웹서버 문서 루트라 일반 유저 권한으로 수정 안 될 때가 많음..
    저장이 안 되거나 "Permission denied" 또는 갑자기 코드 날아가면 멘탈 나감.. 걍 root 또는 sudo로 편하게 하자..
  • Apache 기본 설정에서는 /var/www/html 아래에 있는 파일들이 http://localhost/... 로 감.
  • vi/vim에서 한글 입력 미리 세팅해두는 게 정신건강에 이로움..(+VM, vim, 호스트OS 간 클립보드 허용도..)
    (안 하면 한글이  입력 안되거나 깨짐. “vim 한글 입력” 검색해서 본인 환경에 맞게 설정)

최종적으로 이 디렉토리 안에 만들 파일은:

  • login.php
  • login_proc.php
  • main.php
  • style.css

1. login.php

login.php 코드 전문

이 파일은 "사용자에게 보여지는 화면"임
아이디/비밀번호를 입력하는 폼이 여기 있음
그리고 중요한 포인트: 에러 메시지가 "조건부로" 나타남

전체 코드는 이렇게 생김:

아래부터 줄별로 씹고 뜯고 맛봐보자.

  • <!DOCTYPE html>: 그냥 HTML5 문서라고 선언하는 약속. 안 쓰면 일부 브라우저에서 예전 모드(호환 모드)로 랜더링할 수도 있어서 항상 넣는다고 보면 됨. 사실상 "그냥 제일 위에 항상 이거 쓰는 거"라고 외우자
  • <html>: 페이지 전체를 감싸는 최상위 태그.
  • lang="ko": 이 문서의 기본 언어는 한국어라고 알리는 속성.
    • 화면에 직접 보이진 않지만 중요함.
    • 스크린 리더(시각장애인용 읽기 도구)가 이걸 보고 한국어 발음으로 읽는다.
    • 브라우저 번역기나 검색엔진도 이 정보를 사용한다.

즉 <html lang="ko"> = "이 페이지는 한국어 페이지야."

<head> 안은 실제 화면에 표시되는 내용은 아니고, 페이지 설정 정보들이다.

  • &lt;meta charset="UTF-8"&gt;
    → 이 문서는 UTF-8 인코딩이다!
    → 이걸 선언 안 하면 한글이 깨질 수도 있음.
    → 한글 쓰는 사이트는 이거 없으면 거의 100% 깨진다고 보면 된다고 함
  • &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    → 모바일에서 페이지가 너무 확대/축소돼서 깨져 보이지 않게 해준다.(애플이 생기면서 생긴거라고..?)
    → "화면 너비를 기기 화면 너비에 맞춰줘" 이런 의미.
  • &lt;title&gt;로그인 페이지&lt;/title&gt;
    → 브라우저 탭 제목에 보이는 글자.
  • &lt;link rel="stylesheet" href="style.css"&gt;
    → CSS 파일 연결.
    → href="style.css" 라고 썼으니까 이 login.php와 같은 디렉토리(weblogin) 안의 style.css를 읽어온다.

여기서 rel="stylesheet" 는 "이 링크는 스타일시트(디자인 정보)다"라는 의미.
href="..." 는 "어디 파일에서 그걸 가져올지"를 알려준다.

  • <body>: 이제부터 화면에 실제로 보일 내용이 시작된다는 뜻.
  • class="login-body":
    • body 태그에 클래스를 하나 달아준 것.
    • CSS에서 .login-body { ... } 로 body 전체 스타일(배경색, 정렬 등)을 제어할 수 있다.

여기서 중요한 포인트
→ <body class="login-body"> 는 <body>랑 다르게 보이는 게 아니라, <body> 시작 + “추가 속성(class)”을 준 것.
HTML 태그는 <태그명 속성="값" 속성="값"> 이런 식으로 여는 게 기본 문법이다.

  • <div> = "구역을 나눈다" / "박스를 하나 만든다" 정도로 이해하면 편함.
  • class="login-container" 라고 이름 붙이면 CSS에서 이 부분만 따로 꾸밀 수 있다.
  • 이 login-container는 화면 가운데 떠 있는 로그인 박스 그 자체다.
    • width, 배경색, 보더, 그림자 등을 style.css에서 지정해줄 거다.

안쪽에 또 이런 게 들어있다:

  • login-header 라는 또 다른 클래스로 감싸서
    • 큰 제목(h1)
    • 안내문(p)
      를 묶어둔 것.
  • 왜 이렇게 또 나눔?
    • 제목 부분만 가운데 정렬하고, 글자 크기 다르게 하고, 아래쪽 여백 주고... 이런 스타일을 주고 싶기 때문.
    • 구조를 쪼개두면 CSS에서 제어가 훨씬 깔끔해진다.

이 부분이 솔직히 제일 헷갈렸는데, 이제 이해했다.

이게 하는 일은 딱 하나다:

로그인에 실패해서 다시 돌아온 경우에만 에러 메시지를 보여줘.

자세히 보면:

  • $_GET['error']
    → URL의 쿼리스트링(?error=1)에서 넘어온 값.
    예: login.php?error=1 이라고 접속하면 $_GET['error'] 는 1이 된다.
  • isset($_GET['error'])
    → error라는 값이 URL에 실제로 존재하는지 체크.
    값이 없는데 바로 $_GET['error']를 읽으면 경고 뜰 수 있으니까 방어용으로 씀.
  • $_GET['error'] == '1'
    → 그 값이 '1'인지 확인.
  • &&
    → 그리고 (AND).
    → 즉 둘 다 참이어야 전체가 참.

그러니까

if (isset($_GET['error']) && $_GET['error'] == '1') {

라는 건 이런 뜻이다:

"URL에 error=1 이라고 붙어서 이 페이지에 들어왔다면,
아래 HTML(error-message div)을 출력하자."

그럼 <div class="error-message">아이디 또는 비밀번호가 올바르지 않습니다.</div> 이 부분이 실제로 페이지에 찍혀서 빨간 경고 박스로 나타난다.

만약 그냥 처음 접속해서 login.php만 열었다면 ?error=1이 없으니까 저 div 자체가 아예 출력 안 된다.
= 깔끔한 화면.

여기서 가장 헷갈렸던 문법:

<?php if (...) { ?>
    <div>...</div>
<?php } ?>

왜 PHP 열었다 닫았다 다시 열었다 닫냐?

  • <?php if (...) { ?>
    • PHP 모드로 들어가서 if문을 연다. { 까지 쓴다.
    • 그리고 ?> 로 PHP를 잠깐 “일시정지”한다.
  • 그 다음에 일반 HTML을 쓴다.
  • 마지막에 <?php } ?>
    • 다시 PHP 모드로 돌아와서 if문의 }를 닫는다.

즉 "조건부로 HTML 블럭을 넣고 싶어서" PHP를 잠깐 끊고 다시 켠 거다.

중요한 포인트:

  • <form ... method="POST" action="login_proc.php">
    → 사용자가 로그인 버튼을 누르면
    이 <form> 안의 값들을
    POST 방식으로
    login_proc.php한테 보낸다.
  • <input ... name="username">
    여기서 name="username" 이 핵심
    서버(login_proc.php)에서는 이 값이 $_POST['username'] 으로 들어온다.
    즉 "username이라는 이름의 상자에 담아서 보내겠다"라고 이해하면 편함.
  • <input type="password">
    브라우저 화면에서 입력한 글자를 ****처럼 가려준다.
    (서버로 갈 때는 실제 값이 그대로 간다. 단순히 화면 표시용 마스킹이다.)
  • <button type="submit">로그인</button>
    이 버튼을 누르면 form이 전송된다.

여기까지가 login.php.
말하자면 “로그인 폼 + (실패 시) 에러메시지 표시용 페이지”다.

완성된 login.php 페이지

 

2. login_proc.php(얘는 실제 보이는 화면이 아님)


"로그인 시도 결과를 판단하고, 어딜로 보낼지 결정하는 애"다.
즉 직접 보는 페이지가 아니라, 중간 심판 같은 역할.

전체 코드는 이렇게 썼다:

한 줄씩 해부해보자.

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  • $_SERVER 는 PHP가 자동으로 채워주는 "서버/요청 정보" 모음(슈퍼글로벌).
  • $_SERVER['REQUEST_METHOD'] 는 이 파일이 어떤 방식으로 호출되었는지 알려준다.
    예: GET, POST 등.
  • 우리는 <form method="POST" ...> 로 login_proc.php를 호출할 예정.
    → 정상 루트라면 무조건 POST여야 한다.
  • 그래서 === 'POST'
    → "정확하게 POST일 때만" 이라는 의미.
    === 는 "값 + 타입까지 완전히 같다"는 엄격 비교 연산자.

결국 이 줄은:

"이 파일은 POST로 불린 경우(폼 제출을 통해 정상적으로 온 경우)에만 처리 계속하겠다.
만약 누가 주소창으로 직접 login_proc.php를 치면 GET이니까 else로 빠질 거다."

이걸로 이상한 접근(우회 접근)을 1차 차단한다.

$username = isset($_POST['username']) ? trim($_POST['username']) : '';
$password = isset($_POST['password']) ? $_POST['password'] : '';

여기가 중요하고 어려움..

  • $_POST 는 <form method="POST"> 로 넘어온 값들이 들어 있는 전역 배열이다.
    • $_POST['username'] → 로그인 폼의 아이디 입력값
    • $_POST['password'] → 로그인 폼의 비밀번호 입력값
  • isset($_POST['username'])
    → 저 값이 실제로 존재하는지 확인.
    (안 그러면 값이 없을 때 바로 꺼내다가 "undefined index" 같은 경고 날 수 있다.)
  • 삼항 연산자 조건 ? A : B
    • 형식은 X ? Y : Z
    • 의미: X가 참이면 Y, 거짓이면 Z를 결과로 써라.
    • 여기선 “username이 존재하면 trim(….) 해서 넣고, 아니면 빈 문자열 넣어라”.
  • trim($_POST['username'])
    • 아이디 앞뒤 공백 제거.
      예: " admin " → "admin"
    • 사용자가 실수로 스페이스 넣은 거 때문에 로그인이 안 되는 경우를 줄여준다.
  • password 쪽은 trim()을 안 했다.
    이유: 비밀번호는 공백 자체도 실제 값으로 취급하고 싶을 수 있기 때문.
    "1234" 와 "1234 " 는 다르다고 보고 싶은 경우가 많다.

정리하면:

  • $username에는 "아이디 입력값(앞뒤 공백 제거 버전)"이 들어간다.
  • $password에는 "비번 입력값(있는 그대로)"이 들어간다.
  • 만약 뭔가 이상해서 값이 안 넘어왔다면 그냥 빈 문자열이 들어가게 해서 에러를 막는다.
if ($username === 'admin' && $password === '1234') {
  • $username === 'admin'
  • $password === '1234'

이 코드에서는 id, pw가 하드코딩되어 있다.
진짜 서비스라면 DB에서 사용자 정보를 가져와서 비교해야 한다.
하지만 이번 과제는 거기까진 아니었어..!

&& 는 AND (그리고).
둘 다 참이어야 if 안으로 들어간다.

즉:

아이디가 admin이고 비밀번호가 1234면 “로그인 성공”으로 인정해라.

header('Location: main.php');
exit();
  • header('Location: main.php');
    → PHP가 브라우저에게 "main.php로 이동해"라고 HTTP 헤더를 보낸다.
    즉 리다이렉트 시킨다.
  • exit();
    → 코드 실행을 여기서 끊는다.
    이게 없으면 혹시 아래 이상한 코드가 더 실행될지도 모르니 !
    보안적으로도 깔끔하게 끊는 게 좋다고 한다.

main.php는 로그인 성공 후에 보여줄 페이지다.(아직 안꾸밈 ㅜㅜ)

header('Location: login.php?error=1');
exit();

이 줄이 login.php와 완전히 연결된다.

  • 실패하면 다시 login.php로 돌려보내는데, 그냥 보내지 않고 ?error=1 을 붙인다.
  • 그래서 사용자는 사실상
    http://localhost/weblogin/login.php?error=1
    로 다시 오게 된다.
  • login.php 안에서는 $_GET['error'] == '1' 이면 빨간 에러 메시지 div를 찍도록 되어 있다.
    즉 "아이디 또는 비밀번호가 올바르지 않습니다."가 뜬다.

→ 여기서 우리는 “정적인 HTML 페이지”가 아니라 “조건에 따라 다른 HTML을 보여주는 동적 페이지”를 만든 거다.

} else {
    header('Location: login.php');
    exit();
}
  • 이건 아까 첫 if의 else에 해당한다.
  • 즉, 만약 이 파일이 POST 방식으로 호출된 게 아니라면(=정상적인 폼 제출이 아님),
    그냥 login.php로 돌려보낸다.

필요한 이유는

  • 누가 브라우저 주소창에 http://localhost/weblogin/login_proc.php 라고 직접 치고 들어올 수도 있음
  • 그건 우리가 의도한 흐름이 아님. (폼 안 거쳤으니까)
  • 그런 경우를 여기서 걸러주는 것

이렇게 해서 login_proc.php는
"POST로 들어온 로그인 시도만 처리하고, 나머지는 다 튕겨내는 확인자 역할을 함!

 

 

 

참고사항!

1) HTTPS 안 되는 이유

  • http://localhost/... 는 잘 되는데
    https://localhost/... 는 기본 상태에선 안 된다.
  • 왜냐면 https는 "그냥 http + s"가 아니고:
    • SSL/TLS 인증서
    • 443번 포트
    • Apache SSL 모듈 설정
      이 3가지가 세트여야 한다.
  • 우리는 지금 그냥 기본 Apache 설치로 80포트(http)만 열어둔 상태라서 https 요청은 실패하는 게 정상이다.
  • 즉 "왜 https 안 돼요?" → 정상임. 아직 https 준비 안 한 거.

2) main.php에 그냥 바로 들어가는 문제
현재 상태에서는 브라우저 주소창에 그냥
http://localhost/weblogin/main.php
라고 치면, 로그인 안 해도 main.php가 열린다.

왜냐면 main.php는 지금 아무 검증이 없기 때문.
"이 사람이 실제로 로그인에 성공한 사용자냐?" 라는 체크가 없다.

그래서 등장하는 개념이 "세션" 인데 이건 아직 안배워서 다음에 다루겠다.

 

<기타> stlye.css

style.css

이 파일이 실제로 화면을 예쁘게 만들어준다.
login.php에서 <link rel="stylesheet" href="style.css"> 로 가져온다.

/var/www/html/weblogin/style.css:

이 CSS에서 중요한 포인트들:

  • .login-body
    • 배경색을 어둡게 깐다
    • display: flex; justify-content: center; align-items: center; min-height: 100vh;
      → 화면 전체에서 가로세로 가운데 정렬
    • 즉 로그인 박스가 화면 정중앙에 뜨도록 만드는 핵심
  • .login-container
    • 실제 로그인 박스(카드)
    • 둥근 모서리, 어두운 배경, 그림자
    • 폭 고정 (320px)
  • .error-message
    • 빨간 느낌으로 경고창처럼 표시
    • margin-bottom으로 아래 폼과 간격 확보
  • .form-group
    • label + input 묶음 한 줄
    • column 방향으로 세로 정렬
  • .login-btn
    • 파란색 버튼
    • hover시 살짝 색/그림자 변화

이제 할 수 있는 것들 정리하면:

  • HTML로 폼 만들고
  • CSS로 화면 예쁘게 하고(피티햄이 도와줌..)
  • PHP로 POST 데이터를 받고
  • PHP에서 조건(if)으로 판정해서
  • header('Location: ...') 로 다음 화면을 결정하고
  • URL 파라미터에 따라 에러 메시지를 "필요할 때만" 띄운다

다음 과제

  • 하드코딩된 ID/PW 대신 DB 연동해서 진짜 계정 확인하기