개념 사전 · 프로그램

DVWA

Damn Vulnerable Web Application

와 를 사용하는 의도적으로 취약한 웹 애플리케이션이다. 여러 취약점과 난이도별 구현을 비교하면서 웹 보안 점검과 방어 코드의 차이를 배우는 실습 대상이다.

밑줄 용어에 마우스를 올리거나, 키보드로 선택하거나, 터치하면 설명을 볼 수 있습니다.

01무엇이고, 어떤 일을 하나

  • 웹 취약점의 요청·응답과 서버 코드의 관계를 실습한다.
  • 난이도별 방어 처리의 차이를 비교해 단순 문자열 필터와 근본적인 대책을 구분한다.
  • 점검 도구의 출력이 실제 취약점과 일치하는지 확인하는 교육용 대상으로 사용한다.

02어떤 순서로 동작하나

  1. 실습 상태 기록

    공식 배포본의 버전, 선택한 , 데이터베이스 상태를 기록한다. 결과를 비교할 때 이 조건을 동일하게 맞춘다.

  2. 정상 흐름 확인

    교육용 계정으로 로그인하고 선택한 이 입력을 어떤 기능에 사용하는지 정상 동작부터 확인한다.

  3. 입력 처리 추적

    요청 매개변수가 처리와 데이터베이스 질의 또는 HTML 출력으로 이어지는 지점을 살핀다.

  4. 구현 차이 비교

    같은 에서 에 따라 달라지는 검증·인코딩·질의 처리 코드를 비교한다. 결과가 달라진 이유를 코드와 연결한다.

  5. 초기화와 결과 정리

    실습 데이터를 복구한 후 ·관측·권고안을 기록한다. 실습용 데이터베이스 초기화는 자신의 격리 환경에만 적용한다.

03먼저 알아둘 용어와 구성요소

에서 취약점 의 구현·방어 조건을 달리하는 설정이다. 제품 전체의 보안을 인증하는 등급은 아니다.
명령 구조와 입력 값을 분리해 데이터베이스에 전달하는 방법이다. 문자열 연결과 구별한다.
데이터를 HTML·속성· 등 출력 문맥에 맞게 표현해 코드로 해석되지 않게 하는 처리다.
브라우저의 요청을 서버의 로그인 상태와 연결하는 값을 담는 쿠키다.
· 등 특정 취약점 학습을 위해 나뉜 기능 단위다.
같은 결과를 확인하기 위해 맞춰야 하는 버전·설정·계정 권한·요청 등의 조건이다.
LEARN THROUGH PRACTICE

Burp에서 멈춘 요청과 서버에 도착한 요청 구분하기

프록시가 요청을 잡는 위치를 이해하고 브라우저 대기·전달·응답을 한 흐름으로 설명한다.

실습 구성과 전제

  • 자신이 운영하는 격리 와 전용 브라우저 프로필을 사용한다.
  • 브라우저 프록시가 Burp 리스너를 향하도록 설정하고 운영 계정·실제 비밀번호는 입력하지 않는다.
  1. Intercept가 요청을 보류하는 상태를 본다

    • 브라우저에서 로그인 페이지를 열고 Intercept가 켜져 있는지 확인한다. 프록시에서 멈춘 요청은 아직 서버 응답을 받지 않은 상태다.
    • Forward는 보류 요청을 다음 홉으로 보내고 Drop은 버린다. 브라우저가 멈춘 것처럼 보여도 우선 프록시의 대기 요청을 확인한다.
    Burp·Hydra 격리 로그인 실습 · BurpSuite원본 크기로 보기 ↗
    • Proxy→Intercept가 선택돼 있고 Intercept on·Forward·Drop 버튼이 보인다.
    • 목록의 요청은 GET이며 login.php를 향한다. 이 화면만으로 로그인 처리가 완료됐다고 볼 수 없다.
    확인 기준

    Forward 전후 브라우저 상태와 HTTP history에 응답이 생기는지를 비교한다.

  2. 헤더와 폼 본문을 나누어 읽는다

    • Content-Type이 application/x-www-form-urlencoded이면 본문은 이름=값 쌍이다. Cookie는 세션 식별 자료이고 본문의 user_token은 애플리케이션이 별도로 검사할 수 있다.
    • Content-Length는 글자 수가 아니라 전송 바이트 길이와 연결된다. 한글이나 URL 인코딩을 포함하면 보이는 문자열 길이와 다를 수 있다.
    Burp·Hydra 격리 로그인 실습 · BurpSuite원본 크기로 보기 ↗
    • 요청 헤더와 빈 줄 아래 폼 본문이 나뉘며 Content-Type·Cookie·Referer를 볼 수 있다.
    • 본문에는 로그인 입력과 user_token이 함께 있다. 이 값들은 실습 때 새로 받은 값으로 다뤄야 한다.
    확인 기준

    실제 계정·세션 값은 보고서에 붙이지 않고 필드 이름과 처리 결과만 기록한다.

  3. 한 필드씩 바꾼 요청을 비교한다

    • Repeater에는 정상 요청을 기준본으로 보관한다. 무해한 테스트 입력 하나만 바꾸고 메서드·경로·쿠키·토큰·응답 상태를 비교한다.
    • 로그인 화면으로 돌아오면 입력 검증 실패뿐 아니라 세션 만료나 토큰 불일치를 함께 점검한다. 응답 길이 차이만으로 성공을 판정하지 않는다.
    확인 기준

    브라우저의 정상 기능과 같은 세션 조건에서 요청이 처리되는지 확인한다.

