[인증/보안]

해피데빙·2022년 3월 16일
1

TIL

목록 보기
36/45

HTTPS

Http + s : 암호화를 통해 유출을 방지한다
사용하는 방법
-인증서
: 데이터를 보낸 서버가 정말 서버인지 인증 확인 용도
: 서버 도메인 관련 정보가 있어서 인증을 용이하게 한다
인증서와 함께 응답 전송, 클라는 그걸 받아서 인증서의 도메인과 응답에서 확인한 도메인이 같은지 확인
만약 해커가 중간에 인증서를 탈취해서 도메인을 없애거나 한다
-ca
: 인증서를 발급하는 공인된 기관
: 각 브라우저는 신뢰하는 ca의 정보를 가지고 있다
-> 브라우저마다 다르다, 바뀐다(자격 박탈 되기도 하니까)
-비대칭 키 암호화
: 다른 키 한쌍으로 암호화, 복호화
-> 암호화용 키와 복호화용 키는 다르다

모든 통신에 대해서 공개 키 방식을 사용하는 것은 아니다
매번 공개 키로 사용하면 매우 복잡하니까
통신의 초창기에서만 비밀키로 사용하기 위한 키를 만들어내기 위해 사용

통신 과정의 각 부분
hand shake : 서로 호가인, 서버는 공개 키 한쌍 중의 하나를 클라에 전달
클라는 전달받은 키를 이용해서 임의의 정보를 암호화해서 전달
클라 및 서버는 서로 만들고 교환한 정보 바탕으로 비밀 키 생성
각자 생성한 키를 바탕으로 클라가 테스트용 데이터를 만들어낸 비밀키로 암호화해서 전달
서버 역시 복호화를 하고 다시 암호화해서 클라에 전달
클라가 같은 내용의 데이터를 복호화하는데 성공 했으면 성공적으로 비밀키가 만들어진 상태
https 연결이 성립한 것
비밀 키를 바탕으로 데이터 송신 때 필요한 암호화 및 복호화 진행

Learn About HTTPS

HTTPS 프로토콜
HTTPS는 Hyper Text Transfer Protocol Secure Socket layer 의 약자
HTTP over SSL(TLS), HTTP over Secure

HTTPS는 HTTP 요청을 SSL 혹은 TLS라는 알고리즘을 이용해,
HTTP 통신을 하는 과정에서 내용을 암호화하여 데이터를 전송하는 방법입니다.

  • 질문: SSL, TLS는 무엇인가요? SSL과 인증기관(CA)은 어떤 관계가 있나요?
    인증에서 HTTPS 프로토콜을 사용해야만 하는 이유는
    ->HTTP보다 상대적으로 안전한 방법이고,
    ->데이터 제공자의 신원을 보장받을 수 있기 때문입니다.

데이터 제공자의 신원을 확인하고 보장받는 게 인증에서 중요한 이유는 다음과 같습니다.

클라이언트는 데이터 제공자가 제공해 준 데이터를 사용할 수밖에 없습니다.
클라이언트는 서버에 데이터 요청을 하고 이후 받은 데이터를 이용해서 화면을 렌더링하는 등의 작업을 해야 합니다.

그렇기 때문에 요청 및 응답을 중간에서 가로채는 중간자 공격에 취약합니다.
'중간자 공격'은 클라이언트와 서버 사이에서 공격자가 서로의 요청, 응답의 데이터를 탈취 및 변조하여 다시 전송하는 공격

데이터가 중간에 다른 도메인을 거쳐서 전달되기 때문에 서버가 해당 데이터는 https://example.com 도메인에서 제공되었습니다.라는 추가 데이터를 응답 객체에 실어 보낸다면 '중간자 공격'으로 인해 다른 도메인에서 데이터를 받은 클라이언트는 데이터를 제공한 도메인과 전달받은 내용의 도메인을 비교하여 '중간자 공격'이 존재하는지 아닌지 확인할 수 있습니다.

물론 중간자 공격으로 인해 이런 추가 데이터 또한 변조할 수 있습니다.
따라서 해당 데이터를 암호화시키는 작업이 필요합니다.

