docs(keycloak-session-store): take the sentences out of the diagram canvases
The skill says drawings carry names and sentences go in <desc> and the paragraph beside the figure. I put sentences in node details and edge labels instead, and 27 of the 28 diagrams shipped with prose inside the canvas — "예측 다섯 개가 틀렸다", "아홉 번 조용히 실패했다", "막혀서 닿지 않는다". Only label and details render on the canvas; description does not. So every sentence moved to a noun phrase and the meaning stays in description, which was already carrying it. 막혀서 닿지 않는다 -> 차단 아홉 번 조용히 실패했다 -> 조용한 실패 9건 예측 다섯 개가 틀렸다 -> 틀린 예측 5건 로그아웃이 정리하지 않는다 -> 로그아웃 미정리 볼륨이 없으면 여기까지다 -> 볼륨 없음 Three node labels were sentences too and became names: 세션 스냅샷, 예측 선기록, 대조군 확보. What stays is what the rules protect — identifiers, commands and measured values: PRIMARY KEY (client_registration_id, principal_name), ValidationFailedException: 1 changesets check sum, nginx -t && nginx -s reload, SET LOCAL synchronous_commit TO OFF. Those are names of things, not prose about them. 294 canvas strings across 28 diagrams, none matching a sentence ending, average 11 characters. All 28 still lint clean and re-rendered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
75bed382c8
commit
95c0e680b5
+27
-23
@@ -20,62 +20,66 @@ assets:
|
||||
|
||||
# BFF에서 Browser Token을 제거하고 Session과 CSRF를 처리한 방식
|
||||
|
||||
BFF 구조에서는 브라우저가 access token이나 refresh token을 받지 않는다. 로그인 이후 브라우저는 `AP3_SESSION`으로 BFF를 호출하고, access token과 refresh token은 BFF의 authorized client에 저장된다.
|
||||
BFF 구조에서는 브라우저가 access token도 refresh token도 받지 않는다. confidential client인 BFF 서버가 code를 교환해 두 토큰을 server-side authorized client에 넣어 두고, 로그인이 끝난 브라우저는 HttpOnly가 붙은 AP3_SESSION 쿠키 하나로 BFF를 부른다.
|
||||
|
||||
상태를 변경하는 요청도 session cookie를 사용하게 되면서 CSRF 검증이 추가됐다. 이때 브라우저에는 `XSRF-TOKEN`도 함께 사용된다. 현재 구현에서는 여기까지 확인했고, 재시작이나 여러 replica에서 session을 공유하는 부분은 아직 구현하지 않았다.
|
||||
토큰이 빠진 자리를 세션 쿠키가 채우면서 브라우저는 상태를 바꾸는 요청에도 쿠키를 자동으로 붙이게 됐고, 그래서 그 요청이 사용자가 보던 화면에서 시작한 것인지 가려낼 CSRF 검증이 새로 필요해졌다. 이때 쓰는 XSRF-TOKEN 값은 스크립트가 읽어 헤더에 다시 넣어야 해서 이 쿠키에는 HttpOnly를 붙이지 않았다. 지금 구현에서 확인한 것은 여기까지이고, 재시작한 뒤 로그인을 유지하는 것과 여러 replica가 세션을 함께 쓰는 것은 아직 구현하지 않았다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **BFF 인증 구조 설계 기준**
|
||||
BFF 구조에서 필요한 항목 중 현재 구현된 부분과 아직 구현하지 않은 부분을 확인한다.
|
||||
그 기준이 함께 설계하라고 적은 항목 가운데 지금 구현에 들어가 있는 것은 CSRF 검증 하나이고, 나머지는 아직 없다.
|
||||
- **OAuth Token과 Application Session을 구분하는 기준**
|
||||
`AP3_SESSION`, `XSRF-TOKEN`, server-side access token과 refresh token이 각각 다른 위치에서 사용된다.
|
||||
AP3_SESSION은 브라우저와 BFF 사이에서, access token은 BFF와 Resource Server 사이에서 쓰이고, XSRF-TOKEN은 상태를 바꾸는 요청에서만 대조된다.
|
||||
- **OAuth/OIDC 인증 패턴 선택 기준**
|
||||
브라우저에 OAuth token을 전달하지 않는 대신 BFF가 session과 token을 관리하고, 상태 변경 요청에는 CSRF 검증이 필요하다.
|
||||
이 구조를 고르면 브라우저에서 OAuth token이 사라지는 대신 BFF가 세션과 토큰을 든 보안 구성요소가 되고, 상태 변경 요청마다 CSRF 검증이 붙는다.
|
||||
- **BFF가 OAuth Token을 관리하는 조건**
|
||||
access token과 refresh token을 BFF가 보관하고 Resource Server 호출도 BFF가 수행하는 구조를 실제로 확인한다.
|
||||
그 결정이 적은 대로 BFF가 code를 교환해 두 토큰을 보관하고 Resource Server도 BFF가 부르는지 이 구성에서 확인했다.
|
||||
- **서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가**
|
||||
현재 session과 authorized client가 모두 process-local memory에 있다는 점에서 시작한다.
|
||||
지금 세션과 authorized client는 한 프로세스의 메모리에 있어서, 인스턴스를 늘리면 그 상태를 어디에 둘지부터 정해야 한다.
|
||||
- **BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가**
|
||||
HttpSession과 authorized client가 서로 다른 방식으로 조회되고 저장된다.
|
||||
HttpSession은 session ID로 찾고 authorized client는 client registration 이름과 principal name으로 찾아서, 두 상태를 꼭 같은 저장소에 둬야 하는 것은 아니다.
|
||||
|
||||
## 문제
|
||||
|
||||
BFF에서는 confidential client인 BFF 서버가 code를 교환하고 access token과 refresh token을 server-side authorized client에 저장한다. 브라우저에는 HttpOnly AP3_SESSION이 전달된다.
|
||||
브라우저에 OAuth token을 두지 않으려면 code 교환과 token 보관을 서버가 맡아야 한다. BFF 구조에서는 confidential client인 BFF 서버가 code를 교환하고, access token과 refresh token을 server-side authorized client에 넣는다. 브라우저가 받는 것은 HttpOnly가 붙은 AP3_SESSION 쿠키다.
|
||||
|
||||
브라우저는 이후 요청마다 이 session cookie를 BFF로 보낸다. 상태를 변경하는 요청에서도 cookie는 자동으로 전송되기 때문에 session cookie만 확인해서는 해당 요청이 원래 페이지에서 보낸 요청인지 구분할 수 없다. 그래서 상태 변경 요청에는 CSRF 검증이 추가된다.
|
||||
토큰이 사라진 자리를 이 세션 쿠키가 대신하면서 브라우저는 이후 요청마다 쿠키를 자동으로 붙인다. 상태를 바꾸는 요청에서도 마찬가지라, 세션 쿠키만 봐서는 그 요청이 사용자가 보던 화면에서 시작한 것인지 알 수 없다. 그래서 상태 변경 요청에는 CSRF 검증이 새로 붙는다.
|
||||
|
||||
현재 session과 authorized client는 memory에 저장되어 있다. BFF를 재시작하거나 요청이 다른 replica로 이동하는 경우까지 처리하려면 이 상태를 어디에 저장할지도 따로 정해야 한다.
|
||||
서버가 들고 있게 된 상태에도 둘 곳이 필요하다. 지금 session과 authorized client는 모두 memory에 있어서, BFF를 재시작하거나 요청이 다른 replica로 가는 경우까지 처리하려면 이 상태를 어디에 저장할지 따로 정해야 한다.
|
||||
|
||||
브라우저에서 OAuth token을 제거한 뒤 실제로 브라우저에 무엇이 남고 BFF에서 추가로 처리해야 하는 부분이 무엇인지 확인했다.
|
||||
그래서 브라우저에서 OAuth token을 없앤 뒤 브라우저에 무엇이 남는지, BFF가 무엇을 더 맡게 되는지 확인했다.
|
||||
|
||||
## 결론
|
||||
|
||||
브라우저에서는 두 개의 cookie를 사용한다.
|
||||
브라우저에 남는 자격 증명은 cookie 2개다.
|
||||
|
||||
AP3_SESSION : HttpOnly, JavaScript 읽기 x
|
||||
AP3_SESSION : HttpOnly, JavaScript 읽기 x
|
||||
XSRF-TOKEN : JavaScript 읽기 o
|
||||
|
||||
AP3_SESSION은 브라우저가 요청을 보낼 때 자동으로 포함된다. 상태 변경 요청에서는 XSRF-TOKEN의 값을 X-XSRF-TOKEN 헤더에도 넣고 BFF가 이를 확인한다. JavaScript에서 값을 읽어 헤더에 넣어야 하기 때문에 XSRF-TOKEN은 HttpOnly가 아니다.
|
||||
AP3_SESSION은 브라우저가 BFF로 요청을 보낼 때마다 자동으로 실린다. 상태를 바꾸는 요청에서는 XSRF-TOKEN의 값을 X-XSRF-TOKEN 헤더에도 넣어야 하고 BFF가 그 둘을 대조한다. 스크립트가 쿠키 값을 읽어 헤더에 넣어야 하기 때문에 XSRF-TOKEN에는 HttpOnly를 붙이지 않았다.
|
||||
|
||||
XSS가 없어지는 것은 아니다. same-origin의 악성 script는 AP3_SESSION을 직접 읽을 수는 없지만, 브라우저가 session cookie를 붙인 상태로 BFF를 호출하게 할 수 있다. XSRF-TOKEN은 JavaScript에서 읽을 수도 있다.
|
||||
토큰이 브라우저에서 사라졌다고 XSS까지 없어지지는 않는다. 같은 출처에서 실행되는 악성 script는 AP3_SESSION을 직접 읽지 못하지만 브라우저가 session cookie를 붙인 채 BFF를 부르게 만들 수 있고, XSRF-TOKEN은 아예 읽을 수 있다. 이 구조가 줄이는 것은 브라우저로 새어 나간 access token과 refresh token 원문을 다른 client에서 쓰거나 Resource Server로 바로 보내는 재사용이다.
|
||||
|
||||
이 구조에서 브라우저에 전달되지 않는 것은 access token과 refresh token 원문이다. 그래서 브라우저에서 유출된 OAuth token을 다른 client에서 사용하거나 Resource Server에 직접 보내는 형태의 재사용은 줄어든다.
|
||||
브라우저에서 덜어낸 일 가운데 지금 BFF에 들어가 있는 것은 CSRF 검증 하나다. 나머지 일곱은 아직 없다.
|
||||
|
||||
현재 추가로 구현된 부분은 CSRF 검증이다.
|
||||
|
||||
재시작 뒤 로그인 유지, replica 공유 session, 저장 token 암호화, logout, downstream 오류 변환, timeout, 경로별 인가 : x
|
||||
재시작 뒤 로그인 유지 : x
|
||||
replica 공유 session : x
|
||||
저장 token 암호화 : x
|
||||
logout : x
|
||||
downstream 오류 변환 : x
|
||||
timeout : x
|
||||
경로별 인가 : x
|
||||
|
||||
## 검증 환경
|
||||
|
||||
Keycloak 26.7.0
|
||||
|
||||
realms
|
||||
confidential, client_secret_basic
|
||||
client : confidential, client_secret_basic
|
||||
PKCE S256 : o
|
||||
provider : authorization-code, refresh-token
|
||||
|
||||
store : memory o
|
||||
store : memory
|
||||
CSRF : o
|
||||
HTTP : o
|
||||
|
||||
@@ -110,7 +114,7 @@ HTTP : o
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
이 기록은 브라우저에서 OAuth 토큰을 없앤 구조에서 브라우저에 무엇이 남고 서버가 무엇을 더 맡게 되는지 확인한 것이다. BFF(Backend For Frontend)는 화면에 필요한 API를 브라우저 대신 호출하고 결과만 돌려주는 서버이고, 여기서는 로그인과 토큰 보관까지 이 서버가 맡는다. 먼저 브라우저에 남는 값부터 보고, 그 값으로 시작한 요청이 보호 자원 서버(Resource Server)로 가는 Bearer 요청이 되기까지를 따라간 다음, 상태를 바꾸는 요청에서 새로 필요해진 검사와 아직 구현하지 않은 항목을 적는다.
|
||||
BFF(Backend For Frontend)는 화면에 필요한 API를 브라우저 대신 호출하고 결과만 돌려주는 서버이고, 여기서는 로그인과 토큰 보관까지 이 서버가 맡는다. 브라우저에서 OAuth 토큰을 걷어내면 브라우저에는 무엇이 남고 서버는 무엇을 더 맡게 되는지 확인했다. 먼저 브라우저에 남는 값부터 보고, 그 값으로 시작한 요청이 보호 자원 서버(Resource Server)로 가는 Bearer 요청이 되기까지를 따라간 다음, 상태를 바꾸는 요청에서 새로 필요해진 검사와 아직 구현하지 않은 항목을 적는다.
|
||||
|
||||
## 브라우저에는 쿠키 2개만 남는다
|
||||
|
||||
|
||||
+30
-36
@@ -18,44 +18,38 @@ assets:
|
||||
|
||||
# Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유
|
||||
|
||||
X-Auth-Request-User는 인증을 마친 edge가 만들 수도 있고 공격자가 직접 적어 보낼 수도 있다.
|
||||
upstream이 받는 요청에서는 두 경우의 모양이 같다.
|
||||
그래서 header overwrite, backend direct path 차단, internal credential 검증을 서로 독립된 세 곳에 둔다.
|
||||
앞단에 세운 프록시가 로그인을 대신 받으면 upstream은 OAuth를 몰라도 되고, 대신 요청에 붙어 온 X-Auth-Request-User 하나로 사용자를 판단한다. 이 헤더는 인증을 마친 프록시가 붙일 수도 있고 브라우저가 직접 적어 보낼 수도 있는데, 두 값은 upstream이 받은 요청에서 이름도 형식도 같아 서로 구분되지 않는다.
|
||||
|
||||
그래서 이 구성에서는 헤더를 믿을 조건을 세 곳에 나눠 두었다. 밖에서 들어오는 길을 Nginx 8088 하나로 줄이고, Nginx가 클라이언트의 동명 헤더를 자기 값으로 덮어쓰고, upstream이 사용자 헤더와 함께 내부 토큰까지 대조한다. 세 곳은 각각 다른 구간을 맡아서 하나가 나머지 둘을 대신하지 못한다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **Forward-Auth에서 Identity Header를 신뢰하기 위한 조건**
|
||||
이 기준의 다섯 조건이 실제로 어떻게 구성되는지 코드와 설정으로 확인한 자리다.
|
||||
그 기준이 세운 다섯 조건을 Nginx 설정과 upstream 코드에서 하나씩 찾아 어디에 들어가 있는지 확인했다.
|
||||
- **OAuth Token과 Application Session을 구분하는 기준**
|
||||
proxy session cookie와 identity 헤더를 JWT와 구분해야 하는 실례다.
|
||||
프록시 세션 쿠키 AP4_SESSION과 identity 헤더가 각각 다른 구간에서 쓰이고, upstream은 JWT를 입력으로 받지 않는다.
|
||||
- **OAuth/OIDC 인증 패턴 선택 기준**
|
||||
OAuth를 모르는 upstream 앞의 공통 관문을 얻고 network·헤더 신뢰 계약을 내주는 경우다.
|
||||
이 구조를 고르면 OAuth를 모르는 upstream 앞에 공통 관문이 생기는 대신, 네트워크 경로와 헤더를 어디까지 믿을지를 따로 정해 두어야 한다.
|
||||
- **Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가**
|
||||
edge가 user와 email만 전달한다는 사실이 이 질문의 출발점이다.
|
||||
지금 edge는 사용자와 이메일만 넘기고 role은 넘기지 않는데, 그러면 인가를 어디서 판단할지가 남는다.
|
||||
|
||||
## 문제
|
||||
|
||||
앞단 proxy가 로그인을 맡으면 upstream은 OAuth를 몰라도 된다.
|
||||
앞단 프록시가 로그인을 맡으면 upstream은 OAuth를 몰라도 되고, 대신 요청에 붙어 온 X-Auth-Request-User 하나로 사용자를 판단하게 된다. 그러려면 upstream은 이 헤더를 인증을 마친 edge가 붙였는지 브라우저가 직접 적어 넣었는지 가릴 수 있어야 한다.
|
||||
|
||||
대신 upstream은 X-Auth-Request-User 하나로 사용자를 판단하게 된다.
|
||||
이 헤더는 인증을 마친 edge가 만들 수도 있고 공격자가 요청에 직접 적어 보낼 수도 있다.
|
||||
upstream이 받는 요청에서는 둘이 구분되지 않는다는 점이 문제가 된다.
|
||||
가릴 방법이 없다. 두 값은 upstream이 받은 요청에서 이름도 형식도 같고, 어느 쪽이 붙였는지 적힌 자리가 없기 때문이다.
|
||||
|
||||
backend port가 외부에 열려 있거나 Nginx가 브라우저의 헤더를 그대로 넘기면
|
||||
공격자가 인증된 사용자처럼 보낼 수 있다.
|
||||
|
||||
그래서 이 구조의 문제는 upstream이 X-Auth-Request-User의 출처를 구분할 수 없다는 점이다.
|
||||
그래서 backend port가 외부에 열려 있거나 Nginx가 브라우저의 동명 헤더를 그대로 넘기면, 공격자가 원하는 이름을 적어 인증된 사용자처럼 요청을 보낼 수 있다.
|
||||
|
||||
## 결론
|
||||
|
||||
헤더를 믿으려면 서로 독립된 세 곳에서 막아야 한다.
|
||||
헤더를 인증 근거로 쓰려면 서로 독립된 세 곳에서 막아야 한다.
|
||||
|
||||
host port 닫힘 : 외부에서 upstream·proxy로 바로 가는 경로를 막는다
|
||||
host port 닫힘 : 외부에서 upstream과 프록시로 바로 가는 경로를 막는다
|
||||
Nginx header 덮어쓰기 : client가 보낸 동명 헤더를 merge하지 않고 덮어쓴다
|
||||
upstream internal token : edge를 거치지 않은 내부 요청을 막는다
|
||||
|
||||
network isolation만으로는 내부 workload나 잘못된 proxy 헤더가 신뢰되는 문제를 막지 못한다.
|
||||
controller의 공유 token만으로는 외부 직접 접근이 어려워지는 network 속성을 대신할 수 없다.
|
||||
세 곳은 각각 다른 구간을 맡는다. 네트워크를 나눠 두는 것만으로는 내부 workload가 보낸 요청이나 Nginx가 잘못 넘긴 헤더가 신뢰되는 것까지 막지는 못한다. 컨트롤러가 들고 있는 공유 토큰만으로는 외부에서 upstream으로 바로 가는 경로가 막혀 있다는 네트워크 쪽 성질을 대신할 수 없다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
@@ -71,7 +65,7 @@ app 8081, oauth2-proxy 4180 : Compose network에 expose만, host publish x
|
||||
Nginx
|
||||
auth_request /oauth2/auth
|
||||
location = /oauth2/auth : internal
|
||||
auth_request_set으로 user, email, Set-Cookie 복사
|
||||
auth_request_set : user, email, Set-Cookie 복사
|
||||
client 제공 동명 헤더 : 덮어쓰기
|
||||
trusted proxy : 단일 IP
|
||||
|
||||
@@ -83,46 +77,46 @@ SecurityConfig의 /edge/** : permitAll
|
||||
AP4_SESSION
|
||||
HttpOnly : true
|
||||
SameSite : Lax
|
||||
Secure : false in local HTTP fixture
|
||||
expire : 1 hour in proxy configuration
|
||||
Secure : false, local HTTP fixture 기준
|
||||
expire : 1 hour, 프록시 설정값
|
||||
session-cookie-minimal : true
|
||||
|
||||
server-side session store : x
|
||||
automatic discovery : x
|
||||
login, token, JWKS, userinfo URL을 각각 관리.
|
||||
login, token, JWKS, userinfo URL : 각각 따로 관리
|
||||
|
||||
HTTP : o
|
||||
|
||||
## 재현 조건
|
||||
|
||||
1. cookie 없이 GET /를 부르면 /oauth2/start로 302가 되는지 확인.
|
||||
1. cookie 없이 GET /를 부르면 /oauth2/start로 302가 되는지 확인한다.
|
||||
|
||||
2. cookie 없이 GET /api/edge를 부르면 Location 없는 401이 되는지 확인.
|
||||
2. cookie 없이 GET /api/edge를 부르면 Location 없는 401이 되는지 확인한다.
|
||||
|
||||
3. authorization request에 client_id=edge-proxy와 code_challenge_method=S256이 있는지 확인.
|
||||
3. authorization request에 client_id=edge-proxy와 code_challenge_method=S256이 있는지 확인한다.
|
||||
|
||||
4. 로그인 뒤 cookie가 AP4_SESSION이며 HttpOnly와 SameSite=Lax인지 확인.
|
||||
브라우저 요청 목록에 Keycloak token endpoint가 없어야 함.
|
||||
Web Storage가 비어 있고 document.cookie로 session cookie를 읽을 수 없어야 함.
|
||||
4. 로그인 뒤 cookie가 AP4_SESSION이며 HttpOnly와 SameSite=Lax인지 확인한다.
|
||||
브라우저 요청 목록에 Keycloak token endpoint가 없어야 한다.
|
||||
Web Storage가 비어 있어야 하고 document.cookie로 session cookie를 읽을 수 없어야 한다.
|
||||
|
||||
5. 정상 session에 다음 헤더를 얹어 GET /api/edge를 보냄.
|
||||
5. 정상 session에 다음 헤더를 얹어 GET /api/edge를 보낸다.
|
||||
X-Auth-Request-User : spoofed-admin
|
||||
X-Auth-Request-Email : spoofed-admin@example.test
|
||||
X-Internal-Auth-Token : attacker-controlled-token
|
||||
|
||||
응답은 200이고 user는 spoofed-admin이 아니라 실제 authenticated user여야 함.
|
||||
응답은 200이고, 응답의 user는 spoofed-admin이 아니라 실제로 인증된 사용자여야 한다.
|
||||
|
||||
6. 외부에서 GET /oauth2/auth를 부르면 404인지 확인.
|
||||
6. 외부에서 GET /oauth2/auth를 부르면 404인지 확인한다.
|
||||
|
||||
7. host의 4180과 8081에 접근할 수 없는지 확인.
|
||||
7. host의 4180과 8081에 접근할 수 없는지 확인한다.
|
||||
|
||||
8. 내부에서 /edge/me를 부를 때 user 헤더만 있거나 internal token이 없거나 틀리면 401이고,
|
||||
둘 다 맞으면 200인지 확인.
|
||||
둘 다 맞으면 200인지 확인한다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
이 기록은 앞단 프록시가 로그인을 맡는 구성에서 upstream이 받은 사용자 헤더를 어디까지 믿을 수 있는지 확인한 것이다. forward-auth는 실제 요청을 upstream으로 넘기기 전에 별도의 인증 엔드포인트에 허용 여부를 묻는 방식이고, Nginx에서는 `auth_request` 디렉티브가 그 질문을 subrequest로 만든다. 이 두 낱말만 알면 따라올 수 있고, 나머지 용어는 쓰는 자리에서 푼다. 먼저 위조 요청의 모양부터 보고, 그것을 막는 세 곳을 하나씩 따라간 뒤, 지금 확인한 범위와 확인하지 않은 범위를 나눠 적는다.
|
||||
forward-auth는 실제 요청을 upstream으로 넘기기 전에 별도의 인증 엔드포인트에 허용 여부를 묻는 방식이고, Nginx에서는 `auth_request` 디렉티브가 그 질문을 subrequest로 만든다. 이 두 낱말만 알면 따라올 수 있고, 나머지 용어는 쓰는 자리에서 푼다. 먼저 위조 요청의 모양부터 보고, 그것을 막는 세 곳을 하나씩 따라간 뒤, 지금 확인한 범위와 확인하지 않은 범위를 나눠 적는다.
|
||||
|
||||
## 같은 이름의 헤더가 두 곳에서 만들어진다
|
||||
|
||||
@@ -131,7 +125,7 @@ X-Internal-Auth-Token : attacker-controlled-token
|
||||
|
||||
앞단 프록시가 로그인을 맡으면 upstream은 OAuth를 몰라도 된다. 로그인과 세션 검증은 앞단에 세운 oauth2-proxy가 맡는데, 이렇게 로그인을 대신 받는 관문을 edge라고 부른다. upstream은 요청에 붙어 온 `X-Auth-Request-User` 하나로 사용자를 판단한다. 이 헤더는 oauth2-proxy가 확인한 로그인 사용자의 이름을 담아 edge가 upstream 요청에 붙이는 값이다.
|
||||
|
||||
문제는 같은 이름의 헤더를 브라우저도 직접 적어 보낼 수 있다는 점이다. upstream이 받는 요청에서 두 값은 이름도 형식도 같고, 어느 쪽이 붙였는지 적힌 자리가 없다. 그래서 이 구조의 문제는 upstream이 `X-Auth-Request-User`의 출처를 구분할 수 없다는 점이다. 백엔드 포트가 외부에 열려 있거나 Nginx가 브라우저의 헤더를 그대로 넘기면 공격자가 인증된 사용자처럼 보낼 수 있다.
|
||||
같은 이름의 헤더는 브라우저도 직접 적어 보낼 수 있다. upstream이 받는 요청에서 두 값은 이름도 형식도 같고, 어느 쪽이 붙였는지 적힌 자리가 없다. 그래서 upstream은 `X-Auth-Request-User`가 edge에서 온 값인지 브라우저가 적어 넣은 값인지 가리지 못한다. 백엔드 포트가 외부에 열려 있거나 Nginx가 브라우저의 헤더를 그대로 넘기면 공격자가 인증된 사용자처럼 보낼 수 있다.
|
||||
|
||||
### 상태 코드가 아니라 응답의 user로 판정한다
|
||||
|
||||
|
||||
Reference in New Issue
Block a user