실습에서 확인한 동작 원리

  • 프록시 보류는 서버 장애와 다르다.
  • 요청의 메서드·헤더·본문·세션 상태를 함께 보존해야 비교가 재현된다.
관련 개념·전체 실습 화면 이어서 읽기 →
동작·명령 참고 문서
LEARN THROUGH PRACTICE

DVWA의 문자열 결합 쿼리와 결과 열을 연결하기

입력→ 문자열→DB 결과→화면의 연결을 읽고, 이 바꾸는 경계를 설명한다.

실습 구성과 전제

  • 격리된 의 폐기 가능한 데이터베이스를 사용한다.
  • 사진은 구형 · 코드다. 현재 서비스에 복사하지 않고 원리 비교 자료로만 읽는다.
  1. 입력이 SQL 문법 안으로 들어가는 위치를 찾는다

    • 소스에서 GET의 id를 읽은 뒤 WHERE user_id = '$id'에 그대로 넣는 흐름을 따라간다. 따옴표와 변수 값이 하나의 문자열로 합쳐진다.
    • mysql_query 계열은 구형 API다. 보안 교정은 다른 함수 이름으로 바꾸는 것만이 아니라 값과 쿼리 구조를 분리해야 한다.
    SQL Injection 실습 · 실습환경 (2)원본 크기로 보기 ↗
    • $_GET['id']가 SELECT 문자열에 연결되고 mysql_query로 실행된다.
    • 반복문은 결과 열 first_name·last_name을 화면에 출력한다. DB 결과의 두 열과 표시 위치를 연결할 수 있다.
    확인 기준

    문자열 결합 지점과 출력 지점을 각각 표시한다.

  2. 결과 열의 모양이 유지되는 이유를 읽는다

    • 교육 화면은 UNION으로 결합된 두 열이 기존 이름·성 표시 위치에 나타나는 사례다. 결과 집합의 열 수와 호환 가능한 형식이 맞아야 결합될 수 있다.
    • 조회에 사용하는 DB 계정의 권한이 넓으면 애플리케이션이 의도하지 않은 자료도 결과에 섞일 수 있다. 화면의 테스트 해시를 다시 배포하지 않는다.
    SQL Injection 실습 · 실습환경 (4)원본 크기로 보기 ↗
    • User ID 입력 뒤 여러 행이 표시되고 First name·Surname 위치에 계정명과 해시 형태의 값이 나타난다.
    • 표시 레이블은 여전히 이름·성이다. 화면 레이블이 데이터의 실제 의미나 안전성을 보증하지 않는다.
    확인 기준

    한 결과 화면만으로 데이터베이스 전체 권한이나 모든 공격 유형의 성공을 일반화하지 않는다.

  3. 정상 조회를 유지하면서 구조를 고정한다

    • 준비된 쿼리의 값 자리에 사용자 입력을 바인딩한다. 정렬 열처럼 바인딩할 수 없는 구조는 허용 목록으로 고른다.
    • 정상 ID·존재하지 않는 ID·따옴표 포함 입력을 비교하고 오류 내용이나 다른 행이 노출되지 않는지 확인한다.
    cursor.execute('SELECT first_name, last_name FROM users WHERE user_id = %s', (user_id,))

    DB-API 드라이버가 %s 바인딩을 지원한다는 조건의 비교 코드. 문자열 포매팅으로 직접 치환하지 않는다.

    확인 기준

    정상 기능이 유지되고 입력이 쿼리 문법으로 해석되지 않아야 한다.

