Skip to content

Latest commit

 

History

History
324 lines (238 loc) · 10.3 KB

0918-WPSN.md

File metadata and controls

324 lines (238 loc) · 10.3 KB

170918

CSRF

CSRF 예제

CSRF(Cross-site Request Forgery, 사이트 간 요청 위조)는 사용자가 악의적인 웹 페이지에 접속했을 때 해당 웹 페이지에서 다른 서버로 요청을 보내어 정보를 조작하는 공격 기법입니다. 전통적인 웹 개발에서 자주 일어나는 보안 사고입니다. (2008년 옥션 개인정보 유출 사건을 참고하세요.)

URL shotener 서버로 CSRF 공격을 시험해볼 수 있습니다. 로컬 서버를 켜고 상단의 Show 버튼으로 웹 페이지를 열어 요청을 보내보세요.

CSRF 공격이 가능한 이유는 웹 서버로 오는 요청이 어떤 웹 페이지에서 출발했던 간에 쿠키가 자동으로 포함되어 오기 때문입니다. (Ajax 요청은 제외) 이렇게 쿠키는 편하긴 하지만 잘못 다루었을 경우에 보안에 심각한 위협이 될 수 있습니다.

CSRF 공격을 방어하기 위해서는, 사용자가 우리 웹 페이지에 접속하지 않고는 데이터를 조작하는 요청(POST)을 보낼 수 없게 만들어야 합니다. 이를 위해 우리 웹 페이지에 접속해야만 받을 수 있는 정보(이를 CSRF 토큰이라 부릅니다)를 요청에 포함시켜 보냄으로써 CSRF 공격을 방어할 수 있습니다.

express 기반 웹 사이트에서는 csurf 미들웨어를 사용해 CSRF 공격을 방어할 수 있습니다.

CRURF

var cookieParser = require('cookie-parser')
var csrf = require('csurf')
var bodyParser = require('body-parser')
var express = require('express')
 
// setup route middlewares 
var csrfProtection = csrf({ cookie: true })
var parseForm = bodyParser.urlencoded({ extended: false })
 
// create express app 
var app = express()
 
// parse cookies 
// we need this because "cookie" is true in csrfProtection 
app.use(cookieParser())
 
app.get('/form', csrfProtection, function(req, res) {
  // pass the csrfToken to the view 
  res.render('send', { csrfToken: req.csrfToken() })
})
 
app.post('/process', parseForm, csrfProtection, function(req, res) {
  res.send('data is being processed')
})
<form action="/process" method="POST">
  <input type="hidden" name="_csrf" value="<%= csrfToken %>">
  
  Favorite color: <input type="text" name="favoriteColor">
  <button type="submit">Submit</button>
</form>

csrf는 cookie session을 사용할때 문제가 된다.

jwt를 사용할때는 csrf는 필요가 없다.


SEED

기존의 seed.js로 데이터베이스의 값을 넣는 것이아닌 knex의 seed로 값을 넣어본다.

기존 seed.js

const faker = require('faker')
const randomstring = require('randomstring')
const knex = require('./knex')

knex('user')
  .insert({
    id: 'fast',
    password: 'campus'
  })
  .then(() => {
    for(var i = 0; i < 20; i++){
      knex('url_entry')
        .insert({
          id: randomstring.generate(8),
          long_url: faker.internet.url(),
          user_id: 'fast'
        }).then(console.log)
    }
  })

seeds 생성방법

seed 명령어

// seed file을 만드는 명령어
$ knex seed:make initial_data

// seed 실행 명령어
$ knex seed:run

변경된 seeds/initial_data.js

const faker = require('faker')
const randomstring = require('randomstring')

// 값이 실행되어서 이 파일의 작업이 끝나는 것에 대해서 알려줘야한다.
exports.seed = function(knex, Promise) {
  // Deletes ALL existing entries
  return knex('user')
  .insert({
    id: 'kim',
    password: 'sejune'
  })
  .then(() => {
    const arr = []
    for(var i = 0; i < 20; i++){
      arr.push(
        knex('url_entry')
          .insert({
            id: randomstring.generate(8),
            long_url: faker.internet.url(),
            user_id: 'kim'
          })
      )
    }

    return Promise.all(arr)
    // promise는 어떤값을 감싸고 있는데 그 값을 then을 사용해서 사용할 수 있다.
    // 단 return하는 것이 그냥 값이면 then에서는 그 값을 사용할 수 있다.
    // 여기서 다음에 등장하는 then에는 arr를 사용할 수 있다.
  })
}

170918

SocketIO

초기의 HTTP는 실시간 전송이 안되었어서 초기에는 실시간처럼 보이는 Long Polling와 comet이라는 응답을 서버가 결정을 하는 기술을 사용하였다.
현재는 WebSocketIO를 표준으로 사용하고 있다.

WebSocketIO HandShake

  1. WebSocketIO는 client가 server에게 websocket이라고 요청을 보낸다.
  2. server가 지원을 해주면 연결을 한다.
  • HTTP의 요청응답을 넘어서 server, client가 서로가 필요할때 바로 정보를 요청 및 전달한다.

websocket연결을 보기위해서는 검사tab의 Frames로 확인 할 수 있다.

Socket.io

SocketIO 예제

Socket.io는 실시간 웹을 위한 JS 라이브러리입니다. 기존의 웹은 클라이언트(브라우저)가 요청을 해야만 서버로부터 데이터를 받을 수 있었던 데 반해, Socket.io와 같은 기술을 사용하면 클라이언트가 요청을 하지 않아도 필요할 때 서버로부터 데이터를 받을 수 있습니다. 이를 이용하면, 채팅이나 실시간 차트 혹은 실시간 알림을 지원하는 웹 어플리케이션을 작성할 수 있습니다.

