Cookie 옵션 중 sameSite에 대해서 자세히 설명해주시고 Non, Strict, Lax일 때 각각 어떻게 동작하는지 설명해주세요
쿠키
- 쿠키는 브라우저에 데이터를 저장하기 위한 수단
- 브라우저에서 서버로 요청을 전송할 때 그 요청에 대한 응답에 Set-Cookie 헤더가 포함되어 있는 경우 브라우저는 Set-Cookie에 있는 데이터를 저장 = 쿠키 key=value로 브라우저 쿠키를 저장함
sameSite
크롬 브라우저의 sameSite 기본 속성은 "Lax"로 설정되어 있음 이는 CSRF( Cross-site-request-forgery) 사이트간 요청 위조 및 의도하지 않은 정보유출에 대한 취약성에 대처하기 위함
cross-site로 요청을 보내는 경우 쿠키의 전송제약 조건을 설정하기 위한 옵션
쿠키는 앞서 언급한 서드 파티 쿠키의 보안적 문제를 해결하기 위해 만들어진 기술입니다. 크로스 사이트(Cross-site)로 전송하는 요청의 경우 쿠키의 전송에 제한을 두도록 합니다.
Strict
- Same Site간의 요청에서만 쿠키의 전송을 허용하는 옵션, 보안에 완벽하지만 편의성이 떨어짐
- ex) www.naver.com 에 쿠키가 있는 경우 http://www에 대한 요청에 대해서만 쿠키를 전송
Lax
- 기본적으로는 Strict 이지만, 서드파티 쿠키는 전송되지 않지만, 몇가지 예외적인 요청에는 쿠키를 전송함
- 같은 웹사이트 일대는 쿠키가 당연히 전송된다는 의미, 이 외에는 top lever navigation(웹 페이지 이동)과, 안전한 http 메서드 요청의 경우 쿠키가 전송되는 옵션 (get 요청)
- 안전하지 않은 post나 delete 요청 (서버의 상태를 바꾸는 요청)의 경우 lax옵션에서 쿠키는 전송되지 않음 서드파티 쿠키에 한함
Non
- Same Site와 cross site의 요청에도 모두 전송을 허용함 보안에 취약함, https 프로토콜 하에서 secure속성과 함께사용함
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.COOKIE_SECRET,
saveUninitialized: true,
resave: false,
name: 'connect.sid',
cookie: {
secure: false, // if true: only transmit cookie over https, in prod, always activate this
httpOnly: true, // if true: prevents client side JS from reading the cookie
maxAge: 1000 * 120 * 30, // session max age in milliseconds
sameSite: 'lax',
},
}));
서드파티 쿠키
- 서드파티 쿠키란 사용자가 접속한 페이지와 다른 도메인으로 전송하는 쿠키
퍼스트 파티 쿠키
- 사용자가 접속한 페이지와 같은 도메인으로 전송되는 쿠키
helmet을 통해 구현하신 xssFilter, frameguard, noSniff가 각각 어떤 보안 위협이 있고 그걸 어떻게 방어하는지에 대해 설명해주세요.
app.use(helmet.hsts({
maxAge: ms("1 year"),
includeSubDomains: true
}));
// 1분동안 하나의 ip 주소에서 들어오는 request의 숫자를 100회로 제한
app.use(rateLimit({
windowMs: 1 * 60 * 1000,
max: 100
}));
// xss(교차 사이트 스크립팅) 공격 방어
app.use(helmet.xssFilter());
// 클릭재킹으로 부터 보호
app.use(helmet.frameguard("deny"));
// 브라우저에서 파일 형식의 임의 추측 금지
app.use(helmet.noSniff());
xssFilter
- 해킹 기법중 하나로 게시판이나 웹 메일등 자바스크립트와 같은 스크립트 코드를 삽입해 다른 사용자가 스크립트 코드를 실행 시켰을때 개발자가 고려하지 않은 기능이 작동하게 하는 공격 => 웹에 접속하는 사용자를 대상으로 한 공격
- xss 공격에 취약한 웹사이트 탐색 => xss 공격을 위한 스크립트를 포함한 url을 웹 사이트 사용자에게 노출시킴 => 사용자가 해당 url을 클릭할 경우 웹 서버에 스크립트가 포함 된 url을 통해 requse를 전송하고 웹서버는 해당스크립트를 포함한 response를 사용자에게 전달함
- 크로스 사이트 스크립팅 공격으로 부터 방어하기 위해 xss필터 활성화, 웹 애플리케이션이 응답의 컨텐츠에 포함된 잠재적인 악성 스크립트를 감지하고 제거함
- X-XSS-Protection 헤더를 0으로 설정하여 XSS(Cross Site Scripting) 공격 스크립트를 비활성화여 예방
- helmet은 src나 href, 인라인 자바스크립트 XSS 공격을 막는 용도로
xssFilter 를 설정 했을때 기능
- 유효성 검사, 긴 입력 불가
- html태그 무력화 (escape 처리)
- encodeURI 함수를 사용해 입력받은 URL이 안전한지 확인
- html 속성에 사용자가 쌍따옴표(")를 넣을 수 없는지 확인. 또한 DB에 넣기 전 검사
- HTTP헤더로 완화
- helmet은 src나 href, 인라인 자바스크립트 XSS 공격을 막는 용도
sanitize-html모듈
- html태그에 대한 악성 스크립트를 방지하는 모듈
const sanitizeHtml = require('sanitize-html');
const dirty = `스크립트는 과연
<script>some really tacky HTML</script> 무시될까?
h1태그는 <h1>링크</h1> 무시가 될까?`;
const clean = sanitizeHtml(dirty);
console.log(clean);
frameguard
- 클릭재킹은 사용자의 클릭을 가로채 다른 것을 누르게 하는 방식이다. 제한적인 X-Frame-Option을 보내서 브라우저가 더이상 이 프레임을 로드하지 않도록 해 대응할 수 있다. html의 iframe태그 허용범위를 설정하는 옵션으로 여기서 설정한 deny 옵션은 다른 모든 사이트들안에 우리 사이트를 iframe 태그로 넣지 못하도록 설정하는 옵션이다.
- deny : 프레임 내에 우리 사이트를 넣지 못하게 함
- sameorigin : 다른 사람이 프레임 내에 우리 사이트를 넣지 못하지만 우리 사이트에서는 허용
- allow-from : 지정한 사이트에서 프레임을 표시할 수 있음
noSniff
- MIME(Multipurpose Internet Mail Extensions) 유형을 임의로 추측하는 것을 방지함 브라우저에서 파일을 열 때 잠재적인 보안 취약성을 방지 할 수 있음.
- 웹 브라우저가 특정 파일을 읽을때, 파일의 실제 내용과 Content-Type에 설정된 내용이 서로 다르면 파일의 내용으로부터 파일의 형식을 추축하여 실행하는 것임 편리한 기능이지만 공격자에게 악용될 가능성이 있음 만약에 해커가 html의 파일의 내용을 png등 여러 확장자로 만들어 업로드 하였을때, 그 파일을 읽은 사용자는 MIME 스니핑에 의해 이미지 파일을 요청하더라도 실제 파일의 내용이 HTML 형식인 것을 보고 HTML 코드를 실행 할 수 있다.
결론 : 브라우저가 요청된 파일의 내용을 보고 임의로 파일 형식을 추측할수 없도록 하는설정
JWT가 무엇이고 어떻게 구성되어 있나요? JWT가 아닌 다른 방식으로 토큰을 구성할 수도 있나요? 그럼 어떻게 해야 하나요?
jwt
- json web token 은 웹표준으로 두 개체에서 jswon 객체를 사용하여 가볍고 자가 수용적인 방식으로 정보를 전달함
jwt 구성
- header : typ와 alg를 가지고 있음 typ는 토큰의 타입을 지정함 alg는 해싱 알고리즘을 나타냄 이 일고리즘은 토큰을 검증 할 때 사용되는 signature 부분에서 사용됨
- payload: 토큰에 담을 정보가 들어있음
- signature : header의 인코딩 값과 payload의 인코딩 갑을 합친후 주어진 비밀키로 해쉬를 하여 생성함
jwt가 아닌 다른 방식으로 토큰 구성
jwt가 아닌 다른 방식으로 토큰 구성 어떻게?
jwt 장단점
- 장점 : 상태를 유지 하지 않음, 서버가 유저의 세션 상태를 기억할 필요가 없기 때문에 확장성이 뛰어나고 서버 리소스를 효율적으로 사용가능 stateless 상태
- 단점 : 한번 발급된 jwt는 만료시간이 될 때까지 유효, 토큰이 탈취되면 보안에 문제가 발생할 수 있음 => 짧은 만료시간 설정하여 보안적으로 보완
jwt signature는 어떻게 검증되는지
- 시그니처는 서버에서 토큰을 검증할때 사용함, 헤더와 페이로드를 이어붙인 문자열에 대해 서버가 보유한 시크릿 키를 사용해 해시를 계산함 이 해시는 들어온 토큰의 시그니처와 비교됨, 두 값이 일치하면 토큰이 변조되지 않았음을 확인
let { accesstoken, refreshtoken } = req.headers;
try {
accesstoken = !req.headers.refreshtoken ? req.cookies.accesstoken : accesstoken;
refreshtoken = !req.headers.refreshtoken ? req.cookies.refreshtoken : refreshtoken;
const [authAccessType, authAccessToken] = (accesstoken ?? "").split(" ");
// refreshtoken이 없거나 accesstoken의 형식이 올바르지 않을 때
if ((!refreshtoken) || (authAccessType !== "Bearer" || !authAccessToken)) {
// 로컬에 사용자 정보를 null로 설정 // 가짜 사용자 객체를 만듬
res.locals.user = { userId: null };
// 다음 미들웨어로 진행
return next();
}
const isAccessTokenValidate = validateAccessToken(authAccessToken);
const isRefreshTokenValidate = validateRefreshToken(refreshtoken);
if (!isRefreshTokenValidate) {
return res.status(419).json({ errorMessage: "Refresh Token이 만료되었습니다." });
}
if (!isAccessTokenValidate) {
const accessTokenId = await tokenRepository.findTokenId(refreshtoken);
if (!accessTokenId) {
return res.json({ errorMessage: "Refresh Token의 정보가 서버에 존재하지 않습니다.", });
}
const newAccessToken = createAccessToken(accessTokenId);
console.log(newAccessToken)
res.cookie("accesstoken", `Bearer ${newAccessToken}`);
return res.status(203).json({ newAccessToken });
}
const { userId } = jwt.verify(authAccessToken, process.env.ACCESS_KEY);
const user = await Users.findOne({ where: { userId: userId } });
res.locals.user = user;
next();
} catch (error) {
console.error(error);
res.clearCookie("accesstoken");
res.clearCookie("refreshtoken");
return res.status(403).json({ errorMessage: "전달된 쿠키에서 오류가 발생하였습니다." });
}
};
소켓 서버에 트래픽이 많아져서 로드밸런싱을 해야 하는 경우 어떻게 로드밸런싱을 해야 문제 없이 구성이 가능한가요?
upstream 업스트림이름{
#ip별로 균등하게 서버분배 defalut는 round robbin
ip_hash
server ubuntu주소:3001
server ubuntu주소:3002
server ubuntu주소:3003
}
server{
listen 80
server_name 푸댕푸댕.net;
location / { #root경로로 요청시
proxy_pass http://nodejs_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
}
nginx를 사용한 로드밸런스 서버 구축
리버스 프록시 서버는 로드밸런싱에 사용함 => 트래픽이 증가하면 2가지 선택지가 있는데 하나는 scale up ( 서버의 컴퓨터의 사양을 늘리는것 ) 다른 하나는 scale out 서버의 갯수를 늘려 트래픽을 분산시키는 방법 nginx는 이중에서 scale out 방식으로 서버의 갯수를 늘려 트래픽을 분산시키는 방법이다.
hop-by-hop
- 네트워크에서 데이터 패킷이 한 노드에서 다른 노드로 전달되는 방식을 나타냄
- 패킷이 각각 중간 노드에서 처리되고 검사되며 패킷을 최종 목적지로 전달하는 동안 중간 노드에서의 동작을 제어하는 것을 의미
- 각 노드는 패킷을 받으면 특정 동작을 수행하고 다음노드로 패킷을 전달함, 라우팅, 패킷검사 ,오류 제어 ,흐름제어 등을 포함 할 수 있음
- 노드는 자신이 수행해야할 동작을 처리하고 패킷을 적절한 다음 노드로 전달하는 방식으로 작동
- ip 프로토콜 기반의 네트워크에서 사용되는 일반적인 통신 방식으로 ip패킷은 출발지에서 목적지로 전달되는 동안 여각 라우터는 독립적으로 패킷을 처리함
웹소켓과 nginx
reverse proxy서버는 웹소켓을 지원하는데 몇가지 문제가 있습니다.
1. 웹소켓은 hop-by-hop 프로토콜이기때문에 프록시 서버가 클라이언트로 부터 upgrade request를 가로챌때 백엔드 서버에 자체 업그레이드 요청을 명시 해주어야 합니다. 웹소켓 연결은 긴 시간동안 살아있기대문에 reverse proxy는 이러한 연결이 유휴상태 인것처럼 보입니다. 그러므로 소켓 연결을 닫지 않고 연결상태를 유지하도록 허용해야합니다.
- 업그레이드 요청을 보내기위해 upgrade와 connection 헤더가 반드시 명시되어야 합니다
추가적으로 Upgrade header와 Connection header를 명시해주어 수신하는 웹서버가 이 요청이 websocket요청임을 알 수 있습니다.
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
답변 1. Cookie 옵션중 SameSite에 대해
sameSite : 웹에서 쿠키를 전송할 때 동일 한 출처에 대해서만 요청을 보내도록 제한하는 옵션으로, CSRF 공격을 방지하기 위해 SameSite 옵션을 설정하여 쿠키를 동일한 출처로 제한할 수 있습니다.