성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>네이버는 어떻게 로그인 처리를 할까요?</h1> <p> 마침, 질문하신 분이 계셔서 한번 알아보기로 했습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > naver 소켓 로그인 ; <a target='tab' href='http://www.sysnet.pe.kr/3/0/986'>http://www.sysnet.pe.kr/3/0/986</a> </pre> <br /> 위의 글에 보면, Naver 로그인 시에 https로 정보가 날아가기 때문에 어떤 식의 HTTP 통신이 발생하는 지 알 수 없다는 것인데요. 하지만 실제로 살펴본 결과, 네이버는 부분적으로 HTTPS 통신과 GZIP 압축을 곁들이기 때문에 패킷을 가로채는 도구로는 알 수 없는 바이너리 데이터가 나타난 것이었습니다.<br /> <br /> gzip 압축의 경우에는, IE 설정을 통해서 강제로 gzip 지원을 하지 않는다고 표시를 하면 이후의 통신에서 웹 서버 측에서도 gzip 압축을 사용하지 않기 때문에 패킷 모니터링 도구로 보는 것이 가능합니다. 이를 위해 아래와 같이 HTTP 1.1 통신 설정을 해제해 주면 됩니다.<br /> <br /> <img alt='naver_how_to_login_1.png' src='/SysWebRes/bbs/naver_how_to_login_1.png' /><br /> <br /> 물론, 이렇게 했어도 HTTPS 통신의 경우에는 패킷 모니터링 도구로는 답이 없습니다. 하지만, <a target='tab' href='http://www.fiddler2.com'>Fiddler</a>의 도움을 받으면 그런 경우에도 문제 없이 패킷 내용을 볼 수 있습니다.<br /> <br /> 참고로, 이 글에서는 기왕에 fiddler를 사용하는 것이니, HTTP 1.1 해제 옵션은 사용하지 않겠습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 이제 네이버를 IE에서 방문한 다음 '로그인' 버튼을 누르는 순간부터 발생하는 HTTP 통신을 캡쳐해 보겠습니다.<br /> <br /> <img alt='naver_how_to_login_2.png' src='/SysWebRes/bbs/naver_how_to_login_2.png' /><br /> <br /> 재미있군요. ^^ 곧바로 로그인 처리를 하는 웹 페이지로 폼 데이터를 전송하지 않고 static.nid.naver.com으로부터 keys_js.nhn 파일을 가져오고 있습니다. 내용을 한번 볼까요?<br /> <br /> <img alt='naver_how_to_login_3.png' src='/SysWebRes/bbs/naver_how_to_login_3.png' /><br /> <br /> 보시는 것처럼, keys_js.nhn 요청에 대한 응답은 gzip으로 압축되어 있어서 기본적으로 볼 수 없는데요. Fiddler 도구에서는 이를 위해 "Response is encoded and may need to be decoded before inspection. Click here to transform." 영역을 사용자가 누르는 시점에 다음과 같이 정상적으로 압축 해제된 결과를 확인할 수 있도록 해주고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ==== 요청 ==== GET http://static.nid.naver.com/enclogin/keys_js.nhn HTTP/1.1 Accept: application/javascript, */*;q=0.8 Referer: http://static.nid.naver.com/login.nhn?svc=me&url=http%3A%2F%2Fwww.naver.com Accept-Language: ko-KR User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0) Accept-Encoding: gzip, deflate Host: static.nid.naver.com Connection: Keep-Alive Cookie: npic=...[생략]...<span style='color: blue; font-weight: bold'>nid_enctp=1</span>; nid_me=0 ==== 응답 ==== HTTP/1.1 200 OK Server: nginx/0.8.54 Date: Mon, 15 Aug 2011 03:01:54 GMT Content-Type: text/html Connection: keep-alive X-Powered-By: PHP/5.1.4 Expires: -1 Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0 Pragma: no-cache Cache-Control: no-store, no-cache, must-revalidate Content-Length: 1551 <span style='color: blue; font-weight: bold'> sessionkey = 'gOS...[생략]...N0'; keyname = '1...[생략]...42'; evalue = '5DF...[생략]...8D0DC5413'; nvalue = '010001';</span> $('encnm').value = '1...[생략]...42'; if (!sessionkey || !keyname || !evalue || !nvalue) { //if (true) { if ($('enctp').value == 3) { showErrorDiv2(4); } else { showErrorDiv(4); } $('frmNIDLogin').action="https://nid.naver.com/nidlogin.relogin"; } else { <span style='color: blue; font-weight: bold'>if ($('enctp').value == 1) {</span> var rsa = new RSAKey(); rsa.setPublic(evalue, nvalue); $('encpw').value = rsa.encrypt(getLenChar(sessionkey) + sessionkey + getLenChar($('uid').value) + $('uid').value + getLenChar($('upw').value) + $('upw').value); $('upw').value = ""; $('uid').value = ""; $('frmNIDLogin').submit(); <span style='color: blue; font-weight: bold'>} else if ($('enctp').value == 2) {</span> keySetting('gO...[생략]...001'); loginClick(); <span style='color: blue; font-weight: bold'>} else if ($('enctp').value == 3) {</span> document.CKKeyPro.E2EInitEx("naver", sessionkey, evalue,nvalue, $('id').value); $('encpw').value = document.CKKeyPro.GetEncData("","frmNIDLogin", "pw"); $('id').value = ""; $('pw').value = ""; $('frmNIDLogin').submit(); } } </pre> <br /> 보시는 것처럼, RSA 암호화에 필요한 값들을 네이버는 '로그인' 버튼이 눌리는 시점에 서버로부터 가져와서 처리를 하고 있습니다. 그리고 나서 enctp 값에 따라 if 문 처리를 하고 있는데, GET 요청에 보면 "nid_enctp=1" 값으로 미뤄볼 때 결국 다음과 같은 if 문이 실행되는 것을 짐작할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > var rsa = <span style='color: blue; font-weight: bold'>new RSAKey();</span> <span style='color: blue; font-weight: bold'>rsa.setPublic(evalue, nvalue);</span> $('encpw').value = <span style='color: blue; font-weight: bold'>rsa.encrypt</span>(getLenChar(sessionkey) + <span style='color: blue; font-weight: bold'>sessionkey</span> + getLenChar($('uid').value) + <span style='color: blue; font-weight: bold'>$('uid').value</span> + getLenChar($('upw').value) + <span style='color: blue; font-weight: bold'>$('upw').value</span>); $('upw').value = ""; $('uid').value = ""; $('frmNIDLogin').submit(); </pre> <br /> 보시면, 원래의 HTTP Form 필드에서 upw/uid 값은 빈 문자열("")로 초기화 하고 모든 값을 encpw 필드에 rsa.encrypt로 암호화 해서 넣고 있습니다. 아하~~~ 매우 바람직한 방법으로 보입니다. 패킷을 가로채는 악의적인 사용자로 하여금 암호뿐만 아니라 아이디 조차도 알 수 없게 만들어 버린 것입니다.<br /> <br /> 이렇게 해서 암호화된 encpw 값이 전송되는 부분은 Fiddler의 "/nidlogin.login"에서 찾아볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ==== 요청 ==== POST <span style='color: blue; font-weight: bold'>https://nid.naver.com/nidlogin.login</span> HTTP/1.1 Accept: text/html, application/xhtml+xml, */* Referer: http://static.nid.naver.com/login.nhn?svc=me&url=http%3A%2F%2Fwww.naver.com Accept-Language: ko-KR User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0) Content-Type: application/x-www-form-urlencoded Accept-Encoding: gzip, deflate Host: nid.naver.com Content-Length: 365 Connection: Keep-Alive Cache-Control: no-cache Cookie: npic=...[생략]...nid_enctp=1; nid_me=0 enctp=1&<span style='color: blue; font-weight: bold'>encpw=...[생략]...</span>&encnm=100005842&svctype=0&url=http%3A%2F%2Fwww.naver.com&enc_url=http%253A%252F%252Fwww.naver.com&postDataKey=&saveID=0&nvme=0&smart_level=1&id=&pw= ==== 응답 ==== <span style='color: blue; font-weight: bold'>HTTP/1.1 302 Found</span> Date: Mon, 15 Aug 2011 03:01:55 GMT Server: Apache Set-Cookie: nid_enctp=1; expires=Sun, 13-Nov-2011 03:01:55 GMT; path=/; domain=.nid.naver.com; Set-Cookie: nid_inf=...[생략]...; path=/; domain=.naver.com; Set-Cookie: nid_pwa=expired; expires=Wed, 02-Jun-1999 00:00:00 GMT; path=/; domain=.naver.com; Set-Cookie: adsession=expired; expires=Wed, 02-Jun-1999 00:00:00 GMT; path=/; domain=.naver.com; Set-Cookie: NID_DH_UI=...[생략]...; path=/; domain=.naver.com; Set-Cookie: NID_AUT=...[생략]...=; path=/; domain=.naver.com; Set-Cookie: NID_MATCH_M=...[생략]...; path=/; domain=.naver.com; Set-Cookie: nid_me=...[생략]...; expires=Sun, 11-May-2014 03:01:55 GMT; path=/; domain=.nid.naver.com; Set-Cookie: NID_SES=...[생략]...; path=/; domain=.naver.com; Set-Cookie: nid_sid=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/; domain=.naver.com; <span style='color: blue; font-weight: bold'>Location: http://static.nid.naver.com/sso/cross-domain.nhn?sid=...[생략]...</span> Content-Length: 27 Connection: close Content-Type: text/html <html><body></body></html> </pre> <br /> 보시는 것처럼, 폼 데이터를 POST 방식으로 "<a target='tab' href='https://nid.naver.com/nidlogin.login'>https://nid.naver.com/nidlogin.login</a>" 페이지로 전송하고 있는데, 개인적으로 여기서 약간 이해가 안되는 부분이 있습니다. 어차피 HTTPS 통신을 사용할 거면 굳이 폼 데이터를 RSA 암호화 할 필요는 없었는데,,, 혹시 MITM(Man-in-the-middle Attack) 공격에 대한 대비책인지는 모르겠습니다.<br /> <br /> 어쨌든, 결과를 살펴보면 전형적인 쿠키 인증 절차로 보입니다. keys_js.nhn 자바 스크립트 파일에서 처리된 encpw 필드 값이 nidlogin.login으로 전달되고, 그 웹 페이지에서는 (아마도) RSA 개인키를 통해 encpw 필드 값을 복호화해서 사용자가 입력한 계정/암호 값을 기반으로 인증을 할 것입니다. 성공하면, 위의 응답에서 본 것처럼 인증과 관련된 각종 쿠키값을 보내줄 텐데 이후의 HTTP 통신은 이렇게 설정된 쿠키값들을 기반으로 이뤄질 것입니다.<br /> <br /> 나머지는 그다지 중요한 것 같진 않지만 마저 살펴보면, 위의 응답이 "HTTP/1.1 302 Found"이므로 "Location" 헤더로 설정된 "<a target='tab' href='http://static.nid.naver.com/sso/cross-domain.nhn?sid=...'>http://static.nid.naver.com/sso/cross-domain.nhn?sid=...</a>[생략]..." 페이지로 웹 브라우저는 재방문을 해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ==== 요청 ==== GET <span style='color: blue; font-weight: bold'>http://static.nid.naver.com/sso/cross-domain.nhn?sid=...[생략]...</span> HTTP/1.1 Accept: text/html, application/xhtml+xml, */* Connection: Keep-Alive Accept-Language: ko-KR User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0) Pragma: no-cache Accept-Encoding: gzip, deflate Cookie: npic=...[생략]...nid_enctp=1; nid_me=0 Cache-Control: no-cache Host: static.nid.naver.com ==== 응답 ==== <span style='color: blue; font-weight: bold'>HTTP/1.1 302</span> Server: nginx/0.8.54 Date: Mon, 15 Aug 2011 04:13:35 GMT Content-Type: text/html Transfer-Encoding: chunked Connection: keep-alive X-Powered-By: PHP/5.1.4 <span style='color: blue; font-weight: bold'>Location: http://mstatic.nid.blog.me/sso/cross-domain.nhn?domain_idx=0&sid=...[생략]...</span> Cache-Control: no-store, no-cache, must-revalidate 0 </pre> <br /> 경로에 sso라는 이름이 포함된 것으로 보아, Single-sign-on을 위해 또 다른 웹 페이지를 방문하고 있는 것 같습니다. 위의 응답도 역시 302 redirection이므로 연쇄적으로 또 다른 SSO 경로를 방문하는데, 위의 결과 이외에도 네이버는 다음과 같이 2개의 웹 페이지를 더 방문합니다.<br /> <br /> <ul> <li>http://mstatic.nid.me2day.net/sso/cross-domain.nhn?domain_idx=1&sid=...[생략]...</li> <li>http://static.nid.naver.com/login/sso/finalize.nhn?url=http%3A%2F%2Fwww.naver.com&sid=...[생략]...</li> </ul> <br /> 정리해 보면, 네이버는 로그인 한번에 인증과 함께 4개의 SSO를 함께 처리하고 있습니다. 그리고 최종적으로 마지막의 finalize.nhn까지 와서야 비로소 www.naver.com으로 이동하도록 302 코드를 반환하는 것으로 끝이 납니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 살펴보니, 제법 네이버의 로그인 체계가 마음에 듭니다. 혹시나, 자신의 웹 사이트에 로그인을 구현해야 하는 분들이 있다면 위의 과정을 모방해서 구현하는 것도 좋은 선택이 될 수 있겠습니다. ^^<br /> <br /> 이것으로, 대강의 분석이 끝났으니 다시 처음 질문했던 상황으로 돌아가 볼까요? 문제는 Socket 프로그래밍으로 이것을 어떻게 해야 하느냐 입니다. 음... 간단한 문제가 아니군요. ^^ 시간 관계상 제가 직접 구현할 수는 없지만 대략 다음과 같은 순으로 진행해야 합니다.<br /> <br /> <ol> <li>사용자로부터, 로그인 아이디/암호를 입력받는다.</li> <li>http://static.nid.naver.com/enclogin/keys_js.nhn에 요청해서 암호화 관련 키들을 담고 있는 js 파일을 받아서 파싱한 후 sessionkey, evalue, nvalue 값을 얻는다.</li> <li>자바 스크립트로 되어 있는 RSA 코드를 C/C++로 마이그레이션하고, rsa.encrypt 메서드와 동일한 동작으로 암호화해서 encpw 값을 구함.</li> <li>ssl 소켓 통신을 이용하여 https://nid.naver.com/nidlogin.login으로 각종 폼 데이터를 전송하고 반환받은 쿠키값들을 보관</li> </ol> <br /> 제 역할은 이것으로 끝난 것 같습니다. ^^ 혹시나 위의 구현을 하시는 분이 있다면 블로그를 통해 공개하시는 것도 좋을 것 같습니다. ^^<br /> <br /> (참고로, 저더러 네이버 로그인 처리를 구현하라고 한다면? 차라리 웹 브라우저 컨트롤을 하나 내장해서 로그인 완료과정까지 마친 다음 최종 쿠키값만을 별도로 얻도록 구성할 것 같습니다. 만약, 제가 시간이 많은 상황에 놓여 있다면 위에서 설명한 데로 구현을 바꿀 것이고, 더욱 많은 상황에 놓인다면 ssl 프로토콜 문서에 따라 직접 구현할 것입니다. ^^)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
4013
(왼쪽의 숫자를 입력해야 합니다.)