실습에서 확인한 동작 원리

  • UNION 결과는 기존 출력 열을 통해 보일 수 있다.
  • 바인딩과 DB 최소 권한은 서로 다른 방어층이다.
관련 개념·전체 실습 화면 이어서 읽기 →
동작·명령 참고 문서
LEARN THROUGH PRACTICE

반사 입력과 저장된 DOM을 서로 다른 경로로 읽기

브라우저 화면·DOM·저장 시점을 구분해 의 source와 sink를 설명한다.

실습 구성과 전제

  • 자기 격리 에서 실제 계정 정보가 없는 전용 브라우저를 사용한다.
  • 아래 Reflected 페이지와 Stored 페이지는 서로 다른 과제 화면이므로 한 번의 실행 순서로 합치지 않는다.
  1. 입력이 어디서 화면으로 돌아오는지 찾는다

    • Reflected 과제에서는 폼 입력이 현재 응답에 반영되는 경로를 본다. 먼저 lab-marker 같은 무해한 문자열로 화면에 나타나는 위치를 기록한다.
    • 표시됐다는 사실만으로 코드 실행을 확정하지 않는다. HTML 소스와 DOM에서 텍스트인지 요소인지 확인한다.
    XSS · 설정·관리 화면 1원본 크기로 보기 ↗
    • 제목은 Reflected Cross Site Scripting이며 이름 입력칸과 Hello 출력 위치가 보인다.
    • 왼쪽에 Stored와 DOM 과제가 별도로 있어 유형이 다른 실습임을 구분할 수 있다.
    확인 기준

    현재 요청과 응답에서 입력이 반영되는 위치를 특정한다.

  2. 저장 폼과 DOM의 실행 문맥을 확인한다

    • Stored 과제에서는 입력이 저장된 뒤 다른 조회에서도 다시 렌더링되는지 본다. 재방문에 나타나는 것과 스크립트로 실행되는 것은 별도로 기록한다.
    • textarea의 maxlength 같은 브라우저 조건은 서버 을 대신하지 않는다. 최종 sink가 HTML·속성·스크립트 중 무엇인지 결정해야 한다.
    XSS · 실습·개념 참고 화면 4원본 크기로 보기 ↗
    • DOM에 Stored Cross Site Scripting 제목과 POST guestform이 표시된다.
    • mtxMessage textarea의 maxlength가 1000으로 보인다. 길이 제한이 있어도 출력 문맥 처리가 필요하다.
    확인 기준

    저장 여부·출력 위치·실행 여부를 세 항목으로 나눈다.

  3. 텍스트 출력으로 같은 입력을 비교한다

    • 허용된 HTML이 필요하지 않은 영역은 textContent나 문맥에 맞는 을 사용한다. HTML을 허용해야 한다면 정화 정책을 별도로 설계한다.
    • 문자열 '<b>lab</b>'를 예로 들어 요소로 렌더링하는 출력과 그대로 글자로 표시하는 출력을 비교한다.
    const value = '<b>lab</b>';
    output.textContent = value;

    교육용 DOM 비교 예시. 입력을 코드로 실행하지 않고 글자로 표시한다.

    확인 기준

    수정 후에는 정상 글자가 사라지지 않으면서 데이터가 실행 문맥을 만들지 않아야 한다.

실습에서 확인한 동작 원리

  • 반사·저장·DOM 경로는 입력의 이동 방식과 실행 시점을 나누는 기준이다.
  • 실제 방어는 최종 출력 문맥에 맞춰 적용한다.
관련 개념·전체 실습 화면 이어서 읽기 →
동작·명령 참고 문서
LEARN THROUGH PRACTICE

쿠키가 동반된 상태 변경 요청을 패킷에서 읽기

정상 로그인 상태가 사용자의 변경 의도를 증명하지 않는다는 점을 실제 요청 구조로 설명한다.