암호화
HTTPS 프로토콜의 특징 중 하나는 암호화된 데이터를 주고받기 때문에, 중간에 인터넷 요청이 탈취되더라도 그 내용을 알아볼 수 없습니다. 기존에 배웠던 HTTP 프로토콜은 요청 및 응답을 탈취한다면 프로그램을 이용하여 아래와 같이 해당 요청으로 전달되는 데이터의 내용을 확인할 수 있습니다.

하지만 데이터를 암호화하여 전송하는 HTTPS 프로토콜을 사용한다면 비밀번호와 같은 중요한 데이터가 유출될 가능성이 HTTP 프로토콜보다 현저히 적어지게 됩니다. 아래 사진은 위 사진에서 똑같은 요청을 프로토콜만 HTTPS로 변경했을 때의 데이터를 캡처한 사진입니다.

내용이 암호화되어 전송되기 때문에 정확한 키로 복호화하기 전까지는 어떤 내용인지 알 수 없습니다.

인증서
HTTPS 프로토콜의 또 다른 특징 중 하나는 브라우저가 응답과 함께 전달된 인증서 정보를 확인할 수 있다는 점입니다.
브라우저는 인증서에서 해당 인증서를 발급한 CA 정보를 확인하고 인증된 CA가 발급한 인증서가 아니라면 아래와 같이 화면에 경고창을 띄워 서버와 연결이 안전하지 않다는 화면을 보여줍니다.

이렇게 브라우저는 인증서의 도메인과 데이터를 제공한 제공자의 도메인을 비교할 수 있기 때문에 인증서의 도메인 정보와 데이터 제공자의 도메인 정보가 다른 '중간자 공격'을 감지하여 보안 위협으로부터 사용자 및 사용자의 데이터를 보호할 수 있습니다.

또한 이런 경고를 직접 보여줌으로써 브라우저들은 인증된 CA가 발급한 인증서를 이용하여 데이터를 제공하는 안전한 서버를 사용할 수 있게 사용자를 유도합니다.

사설 인증서 발급 및 HTTPS 서버 구현

Before You Learn
HTTP에 대한 이해

Achievement Goals
HTTPS의 개념을 이해할 수 있다.
HTTPS가 왜 인증에서 필요하고, 왜 사용해야 하는지 이해할 수 있다

Bare minimum requirement
로컬 환경(localhost)에서 인증서를 생성하고,
인증서를 이용해 HTTPS 서버를 만드세요.

Getting Started: HTTPS 사설 인증서 발급 및 서버 구현
이번 시간에는 아래 설명을 참고하여 HTTPS를 학습하고 서버를 직접 구현합니다.
별도의 테스트는 존재하지 않지만 아래 설명을 따라 직접 HTTPS 서버를 구축해야 다음 Part를 진행하실 수 있습니다.

현업에서는 HTTPS 프로토콜을 사용하는 것이 일반적이고, 특히 HTTPS 프로토콜은 인증의 중요한 부분을 차지하기 때문에, 앞으로 진행될 스프린트에서는 HTTPS 통신으로 진행합니다.

설치
mkcert라는 프로그램을 이용해서 로컬 환경(내 컴퓨터)에서 신뢰할 수 있는 인증서를 만들 수 있습니다.

Ubuntu
우분투의 경우 다음 명령어를 이용해 설치합니다.

$ sudo apt install libnss3-tools
$ wget -O mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.3/mkcert-v1.4.3-linux-amd64
$ chmod +x mkcert
$ sudo cp mkcert /usr/local/bin/
macOS
macOS 사용자의 경우, Homebrew를 통해 mkcert를 설치할 수 있습니다.

$ brew install mkcert

firefox를 사용할 경우 필요에 따라 설치해주세요.

$ brew install nss
인증서 생성
먼저 다음 명령어를 통해 로컬을 인증된 발급기관으로 추가해야 합니다.

$ mkcert -install
다음은 로컬 환경에 대한 인증서를 만들어야 합니다. localhost로 대표되는 로컬 환경에 대한 인증서를 만들려면 다음 명령어를 입력해야 합니다.

