본문 바로가기

Normaltic 취업반/인증 및 인가 취약점

CTF 풀이 - Authentication Bypass

Get Admin

loginUser라는 쿠키를 admin으로 바꿔치기하자 바로 Flag값이 나와버린다.

취약점

  • 쿠키 값을 신뢰해선 안 된다는 것을 보여주는 전형적인 케이스 문제다.
  • 클라이언트의 쿠키값을 기반으로 인증을 검증해서는 안 된다.
  • 반드시 세션ID나 해시된 토큰을 기반으로 인증을 하여야 한다.

 

Admin is Mine

주어진 계정으로 로그인을 시도하면 다음과 같이 GET 방식으로 전송이 된다.

그럼 우선 admin에다가 아무 비밀번호나 넣고 보내보자.

이 응답을 보면, "result":"fail" 이라고 되어있다.

우선, 일어난 현상 그대로를 얘기해보자면 이렇게 말할 수 있다.

  • 로그인 정보에 admin:{비밀번호 아무거나}를 넣고 보냈다.
  • ‘GET /4/loginProc.php?userId=admin&userPw={비밀번호 아무거나}’ 요청에 대한 응답 중 ‘{"result":"fail"}’이라는 요청이 있다.

그럼 일단 {"result":"fail"}을 {"result":"ok"}로 바꿔보자.

Request가 아닌 Reponse를 조작해보자.

Burp Suite에서는 Request뿐만 아니라 Reponse도 Intercept할 수 있다.

우선 Intercept를 건 후, index.php에 접속 시도를 하고 그 요청 신호에 Response Intercept를 건다.

요청 Intercept → 마우스 우클릭 → Do Intercept → Response to this request

결과는?

근데 안 된다. 그런데, 다시 요청을 보면,

Burp의 히스토리를 보면, index.php를 거치고 나서 login.php로 돌아왔다.

그럼 index.php의 요청이 있었고, 여기에서 뭔가 login.php로 가라는 지시가 있었던 것이다.

Response 메시지를 잘 봐보자.

우선, 일어난 현상 그대로를 얘기해보자면 이렇게 말할 수 있다.

  • index.php로 접속을 시도했다.
  • 그런데 login.php로 가라는 location JS코드가 있다.
  • 그래서 login.php로 돌아왔다.
  • ⇒ 이 말인 즉, index.php에 login.php로 가게하는 location JS코드에 의해 login.php로 왔다는거다.

Response 메시지를 잘 봐보자.

저 빨간색 동그라미 친 부분을 지워버리자. 저 부분에서 login.php로 location되는 것이다.

<script>
	location.href='login.php';
</script>

이 부분인 것 같다. 로그인을 하지 않으면 즉, GET메소드로 id admin과 admin의 패스워드를 입력치 않으면 저렇게 login.php로 location을 보내는 것 같다.

그럼 이 문제를 풀기위해 다시 Reponse를 조작해보자.

이번에는 바로 index.php로 넘어가는 요청을 intercept하고 거기에 대한 Response를 intercept한다.

저 빨간색 동그라미 친 부분을 지워버리자. 저 부분에서 login.php로 location되는 것이다.

지우고 응답을 그대로 보내고 페이지를 보면

취약점

  • 클라이언트를 일단 신뢰하는 것이 문제다.
  • 요청과 응답을 클라이언트가 Burp를 통해 다 캡쳐 및 조작하여 로그인이 안 되는 정보를 입력했을 때, 즉 올바르지 않은 admin의 password를 입력했을 때 로그인을 막는 장치를 클라이언트가 없애버릴 수 있다.
  • 로그인을 하려면 클라이언트가 아닌 서버가 발급하는 세션ID나 토큰 등의 정보를 기반으로 인증하여야 한다.

 

PIN CODE Bypass

문제 페이지에 들어가보니 다음의 내용이 나온다.

저거 인증코드가 몇자리를 요구하는지도 지금까지 나온 정보들로는 알 수 없으니 일단 History를 보자.

step1.php,step2.php로 넘어왔다. 그럼 혹시step3.php로 가야되는 것 아닐까? 해서 해봤다.

