왜 로그인 유지를 해야하는가?
간단한 예를 통해 왜 로그인 유지가 필요한지 설명하겠다.
로그인 페이지가 있어, 로그인을 하면 해당 유저의 점수를 출력하는 사이트가 있다. 하지만 정말로 로그인이 된 상태일까? 새로고침을 해도 페이지 내용이 변하지 않는걸 보면 로그인이 된 게 맞지 않을까?
이에 대한 답은 '아니오'다. URL을 살펴보면 id의 값을 받는 것을 확인할 수 있다. 만약 이 id값을 바꿨을 때, 다른 유저의 정보 페이지가 출력된다면 이 클라이언트는 'nciwo'라는 아이디로 로그인됐다고 보긴 어렵다.
URL의 파라미터 값만 바꿨다고 로그인을 하지 않고도 다른 유저의 정보를 열람할 수 있다.
이러면 로그인이 의미 없게 된다.
그렇기에 로그인을 한 뒤 해당 유저가 로그인한 ID에 해당함을 서버에서는 알 수 있어야 한다.
그것을 이 글에서는 로그인 유지라고 표현한다.
로그인 유지
그럼 어떤 방식으로 로그인을 유지시킬 수 있는가? 다른 페이지로 접속할 때마다 클라이언트로부터 ID와 PW를 받아, 로그인 인증을 한다면 클라이언트의 신원을 확인할 수 있다. 하지만 이걸 유지라고 하긴 어렵다. 상시 재확인이라는 말이 더 어울린다. 옛날 개발자들은 이러한 고민을 해결하기 위해 쿠키라는 개념을 도입했다. 로그인을 하면 서버가 클라이언트에게 특정한 키워드의 쿠키를 지급하고, 앞으로 클라이언트는 이 쿠키를 인증 도구로써 서버와 통신을 하는 것이다.
Figure 1에서는 로그인 과정을 개괄적으로 보여준다. 단계별로 약간의 부연설명을 붙이자면
- `POST id='nciwo', pw='1234'`
맨 처음 사진에 나왔던 로그인 페이지에서, ID와 PW를 입력하고 LOGIN 버튼을 누른 상태다. - `set-cookie: login_id='nciwo'`
해당 ID-PW에 해당하는 유저 정보를 확인한 뒤, 클라이언트에게 login_id의 값이 nciwo인 쿠키값을 전달한다. - `Cookie: login_id='nciwo', GET index.php?id=nciwo`
서버에게 login_id에 해당하는 쿠키 값으로 nciwo를 보여주며, 유저 정보 페이지를 요청한다. - `Return page about 'nciwo'`
서버는 클라이언트에게 해당하는 페이지를 제공한다.
cookie를 이용한 로그인
BurpSuite를 통해 실제로 통신이 이뤄지는 과정을 살펴보겠다.
로그인을 처리하는 login_proc.php로 `id=nciwo&pw=1234`를 `POST`를 통해 전달하는 모습을 확인할 수 있다. 이는 위에서 설명했던 첫번째 과정에 해당한다.
서버는 Response에서 `Set-Cookie`를 통해 login_id의 쿠키값을 nciwo로 지정해준다.
Location을 통해 클라이언트가 유저 페이지로 리다이렉션할 수 있도록 했기 때문에 클라이언트는 자동으로 유저 페이지로 `GET` 요청을 보내게 될 것이다.
첫번째 줄을 통해 index.php?user=nciwo로 `GET` 요청을 보내는 것을 확인할 수 있다.
그와 동시에 Cookie에 login_id=nciwo를 넣어 서버에게 전달한다.
성공적으로 로그인했다.
만약 여기서 아까처럼 URL만 약간 수정하면 어떻게 될까? 'noot'이라는 아이디가 있다고 가정하고, user 파라미터에 nciwo 대신 넣어보자.
인증에 실패했다면서 유저 정보가 뜨지 않는 것을 확인할 수 있다. 이는 파라미터로 들어온 noot과 쿠키 값인 nciwo를 일치하는지 비교한 뒤, 일치하는 경우에만 정보를 보여주도록 설계가 돼있기 때문이다.
보안이 성립된 것으로 보인다.
면 좋겠으나, 잘 생각해보면 문제점이 존재한다.
Cookie값 변조
아까 GET의 파라미터로 들어온 noot과 쿠키값인 nciwo를 비교해서 일치하면 정보를 제공한다고 했다. 여기서 문제는 보내는 쿠키값은 클라이언트가 수정할 수 있다는 점이다. 만약 쿠키값을 noot으로 변조해서 GET 요청을 다시 시도한다면 유저 noot의 정보를 가져올 수 있을지도 모른다..
일단 탈취하고자 하는 유저의 정보페이지를 URL로 입력한다.
그럼 브라우저는 서버에게 패킷을 보내려고 할 것이다. 그 패킷을 보내기 전에 변조를 시도해본다.
중요한 점은 login_id의 값을 nciwo가 아닌 noot으로 수정하는 것이다.
이제 보내보면..
로그인을 건너뛰고 다른 사용자의 정보를 가져와버렸다.
결국 쿠키는 로그인 유지를 하는 데 있어선 필요하지만 보안을 그리 강화시키지는 못한다.
이를 해결하기 위해 세션이라는 것을 사용한다.
세션
쿠키값을 id 그대로 사용하는 것 바람에 보안에 취약하다고 잠깐 생각할 수도 있다. 그렇다고 쿠키값을 랜덤하게 지정해버리면 서버 측에서는 클라이언트가 가져온 랜덤한 쿠키값이 어떤 유저에 해당하는 값인지 알 수 없다.
근본적인 원인은 인증에 사용될 값이 클라이언트에 저장돼있음에 있다. 즉, 세션은 이 근본적인 원인을 해결하기 위해 인증값을 서버에 저장하는 방식을 택한다.
Figure 2에서는 세션을 이용한 로그인 및 유저 페이지 요청 과정을 보여준다.
- `POST id='nciwo', pw='1234'`
이 부분은 바뀌지 않는다. 그저 클라이언트가 서버에게 아이디와 비밀번호를 보내는 과정이다. - `session_start -> session_id: 3289fweiaj2, uid: nciwo`
서버에서는 세션을 시작한다. 랜덤한 이름(여기선 3289fweiaj2)의 세션 파일을 하나 만들어서 그 안에 uid라는 키의 값으로 사용자의 아이디를 적어둔다. - `set-cookie: PHPSESSID=3289fweiaj2`
세션도 인증 정보를 전달할 때는 쿠키를 사용한다. 세션파일의 이름을, 즉 세션 ID를 클라이언트에게 set-cookie로 전달한다. - `GET index.php?user=nciwo, Cookie: 3289fweiaj2`
클라이언트는 방금 받은 세션 ID를 이용해 서버에게 자신의 유저 정보 페이지를 요청한다. - `Return user page`
세션 ID에 해당하는 이름의 세션 파일을 열어보고, 그 안에 있는 uid와 요청받은 URL의 파라미터 값이 일치한지 확인 후, 클라이언트에게 페이지를 반환한다.
이번에도 똑같이 id=nciwo, pw=1234로 POST 요청을 보내본다.
그럼 Response가 오는데, 이번에도 `Set-Cookie`가 있는 것을 확인할 수 있다. 하지만 이번엔 값이 약간 다르다.
PHPSESSID라는 이름을 가진 쿠키의 값으로 이상한 문자열이 들어가 있다. 저 이상한 문자열이 바로 인증에 사용될 세션 ID다.
실제로 세션 ID는 서버 안 특정 경로에 파일로 저장이 되며, 그 안에 사용자의 정보가 들어있다.
특히, 해당 파일의 이름이 `sess_[세션 ID]`인 것 또한 확인할 수 있다.
앞으로 클라이언트는 세션 ID를 쿠키로 설정해, 서버와 통신을 할 것이다. 서버는 해당 세션 ID에 해당하는 파일을 열어 `GET` 파라미터로 들어온 user의 값 nciwo가 일치하는지 확인한 뒤 응답한다.
쿠키값으로 사용자 ID가 들어갈 때와 달리, 세션 ID를 유추하는 것은 사실상 불가능하기 때문에 원하는 사용자로 위장해서 접속하는 것을 막을 수 있다. 물론, 세션 ID를 탈취당한다면 위장 접속이 가능해진다.
세션 vs 쿠키
세션은 쿠키보다 훨씬 보안적으로 안전하지만, 서버에 파일을 생성하도록 한다. 이는 서버에 무리를 줄 수도 있다. 아무 생각없이 세션을 마구잡이로 생성하는 것은 지양해야 한다.
'Study > with normaltic' 카테고리의 다른 글
SegFault CTF - 4주차 (0) | 2025.04.24 |
---|---|
PHP와 MySQL (0) | 2025.04.16 |
간단한 로그인 페이지 (1) | 2025.04.08 |
Web Server와 Web Application Server (0) | 2025.04.05 |