$ mkcert -key-file key.pem -cert-file cert.pem localhost 127.0.0.1 ::1
이제 옵션으로 추가한 localhost, 127.0.0.1(IPv4), ::1(IPv6)에서 사용할 수 있는 인증서가 완성되었습니다. cert.pem, key.pem 이라는 파일이 생성된 것을 확인할 수 있습니다.

여기서 발급받은 key와 cert를 앞으로 쿠키, 세션, 토큰 스프린트에서 계속 활용하게 됩니다. 저장 경로를 반드시 확인하세요.//root에 저장함

인증서는 공개키, 그리고 인증기관의 서명을 포함하고 있으므로 공개되어도 상관이 없지만, key.pem의 경우 개인 키이므로 git에 커밋하지 않고, 암호처럼 다루어야 합니다.

질문: key와 cert의 다른 점은 무엇일까요?
HTTPS 서버 작성
Node.js 환경에서 HTTPS 서버를 작성하기 위해서는 https 내장 모듈을 이용할 수 있습니다. express.js를 이용해 https 서버를 만들 수도 있습니다.

먼저는 방금 생성한 인증서 파일들을 HTTPS 서버에 적용해 주는 작업이 필요합니다.

Node.js https 모듈 이용

const https = require('https');
const fs = require('fs');

https
  .createServer(
    {
      key: fs.readFileSync(__dirname + '/key.pem', 'utf-8'),
      //key, cert를 파일에서 가져온다
      cert: fs.readFileSync(__dirname + '/cert.pem', 'utf-8'),
    },
    function (req, res) {
      res.write('Congrats! You made https server now :)');
      res.end();
    }
  )
  .listen(3001);

이제 서버를 실행한 후 https://localhost:3001로 접속하시면 브라우저의 url 창 왼쪽에 자물쇠가 잠겨있는 HTTPS 프로토콜을 이용한다는 것을 알 수 있습니다!

express.js 이용
만약 express.js 를 사용하는 경우, 다음과 같이 https.createServer의 두 번째 파라미터에 들어갈 callback 함수를 Express 미들웨어로 교체하면 그만입니다!

const https = require('https');
const fs = require('fs');
const express = require('express');

const app = express();

https
.createServer(
{
key: fs.readFileSync(dirname + '/key.pem', 'utf-8'),
cert: fs.readFileSync(
dirname + '/cert.pem', 'utf-8'),
},
app.use('/', (req, res) => {
res.send('Congrats! You made https server now :)');
})
)
.listen(3001);
Advanced
기존에 작성한 서버를 ngrok을 이용해서 HTTPS로 터널링을 시켜보세요.
ngrok이란 HTTP로 만들어진 서버를 HTTPS 프로토콜로 터널링 해주는 프로그램입니다.

hashing

암호화에 앞서서 인증의 기본인 해싱

인증을 쓰지 않는다면
클라 -정보요청-> 서버 -> db -> 서버 -응답-> 클라

ex. 이메일로 서버에 요청을 보낼 때 그냥 보내면 안되니까 비번 사용
암호화를 하지 않으면 비번 누출 가능

but 보안의 issue!
이메일은 공개가 되어도 되지만 pw는 공개되면 안된다

암호화는 일련의 정보를 임의의 방식으로 사용하여 다른 형태로 변환하여
해당 방시게 대한 정보를 소유한 사람을 제외하고 이해할 수 없도록
알고리즘을 이용해 정보를 관리하는 과정

but 이건 굉장히 단순한 알고리즘
그러므로 암호화할 때는 더 어렵게

설정 시 서버에서 암호화 알고리즘을 저장하고 암호화해서 db에 저장
이후 요청이 올 때마다 서버에서 암호화를 해서 db에 저장한 pw와 비교

해싱 원칙
1. 모든 값에 대해 해시 값을 계산하는데 오래걸리지 않아야
2. 최대한 해시 값을 피해야 하며 모든 값은 고유한 해시값을 가진다
3. 아주 작은 단위의 변경이라도 완전히 다른 해시값을 가져야 한다

대표적인 해싱 알고리즘 ex. SHA1

Salt