발사를 원하시면 아래 버튼을 클릭해주세요.”라는 문구의 화면이 나온다.

취약점

  • step3.php로 접근할 때, 현재 사용자의 세션이 관리자의 세션이 맞는지 검증하는 로직이 전무하다.
  • step2.php에서 올바른 인증코드를 입력했을 때에 관리자에 해당하는 세션ID을 발급하고 그 세션ID를 검증하는 로직을 추가해야한다.

Pin Code Crack

여기 GET 메소드 파라미터의 otpNum기반으로 인증하는 것 같다.

Intruder로 보내보자.

 1018을 입력해서 풀어냈다.

그런데, 사실 전화번호에 힌트가 있었다. 010-1414-1018이라고 하였으니, 끝자리인 1018가 정답이었다.

주요 정보를 웹에 그냥 노출하면 안 된다는 것이 보안적 교훈이라면 교훈일 것이다.

Payload Script

import requests

FAIL_STRING = "Login Fail"

cookies = {
    "PHPSESSID": "0b93047a27cff1eb6ea9e388cd268b24",
    "session": "e8a815c5-0ff3-47ae-854c-abef391dcea9.X957i0t58uUpCShDNMZFJCSpcc0"
}

headers = {
    "User-Agent": "Mozilla/5.0",
    "Referer": "http://ctf.segfaulthub.com:1129/6/login.php"
}

for i in range(0, 10000):  # 0000~9999
    otp = f"{i:04d}"       # 항상 4자리 유지

    url = f"http://ctf.segfaulthub.com:1129/6/checkOTP.php?otpNum={otp}"
    r = requests.get(url, cookies=cookies, headers=headers)

    print(f"Try : {otp}")

    if FAIL_STRING not in r.text:
        print(f"[+] Found OTP: {otp}")
        break

 

 

Login Bypass 5

일단 주어진 계정으로 로그인부터 해보자.

Burp로 히스토리를 보자.

Cookie 값에 doldol이라는 값이 있다.

혹시 저 Cookie값을 사이트가 신뢰하고 저거로 인증을 해버리는게 아닌가 싶어 저 값을 admin으로 바꿔치기 해봤다.

위에서 먼저 풀이한 Get Admin문제와 똑같은 원리다.

  • 쿠키 값을 신뢰해선 안 된다는 것을 보여주는 전형적인 케이스 문제
  • 클라이언트의 쿠키값을 기반으로 인증을 검증해서는 안 됨
  • 반드시 세션ID나 해시된 토큰을 기반으로 인증 필요

 

Session 탈취

한 줄 요약

그냥 세션이라는 것의 개념을 알고있는지, 그리고 세션을 설정할 때 개발자의 입장에서 어떻게 설계해야 되는지에 대해 묻는 문제다.

./session_info.php 페이지 들어가보자.

이 세션 ID를 로그인 페이지의 PHPSESSID에 넣어 보자.

취약점

  • 사용자가 로그인을 했을 때, PHP 세션 아이디가 발급된다. 그러나, 이 세션에 만료기한을 지정을 하지 않았다.
  • 만료기간을 지정했으면 세션을 탈취했다 하더라고 이렇게 오랜 시간 동안 계속 유효하지 않는다.
  • 세션의 만료기간을 적절히 지정하여 탈취되더라도 최소한의 안전성을 확보 하여야 한다.

 

Stupid Stupid

옆에 응답에 location: index.php 라고 되어있다. 이게 승인이 안 된다면, login.php로 location이 되는게 맞다.

그럼 저 응답을 브라우저 세션으로 열어보자.

응답에서 우클릭 → Request in browser → in original session

취약점

  • POST 메시지의 Data에서 UserId가 그냥 admin이여버리면 그 값을 신뢰해 버린다.
  • Data에서 Password 값을 전혀 검증하지 않고 있다.
  • DB에 있는 Userid값과 password값을 비교해야 한다. 이때, password값은 해시된 상태로 저장되어야 하고, 서버 내부 로직에서 사용자로부터 입력받은 password값 또한 해시하여 그 값을 DB에 있는 password 해시 값과 비교해야 한다.

 

끝.