실습 구성과 전제

  • 격리 의 폐기 가능한 계정으로만 상태 변경을 시험한다.
  • 사진 속 요청과 테스트 값은 당시 교육 환경의 기록이다. 자신의 환경에서는 새 토큰과 무해한 변경 항목을 사용한다.
  1. 상태 변경을 유발하는 입력 위치를 찾는다

    • 사진의 폼은 게시 메시지 안에 다른 기능을 향하는 링크를 넣은 사례다. 메시지 저장과 링크 목적지의 상태 변경은 서로 다른 요청이다.
    • 링크를 봤다는 것·눌렀다는 것·서버가 변경을 수락했다는 것을 각각 구분한다.
    7/20 · 실습·개념 참고 화면 2원본 크기로 보기 ↗
    • URL-encoded 폼의 mtxMessage에 링크가 들어가며 별도의 변경 기능 경로를 가리킨다.
    • 버튼 값은 Sign Guestbook이다. 겉으로 보이는 게시 기능과 링크의 목적 기능이 다르다.
    확인 기준

    이 첫 화면은 입력 저장 요청이며 계정 변경 성공의 증거는 아니다.

  2. 자동으로 붙는 인증 자료를 확인한다

    • 다음 캡처에는 변경 기능을 향한 GET과 Cookie가 함께 보인다. 브라우저가 인증 자료를 첨부해도 사용자가 그 변경을 의도했다고 증명할 수는 없다.
    • GET은 안전한 조회 의미로 설계해야 하며, 상태 변경을 GET에 맡기지 않는다. 메서드를 POST로 바꾸는 것만으로 가 해결되는 것도 아니다.
    7/20 · 실습·개념 참고 화면 3원본 크기로 보기 ↗
    • 요청 행은 비밀번호 변경 기능을 향한 GET이고 Cookie 헤더가 포함된다.
    • Referer는 다른 과제 페이지를 나타내며 Response in frame 링크가 제공된다. 실제 변경 판정에는 대응 응답과 계정 상태가 더 필요하다.
    확인 기준

    응답 메시지뿐 아니라 서버 상태가 바뀌었는지 확인한다.

  3. 의도 검증 실패가 변경 전에 끝나는지 시험한다

    • 정상 토큰·누락 토큰·다른 세션 토큰으로 요청을 구분한다. 토큰 검증 실패는 실제 변경 코드가 실행되기 전에 거부돼야 한다.
    • SameSite·Origin·Fetch Metadata는 보조 조건이며 배포 환경의 정상 요청과 함께 시험한다.
    확인 기준

    누락·불일치 토큰 요청 뒤 계정 정보가 그대로인지까지 확인한다.

실습에서 확인한 동작 원리

  • 인증된 요청과 의도된 요청은 다르다.
  • 거부 화면이 아니라 변경이 수행되지 않았다는 상태 증거가 필요하다.
관련 개념·전체 실습 화면 이어서 읽기 →
동작·명령 참고 문서
LEARN THROUGH PRACTICE

업로드 성공과 서버 실행 가능성을 두 단계로 분리하기

파일이 저장되는 경계와 웹 서버가 그 파일을 실행하는 경계를 나누어 분석한다.

실습 구성과 전제

  • 교육용 의 격리 스냅샷에서 화면을 읽는다.
  • 직접 재현은 무해한 텍스트·이미지 파일로 한정하고 운영 서버나 실제 웹셸은 사용하지 않는다.
  1. 업로드 응답이 보장하는 범위를 읽는다

    • 성공 메시지는 서버가 선택한 경로에 파일을 저장했다는 단서다. 안전 검사 통과나 비실행 저장소 분리까지 증명하지 않는다.
    • multipart의 파일명·Content-Type은 클라이언트가 보낸 입력이다. 서버에서 확장자·형식·크기·저장 이름을 독립적으로 결정해야 한다.
    파일 포함·업로드와 실행 경계 · file upload원본 크기로 보기 ↗
    • File Upload 화면에 PHP 확장자의 파일이 업로드됐다는 메시지가 표시된다.
    • 반환 경로가 웹 애플리케이션 아래 uploads 위치를 가리킨다. 제공 경로와 실행 정책을 이어서 확인해야 한다.
    확인 기준

    업로드 성공과 파일 제공 정책을 별도 항목으로 적는다.

  2. 제공 경로가 실행 경로가 되는 조건을 찾는다

    • 다른 화면은 업로드 파일에 요청을 보내 서버 측 작업 디렉터리가 응답된 교육 사례다. 실행 권한이 없는 파일 저장소라면 같은 방식으로 프로그램이 실행되지 않아야 한다.
    • 웹 루트 밖 저장, 서버가 만든 식별자, 정적 다운로드 처리, 실행 엔진과의 분리가 서로 다른 통제다.
    파일 포함·업로드와 실행 경계 · file upload원본 크기로 보기 ↗
    • 주소 경로에 업로드된 PHP 파일이 있고 화면에 서버 디렉터리 결과가 나타난다.
    • 업로드 메시지와 달리 이 화면은 저장 파일이 실행 경로에 놓였음을 보여 주는 별도 증거다.
    확인 기준

    공격 입력을 반복하기보다 저장소·확장자 처리·서버 핸들러 설정을 조사한다.

  3. 무해한 파일로 수정 경계를 확인한다

    • 허용 이미지, 잘못된 확장자, 제한 크기 초과를 각각 제출한다. 저장 후 이름이 서버에서 생성되는지와 다운로드 응답의 MIME·Disposition을 확인한다.
    • 검사 중 파일은 제공하지 않고, 검사한 바이트와 제공하는 바이트가 같은지 확인한다.
    확인 기준

    거부된 파일이 디스크나 공개 경로에 남지 않는지도 점검한다.

