Authentication Bypass
이번 5주차의 CTF에서는 문제가 9개로 늘어났다. 바로 write-up으로 들어가겠다.
1. Get Admin
주어진 주소로 들어오면 단순한 로그인 창이 반겨준다. 문제에서 ID: `doldol`, PW: `dol1234`라는 로그인 정보를 줬으니 한 번 로그인해보겠다.
별 거 없다. 상단의 Home, About, Contact 버튼을 눌러도 아무런 작동도 하지 않는다. (Log out 버튼은 로그아웃으로 작동한다)
지난번에 이용했던 Burp Suite를 이용해서 어떤 패킷이 오가는지 살펴보겠다.
빨간색 네모를 보면 내가 작성한 ID와 PW가 패킷 바디 부분에 담겨 전달된다. 서버에서는 ID와 PW를 검사한 뒤, 적합하다면 `loginUser`라는 쿠키에 ID값을 담아서 준다. 응답코드가 302인 것과, `Location` 값이 index.php인 것을 보아 바로 index.php 파일로 리다이렉션을 시킨다.
클라이언트는 `loginUser`의 쿠키값으로 `doldol`을 전달하고, 그에 대한 답으로 서버는 `200 OK`를 띄운다.
index.php에 접근할 때는 쿠키값으로만 인증하는 것이 아닌가 하는 생각이 들 수 있다. 따라서 `loginUser`의 쿠키값을 `admin`으로 변조해보겠다.
정확히는, 서버가 Set-Cookie를 통해 쿠키를 전달할 때 그 값을 `admin`으로 변조해보겠다.
Resposne의 `Set-Cookie` 값을 변조해서 받겠다.
그럼 클라이언트는 자연스럽게 이 `admin`이라는 쿠키값을 이용해 index.php를 요청한다.
Flag를 얻을 수 있다.
2. PIN CODE Bypass
핵미사일을 발사하라고 한다. 나같은 평화주의자에게 어떻게 이런 가혹한 시련을..
바로 발사할 수 있는 줄 알았더니 관리자만 이용 가능하다고 한다.
하지만 URL을 보다보면 약간 특이한 점을 발견할 수 있다.
처음 이 상태는 단순히 index.php인 것으로 보인다. 하지만 Fire를 누르면 아예 다른 파일로 접근한다.
여기서 확인 버튼을 누른다면 또다른 페이지로 이동한다.
그렇다면 아예 step3.php를 들어가버리면 되는거 아닌가?
그냥 발사하게 해준다..
Fire 버튼을 누르면 다음과 같이 Flag가 뜬다.
3. Admin is Mine
또 admin을 털라고 한다.
이번엔 login.js라는 새로운 파일을 같이 받았다. 해당 파일의 내용은 다음과 같다
const HIDDEN_CLASS = "hidden";
const loginForm = document.querySelector("form");
function onLogin(e) {
e.preventDefault();
const userId = loginForm.querySelector("#inputUserid").value;
const userPw = loginForm.querySelector("#inputPassword").value;
const url = `/4/loginProc.php?userId=${userId}&userPw=${userPw}`;
fetch(url)
.then((response) => response.json())
.then((data) => {
const resultData = data.result;
console.log(resultData);
if (resultData == "ok") {
// login Success
const errorMessage = document.querySelector("#errorMsg");
errorMessage.classList.add(HIDDEN_CLASS);
location.href = "index.php";
} else {
// login Fail
const errorMessage = document.querySelector("#errorMsg");
errorMessage.classList.remove(HIDDEN_CLASS);
}
});
}
loginForm.addEventListener("submit", onLogin);
loginProc.php로 입력된 ID와 PW를 보내 결과를 확인한 뒤, 클라이언트를 index.php로 보내는 것 같다.
단순히 loginProc.php로 ID와 PW를 보낼 때 PW를 안 보내고 ID만 넘기면 어떻게 되는가 궁금해서 해봤다.
200 OK가 돌아오긴 했지만 내용은 괴상한 값을 담고 있었다.
하지만 Burp Suite의 HTTP history를 보니, login.js가 자동으로 index.php을 요청했던 것에 대한 응답이 있었다.
뚫렸다.
4. Pin Code Crack
지금까지 본 사이트들과는 디자인이 좀 다르다. LOGIN을 눌러보겠다.
?
이걸 내가 어떻게 알지. 당장 저 전화번호로 개통할 수도 없는 노릇이다.
하지만 숫자 4자리라는 힌트가 주어졌다. 만약 이 사이트가 시도 횟수에 제한을 두지 않았다면, 0000 ~ 9999까지 무한으로 도전을 하면 된다.
생각해보니 0000부터 9999까지 다 입력하려면 내 정신건강이 안 좋아질 것 같다.
마침 checkOTP.php에 GET으로 숫자 4자리를 보내, 맞는지 체크하는 것을 알 수 있다.
아마 정확한 숫자가 아니라면 다 똑같은 Login Fail 결과를 띄울 것이다. 그 말인즉슨, Response의 Content 길이가 전부 똑같을 것이라는 것이다.
0부터 9999까지 시도하면서 유일하게 Content-Length의 값이 다른 하나의 숫자를 찾아내면 된다. 일단 0000으로 시도했을 때의 Response 패킷을 살펴보자.
우리에게 필요한 것은 빨간색 네모로 강조된 `Content-Length`다. 이제부터 시도한 숫자가 틀린다면 전부 `83`의 길이를 가진 내용이 돌아올 것이다. 이를 이용해 파이썬으로 무한반복을 돌릴 코드를 짤 수 있다.
import requests
origin_url = '~~~/6/checkOTP.php?otpNum='
for i in range(0,10000):
url = origin_url + str(i)
r = requests.get(url)
if(r.headers['Content-Length'] != '82'):
print('OTP:', i)
print('======================')
print('Length:', r.headers['Content-Length'])
break
사실 파이썬을 이용해 요청을 해서 Login Fail이 뜨면 `Content-Length`가 `82`로 나온다. 따라서 코드에서는 `82`로 비교했다.
Login Bypass 시리즈는 다음 글에 작성하겠다.
'Study > with normaltic' 카테고리의 다른 글
SegFault CTF - 5주차 - 번외 (0) | 2025.05.07 |
---|---|
SegFault CTF - 5주차 - 2 (0) | 2025.05.06 |
SegFault CTF - 4주차 (0) | 2025.04.24 |
로그인 유지: 쿠키 & 세션 (0) | 2025.04.23 |
PHP와 MySQL (0) | 2025.04.16 |