Socket.io는 실시간 통신을 위해 주로 WebSocket이라는 웹 표준 기술을 사용하지만, 구형 웹브라우저 등 WebSocket을 지원하지 않는 환경에서는 다른 기술을 사용할 수도 있습니다. (long polling, comet 등) 다른 일반적인 웹소켓 서버와는 호환이 되지 않으므로 주의하세요.

//index.js
// DOMContentLoaded html이 다 loading되었을 때 실행되게 한다.
document.addEventListener('DOMContentLoaded', () => {
  const socket = io()
  
  socket.on('response', data => {
    console.log(`${data.message} @ ${new Date}`)
  })
  
  // emit : 우리가 맺은 socketio에다가 message라는 이름의 event를 발생시켜라 거기에 이런 데이터를 포함시켜라
  document.querySelector('#message').addEventListener('click', e => {
    socket.emit('message', {message: '간단한 메시지를 이렇게 보낼 수 있습니다.'})
  })
  
  document.querySelector('#message-and-ack').addEventListener('click', e => {
    socket.emit('messageAndAck', {message: '메시지를 보낸 후에 서버에서 응답을 받을 수도 있습니다'}, data => {
      console.log(data)
    })
  })
  
  document.querySelector('#broadcast').addEventListener('click', e => {
    socket.emit('broadcast', {message: '다른 클라이언트에게만 가는 메시지도 보낼 수 있습니다.'})
  })
})

index.js에 있는 message event가 발생하고 client에게 server.js에서 response event를 발생을 하면 index.js에서 response를 한다.

// server.js
const express = require('express')
const http = require('http') // express를 사용하려면 http를 써야한다.
const socketio = require('socket.io')

const app = express()
const server = http.Server(app)
const io = socketio(server)

app.set('view engine', 'pug')

app.use(express.static('public'))

app.get("/", (req, res) => {
  res.render('index.pug')
})

app.get('/namespace', (req, res) => {
  res.render('namespace.pug')
})

app.get('/room/:id', (req, res) => {
  res.render('room.pug', {id: req.params.id})
})

/*
# 서버 측 API

## 사전 정의된 이벤트 목록

- 'connect' 혹은 'connection': 클라이언트가 새 연결을 맺었을 때
- 'disconnect': 클라이언트의 연결이 끊어졌을 때
- 'error': 에러가 발생했을 때
- 'disconnecting': 클라이언트가 연결을 끊기 직전에

*/

io.on('connection', socket => {
  
  // Simple Message
  socket.on('message', data => {
    // 현재 네임스페이스에 접속 중인 모든 클라이언트에게 메시지 보내기
    io.emit('response', data)
  })
  
  // Acknowledgement
  socket.on('messageAndAck', (data, ack) => {
    io.emit('response', data)
    // 메시지를 보낸 클라이언트에게만 회신하기
    ack({ok: true})
  })
  
  // Broadcast
  socket.on('broadcast', data => {
    // 메시지를 보낸 클라이언트를 제외한 모든 클라이언트에게 메시지 보내기
    socket.broadcast.emit('response', data)
  })
})

// Custom Namespace 통신을 격리하기 위해서 사용한다. -> namespace.js
const someNsp = io.of('/some-namespace')

someNsp.on('connection', socket => {
  socket.on('message', data => {
    someNsp.emit('response', data)
  })
})

// Room
const roomNsp = io.of('/room')

/*
room은 동적으로 지정할 수 있는 통신의 분리 단위입니다.
하나의 소켓은 여러 개의 room에 들어갈 수 있습니다.
*/
roomNsp.on('connection', socket => {
  let id;
  socket.on('join', data => {
    // `socket.join`을 호출해서 특정 room에 진입합니다.
    socket.join(data.id)
    id = data.id
  })
  
  socket.on('message', data => {
    // `socket.to`는 이벤트 방출을 특정 room에 한정시킵니다.
    roomNsp.to(id).emit('response', data)
  })
})

const listener = server.listen(process.env.PORT, function () {
  console.log('Your app is listening on port ' + listener.address().port)
})
  • 접속한 사람마다 각자의 socket객체를 생성 해준다.

사전 정의된 이벤트 목록

SocketIo의 기본 이벤트 목록

  • 'connect' 혹은 'connection': 클라이언트가 새 연결을 맺었을 때
  • 'disconnect': 클라이언트의 연결이 끊어졌을 때
  • 'error': 에러가 발생했을 때
  • 'disconnecting': 클라이언트가 연결을 끊기 직전에

server.js에서

  • socket.emit는 자기자신에게만 보내진다.
  • io.emit는 접속한 전부에게 보내진다.
// namespace.js

document.addEventListener('DOMContentLoaded', () => {
  // 특정 namespace에 대한 연결 수립
  const socket = io('/some-namespace')
  
  socket.on('response', data => {
    console.log(`${data.message} @ ${new Date}`)
  })
  
  document.querySelector('#message').addEventListener('click', e => {
    socket.emit('message', {message: '특정한 이름공간에 접속할 수 있습니다. 통신은 다른 이름공간과 분리됩니다.'})
  })
})

namespace는 미리 만들어진 방에서 격리할 때 사용한다.
roomspace는 동적으로 변경되는 방에서 격리할 때 사용한다.