암호화해야 하는 값에 어떤 별도의 값을 추가해 결과를 변형하는 것

  1. 암호화만 해놓는다면 해시된 결과가 늘 동일
    그러므로 해시된 값과 원래 값ㅇ르 테이블(레인보우 테이블)로 만들어서 decoding 해버리는 경우도 생긴다

  2. 원본값에 임의로 약속도니 별도의 문자열을 추가하여 해시를 진행한다면 기존 해시값과 전혀 다른 해시값이 반환되어 알고리즘이 노출되더라도 원본값을 보호하 ㄹ수 있도록 하는 안전 장치

  3. 기존 : (암호화하려는 값) => hash 값
    salt 사용 : 암호화하려는 값 + salt용 값 => hash 값

salt 사용시 주의점

  1. salt는 유저와 패스워드 별로 유일한 값을 가져야 한다
  2. 사용자 계정을 생성할 때와 비번을 변경할 때마다 새로운 임의의 salt를 사용해서 해싱해야 한다
  3. salt는 절대 재사용하지 말아야 한다
  4. salt는 db의 유저 테이블에 같이 저장되어야 한다

Cookie

쿠키는 서버에서 클라이언트에 데이터를 저장하는 방법의 하나입니다.

그러므로 서버가 원한다면 서버는 클라이언트에서 쿠키를 이용하여 데이터를 가져올 수 있습니다.

그러므로 쿠키를 이용하는 것은 단순히 서버 -> 클라이언트에 쿠키를 전송하는 것만 의미하지 않고
클라이언트-> 서버로 쿠키를 전송하는 것도 포함됩니다.

이런 쿠키에는 다음과 같은 특징이 있습니다.

서버가 클라이언트에 데이터를 저장할 수 있습니다.
앞서 언급한 것처럼 서버는 쿠키를 이용하여 데이터를 저장하고 원할 때 이 데이터를 다시 불러와 사용할 수 있습니다.
하지만 데이터를 저장한 이후 아무 때나 데이터를 가져올 수 없습니다.

데이터를 저장한 이후 특정 조건들이 만족하는 경우에만 다시 가져올 수 있습니다.

이런 조건들은 쿠키 옵션으로 표현할 수 있습니다. 아래에서는 주로 사용되는 쿠키 옵션을 설명합니다.

1. Domain

도메인이라는 것은 여러분들이 흔하게 보실 수 있는 www.google.com과 같은 서버에 접속할 수 있는 이름입니다.
쿠키 옵션에서 도메인은 포트 및 서브 도메인 정보, 세부 경로를 포함하지 않습니다.

여기서 서브 도메인이란 www 같은 도메인 앞에 추가로 작성되는 부분을 말합니다.

따라서 요청해야 할 URL이 http://www.localhost.com:3000/users/login 이라 하면

여기에서 Domain은 localhost.com이 됩니다.

만약 쿠키 옵션에서 도메인 정보가 존재한다면 클라이언트에서는 쿠키의 도메인 옵션과 서버의 도메인이 일치해야만 쿠키를 전송할 수 있습니다.

2. Path

세부 경로는 서버가 라우팅할 때 사용하는 경로입니다.
만약 요청해야 하는 URL이 http://www.localhost.com:3000/users/login 인 경우라면 여기에서 Path, 세부 경로는
/users/login이 됩니다.

명시하지 않으면 기본으로 / 으로 설정되어 있습니다.

Path 옵션의 특징은 설정된 path를 전부 만족하는 경우
요청하는 Path가 추가로 더 존재하더라도 쿠키를 서버에 전송할 수 있습니다.
즉 Path가 /users로 설정되어 있고, 요청하는 세부 경로가 /users/login 인 경우라면 쿠키 전송이 가능합니다.

하지만 /user/login으로 전송되는 요청은 Path 옵션을 만족하지 못하기 때문에 서버로 쿠키를 전송할 수 없습니다.

3. MaxAge or Expires

쿠키가 유효한 기간을 정하는 옵션입니다.

MaxAge는 앞으로 몇 초 동안 쿠키가 유효한지 설정하는 옵션입니다.

Expires 은 MaxAge와 비슷합니다. 다만 언제까지 유효한지 Date를 지정합니다.
이때 클라이언트의 시간을 기준으로 합니다.

이후 지정된 시간, 날짜를 초과하게 되면 쿠키는 자동으로 파괴됩니다.