실습에서 확인한 동작 원리

  • 업로드 성공은 안전 판정이 아니다.
  • 저장 정책과 웹 실행 정책을 함께 분리해야 한다.
관련 개념·전체 실습 화면 이어서 읽기 →
동작·명령 참고 문서

04예제로 따라가기

  • 공식 코드 비교: Low와 Impossible을 읽는 예다. 실행 결과를 주장하지 않으며 저장소 master의 구현은 추후 달라질 수 있다.
  • Low의 값 경계: 요청 id가 질의 문자열에 직접 이어 붙는다. 정상 id=1과 따옴표를 포함하는 입력은 동일한 데이터 전달 구조가 아니며, 후자는 문법에 영향을 줄 수 있다.
  • Impossible의 순서: user_token 검사 뒤 id가 숫자인지 확인하고 정수로 변환한다. 이어 준비된 질의의 :id에 정수 타입으로 값을 바인딩한다.
  • 정상·거부 차이: id=1은 숫자 검사 경로를 통과할 수 있지만 id=abc는 통과하지 못한다. 조회 결과 존재 여부·세션 token 유효성은 별개 조건이다.
  • 방어 역할: 숫자 검사는 업무 입력의 형식을 제한하고 바인딩은 구조와 값을 분리한다. token 검사는 문맥이므로 세 처리를 같은 방어라고 부르지 않는다.

05혼동하기 쉬운 점과 확인 기준

  • 는 진단 도구 자체가 아니라 진단과 학습에 사용하는 취약한 대상 애플리케이션이다.
  • 난이도 설정과 버전을 생략하면 다른 사람의 실습 결과를 재현하기 어렵다.
  • 공식 README는 인터넷에 노출된 운영 서버에 설치하지 않도록 안내한다. 이 노트 배포 패키지에는 설명만 포함된다.

06상세 노트 · 실습 자료

헤더 구조, 설정 예시, 수치 계산과 수업 캡처는 아래 노트에서 이어집니다.

READ · OBSERVE · CONNECT

함께 보는 학습 자료

아래 공식 자료를 본문의 개념·예제와 함께 읽어 보세요. 자료별로 확인할 위치와 관찰할 내용을 적었습니다.

실습 가이드

DVWA SQLi Low: 입력이 질의 문자열에 들어가는 위치

에서 id를 읽는 지점과 문자열 생성 지점을 코드로 연결한다.

읽으면서 확인할 것
  • 요청 id의 취득 위치
  • 문자열 연결이 만드는 데이터· 구문 경계
  • DB 종류별 분기와 실제 선택된 코드 경로
실습 가이드

DVWA SQLi Impossible: 검사와 바인딩의 역할

Low와 나란히 읽으며 token·숫자 검사·정수 변환·준비된 질의를 서로 다른 통제로 구분한다.

읽으면서 확인할 것
  • checkToken과 is_numeric의 선후 관계
  • :id 자리표시자와 정수형 바인딩
  • 입력 형식 검사와 구조 분리의 차이

공식 문서 · 표준

기초 개념과 동작 설명을 대조한 자료입니다. 제품 버전과 실제 설정에 따른 차이는 본문 및 원문에서 확인하세요.