하지만 두 옵션이 모두 지정되지 않는 경우에는 브라우저의 탭을 닫아야만 쿠키가 제거될 수 있습니다.

4. Secure

쿠키를 전송해야 할 때 사용하는 프로토콜에 따른 쿠키 전송 여부를 결정합니다.
만약 해당 옵션이 true로 설정된 경우, 'HTTPS' 프로토콜을 이용하여 통신하는 경우에만 쿠키를 전송할 수 있습니다.

5. HttpOnly

자바스크립트에서 브라우저의 쿠키에 접근 여부를 결정합니다.
만약 해당 옵션이 true로 설정된 경우, 자바스크립트에서는 쿠키에 접근이 불가합니다.

명시되지 않는 경우 기본으로 false로 지정되어 있습니다.
만약 이 옵션이 false인 경우 자바스크립트에서 쿠키에 접근이 가능하므로 'XSS' 공격에 취약합니다.

6. SameSite

Cross-Origin 요청을 받은 경우
요청에서 사용한 메소드와 해당 옵션의 조합으로 서버의 쿠키 전송 여부를 결정하게 됩니다.
사용 가능한 옵션은 다음과 같습니다.

Lax :Cross-Origin 요청이면 'GET' 메소드에 대해서만 쿠키를 전송할 수 있습니다.

Strict : Cross-Origin이 아닌 same-site 인 경우에만 쿠키를 전송 할 수 있습니다.

None: 항상 쿠키를 보내줄 수 있습니다. 다만 쿠키 옵션 중 Secure 옵션이 필요합니다.

이때 'same-site'는 요청을 보낸 Origin과 서버의 도메인이 같은 경우를 말합니다.

이러한 옵션들을 지정한 다음 서버 -> 클라이언트로 쿠키를 처음 전송하게 된다면
헤더에 Set-Cookie라는 프로퍼티에 쿠키를 담아 쿠키를 전송하게 됩니다.

이후 클라이언트 혹은 서버에서 쿠키를 전송해야 한다면 클라이언트는
헤더에 Cookie라는 프로퍼티에 쿠키를 담아 서버에 쿠키를 전송하게 됩니다.

쿠키를 이용한 상태 유지
이러한 쿠키의 특성을 이용하여 서버는 클라이언트에 인증정보를 담은 쿠키를 전송하고, 클라이언트는 전달받은 쿠키를 요청과 같이 전송하여 Stateless 한 인터넷 연결을 Stateful 하게 유지할 수 있습니다.

하지만 기본적으로는 쿠키는 오랜 시간 동안 유지될 수 있고, 자바스크립트를 이용해서 쿠키에 접근할 수 있기 때문에 쿠키에 민감한 정보를 담는 것은 위험합니다.

이런 인증정보를 탈취하여 서버에 요청을 보낸다면 서버는 누가 요청을 보낸 건지 상관하지 않고 인증된 유저의 요청으로 취급하기 때문에, 개인 유저 정보 같은 민감한 정보에 접근이 가능합니다.

Session-based Autentication

cookie는 클라이언트에 데이터 저장

session_id는 신분증 같은 역할


cookie는 클라에서 저장, 기간을 정해주지 않으면 영원히 유지
그러므로 보안 효과가 떨어짐

세션
장점
단점 :

  • 서버의 메모리에 저장하므로 가용 메모리가 줄어들어서 서버의 성능이 떨어진다
  • 쿠키를 완전 대체한 게 아니라서 쿠키 계속 이용 (그러므로 쿠키가 가진 한계 가지고 있다)
    ex. xss공격으로 세션 쿠키 탈취 가능

같은 id로 접속을 하거나 등 서버에서 추가로 확인 가능

로그인

본격적으로 로그인을 통해 인증 정보가 저장되고, 인증된 사용자가 어떤 식으로 웹사이트를 이용하는지 간단하게 알아봅시다.

사용자가 웹사이트에서 아이디 및 비밀번호를 이용해서 로그인을 시도하면(그림 1), 과연 어떤 일이 벌어질까요?

사용자가 만일 정확한 아이디와 비밀번호를 입력했다면, 서버는 인증(Authentication)에 성공했다고 판단할 것입니다. 그렇다면, 다음번에 인증을 필요로 하는 작업(그림에서와 같이, 장바구니에 물품 추가)을 요청할 경우, 또 로그인 과정을 거쳐야 할까요? 아닙니다. 서버는 아이디 및 비밀번호의 해시를 이미 알고 있기 때문에, "인증에 성공했음"을 서버가 알고 있다면, 매번 로그인할 필요가 없을 것입니다.

인증에 따라 리소스의 접근 권한(Authorization)이 달라집니다

이때 서버와 클라이언트에 각각 필요한 것이 다음과 같습니다.

서버는 사용자가 인증에 성공했음을 알고 있어야 합니다.
클라이언트는 인증 성공을 증명할 수단을 갖고 있어야 합니다.
여기서 몇 가지 용어가 등장합니다.

사용자가 인증에 성공한 상태는 세션이라고 부릅니다.
서버는 일종의 저장소에 세션을 저장합니다. (그림 2) 주로 in-memory(자바스크립트 객체를 생각하면 됩니다), 또는 세션 스토어(redis 등과 같은 트랜잭션이 빠른 DB)에 저장합니다.
세션이 만들어지면, 각 세션을 구분할 수 있는 세션 아이디도 만들어지는데(그림 3), 보통 클라이언트에 세션 성공을 증명할 수단으로써 세션 아이디를 전달합니다. (그림 4)
이때 웹사이트에서 로그인을 유지하기 위한 수단으로 쿠키를 사용합니다. 쿠키에는 서버에서 발급한 세션 아이디를 저장합니다

쿠키를 통해 유효한 세션 아이디가 서버에 전달되고, (그림 5) 세션 스토어에 해당 세션이 존재한다면 (그림 6) 서버는 해당 요청에 접근 가능하다고 판단합니다. (그림 7,8)

하지만 쿠키에 세션 아이디 정보가 없는 경우, 서버는 해당 요청이 인증되지 않았음을 알려줍니다.

로그아웃
그렇다면, 로그아웃은 어떻게 구현해야 할까요? 세션 아이디가 담긴 쿠키는 클라이언트에 저장되어 있으며, 서버는 세션을 저장하고 있습니다. 서버는 그저 세션 아이디로만 요청을 판단합니다.

주의: 쿠키는 세션 아이디, 즉 인증 성공에 대한 증명을 갖고 있으므로, 탈취될 경우 서버는 해당 요청이 인증된 사용자의 요청이라고 판단합니다.
이것이, 우리가 공공 PC에서 로그아웃해야 하는 이유입니다.

그러므로 로그아웃은 다음 두 가지 작업을 해야 합니다.

서버의 세션 정보를 삭제해야 합니다.
클라이언트의 쿠키를 갱신해야 합니다.
서버가 클라이언트의 쿠키를 임의로 삭제할 수는 없습니다. 대신, set-cookie로 세션 아이디의 키값을 무효한 값으로 갱신해야 합니다.

express-session
이런 세션을 대신 관리해 주는 'express-session' 이라는 모듈이 존재합니다.

다만 코딩을 바로 시도하기보다 아래 설명을 참고하여 모듈의 역할 및 사용법을 공부해 보세요.

'express-session'은 세션을 위한 미들웨어로, 'Express'에서 세션을 다룰 수 있는 공간을 보다 쉽게 만들어줍니다.

또한 필요한 경우 세션 아이디를 쿠키에 저장하고, 해당 세션 아이디에 종속되는 고유한 세션 객체를 서버 메모리에 저장합니다.
이때 세션 객체는 서로 독립적인 객체이므로 각각 다른 데이터를 저장할 수 있습니다.

req.session이 바로 세션 객체이며 req.session은 세션 객체에 세션 데이터를 저장하거나 불러오기 위해 사용합니다.

이 세션 객체에 값을 담거나, 값을 불러오는 법, 세션을 파괴하는 법은 다음 문서를 참고하세요.
GitHub: express-session

Mark as Completed

profile
노션 : https://garrulous-gander-3f2.notion.site/c488d337791c4c4cb6d93cb9fcc26f17

0개의 댓글