웹 개발시 정보보안

서해빈·2021년 4월 12일
0

web

목록 보기
2/2

예외 처리

에러로 인해 예상하지 못한 중단이 발생하는 일이 없도록 하자


쿼리 문자열 분석 위험

사용자 입력을 절대로 신뢰하지 말자.

사용자는 쿼리 값을 빼먹을 수도, 여러 번 적어 배열로 보낼 수도 있다. 모든 경우에 대처가능한 코드를 작성하자.

// 예시
var arrayWrap = require("arraywrap");
//..
app.get("/search",function(req,res){
    var search = arrayWrap(req.query.q || "");
    var terms = search[0].split("+");
    // .... 기타처리....
});
//출처: https://jeong-pro.tistory.com/68 [기본기를 쌓는 정아마추어 코딩블로그]

HTTPS 사용

HTTPS를 사용하면 모든 종류의 공격에 대해 사용자를 보호하는 데 도움을 준다.

  • 사용자에게 HTTPS 사용 적용하기

    1. "trust proxy" 설정
      (프록시 뒤에서 Express 앱을 실행할 때는, app.set()을 이용하여 애플리케이션 변수 trust proxy를 설정해줘야 한다. trust proxy가 설정되지 않아도 앱은 실행되지만, trust proxy가 구성되지 않으면 프록시의 IP 주소가 클라이언트 IP 주소로 잘못 등록된다.)
    2. 미들웨어 호출
    var enforceSSL = require("express-enforces-ssl");
    // ....
    app.enable("trust proxy"); // == app.set('trust proxy', true)
    app.use(enforceSSL());

    기본적으로 HTTPS를 통한 요청이 오면 나머지 미들웨어와 라우트를 계속 실행하고, HTTP요청으로 오면 HTTPS버전으로 redirection 한다.

  • 사용자의 HTTPS 연결 유지하기
    HTTPS로 접속했을 때 HTTP로 가지 않게 하기위해 HSTS(HTTP Strict Transport Security)라는 기능을 지원한다.
    Strict-Transport-Security : max-age=31536000 하면 약 1년간 HTTPS로 묶어둔다.

    var helmet = require("helmet");
    var ms = require("ms");
    //....
    app.use(helmet.hsts({
        maxAge: ms("1 year"),
        includeSubdomains: true
    }));

    helmet 미들웨어와 ms 모듈을 설치했다. helmet을 통해 HSTS기능을 적용하고 ms를 통해 "2 days" 같이 사람이 쓰는 문자열을 밀리초로 변환 해줬다.

  • let's encrypt
    기존에는 HTTPS를 사용하기 위해 인증기관에서 SSL을 구매해야 했지만, Mozilla, Cisco, EFF등이 모여 ISRG라는 SSL인증기관을 만들어 무료로 제공하겠다 했다. 이 곳에서 인증서를 발급받고 HTTPS를 적용하면 된다.


XSS (교차 사이트 스크립팅)

XSS(교차 사이트 스크립팅) 공격은 게시판이나 웹 메일 등에 자바 스크립트와 같은 스크립트 코드를 삽입 해 개발자가 고려하지 않은 기능이 작동하게 하는 공격이다.

예시

<!-- 자바스크립트 링크 -->
<a href="javascript:alert('XSS')">XSS</a>
<!-- 이벤트 속성 -->
<img src="#" onerror="alert('XSS')">

XSS 공격을 막는 방법은 다음과 같다.

  1. 유효성 검사, 긴 입력 불가
  2. html태그 무력화 (escape 처리)
  3. encodeURI 함수를 사용해 입력받은 URL이 안전한지 확인
  4. html 속성에 사용자가 쌍따옴표(")를 넣을 수 없는지 확인. 또한 DB에 넣기 전 검사
  5. HTTP헤더로 완화
    // X-XSS-Protection 보안헤더 작성.
    app.use(helmet.xssFilter());

CSRF (교차 사이트 요청 위조)

CSRF attack(Cross Site Request Forgery)은 웹 어플리케이션 취약점 중 하나로 인터넷 사용자(희생자)가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 만드는 공격이다.이를 예방하기 위해 매 요청마다 서버로 토큰을 이용한 인증을 해줘야 한다.

공격방식은 다음과 같다

  1. 사용자가 브라우저에 로그인해있다(유효한 쿠키 보유)
  2. 사용자가 공격용 URL이 img의 src 속성값으로 저장된 페이지에 접근
  3. 사용자의 인증정보(쿠키)를 사용한 스크립트가 실행
  4. 서버는 공격명령을 사용자의 요청으로 인식하고 실행

CSRF 공격 예시

post form등을 받을 때, 서버는 쿠키가 올바른 것을 확인하고 작업을 수행하는데, 이를 악용해 해커가 자신이 원하는 POST 요청을 수행하도록 할 수 있다. 다음의 스크립트로 자동으로 form을 제출한다고 생각해보자

<script>
var formElement = document.querySelector("form");
formElement.submit();
</script>

// 출처: https://jeong-pro.tistory.com/68 [기본기를 쌓는 정아마추어 코딩블로그]

이렇게 자기의 폼과 자동 제출을 넣은 것을 iframe으로 보이지 않게 해버린다.

CSRF 보호 방법

특정 폼에 대한 HTML을 전송할 때 숨긴 요소(csrf token)를 양식에 추가

<input name="_csrf" type="hidden" value="1dmkTnkHePmTB0d1glhm">
  1. 사용자에게 데이터 요청할 때마다(form) 임의의 csrf token을 만든다.
  2. 그 데이터를 처리할 때마다 csrf token의 유효성을 검증한다.

만약 서버에서 CSRF token을 유지하는 것이 힘들면 고려해볼 수 있는 대안이다. 구현하기 쉽고 stateless이다.
랜덤 값(csrf token)을 쿠키와 request parameter(혹은 header) 두 곳 모두에 담아 전송하고, 이 두 곳의 값이 일치하는지 항상 확인한다.

이는 웹브라우저의 Same Origin 정책으로 공격자는 쿠키 값에 접근할 수 없는 점을 이용한 것이다.

  1. 브라우저의 get 요청시 csrf token과 csrf secret을 서버로부터 받는다.
    (secret -> cookie, token -> form/request parameter/request header)
  2. 브라우저에서 post 요청시 header에 csrf token을 담고 csrf secret이 담긴 cookie를 동봉하면, 서버에서 decode한 csrf token이 쿠키의 csrf secret와 일치하는지 확인한다.
  3. 이 값이 일치할 때만 request를 수행한다.

csrf secret: Cryptographically secure CSRF tokens. only known by the server.
csrf token: a hash of the secret and a salt.

적용 예시

다음은 csurf 미들웨어를 사용한 Double-Submit Cookie Pattern의 예시이다

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 예제
<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>
// Ajax 예제
<meta name="csrf-token" content="<%= csrfToken  %>">
<form>  
  Name 
  <input type="text" name="name">
  <button type='submit'>Submit</button>
</form>
<script>
const form = document.querySelector('form');
const input = document.querySelector('input');
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
form.onsubmit = async (e) => {  
  e.preventDefault();
  const name = input.value;
  const response = await fetch('/process', {
    credentials: 'same-origin',
    headers: {
      'CSRF-Token': token,
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    method: 'POST',
    body: JSON.stringify({ name })    
  })
  const result = await response.text();  
  alert(result);
}
</script>

cookie를 사용하지 않아도 csurf를 사용할 수 있다. 이 경우 cookie가 아니라 req.session을 사용한다.

다만 서브 도메인에서 부모 도메인의 쿠키를 조작할 수 있고 일반 HTTP 연결을 통해 도메인의 쿠키가 설정(set)될 수 있으므로, 서브 도메인이 모두 보호되거나 HTTPS 연결만 수용하도록 해야 이 기술이 효과가 있다.

예를 들어 https://www.example.com의 서브도메인 https://www.example.com/submit에서 xss공격 스크립트를 이용하거나 meta 태그로 쿠키값을 조작할 수 있다.

<!-- xss공격 스크립트 -->
<script>document.cookie = “_csrf=a; Path=/submit; domain=example.com”;</script>
<!-- meta 태그 -->
<meta http-equiv="set-cookie" content="_csrf=a; Path=/submit; domain=example.com">

Express 사용 정보 숨기기

// Express가 사용되었다는 것을 숨긴다.
app.disable("x-powered-by");

클릭재킹으로부터 보호

클릭재킹은 사용자의 클릭을 가로채 다른 것을 누르게 하는 방식이다.
제한적인 X-Frame-Option을 보내서 브라우저가 더이상 이 프레임을 로드하지 않도록 해 대응할 수 있다.

X-Frame-Option

  • deny : 프레임 내에 우리 사이트를 넣지 못하게 함
  • sameorigin : 다른 사람이 프레임 내에 우리 사이트를 넣지 못하지만 우리 사이트에서는 허용
  • allow-from : 지정한 사이트에서 프레임을 표시할 수 있음
// X-Frame-Option
app.use(helmet.frameguard("deny"));

어도비 제품으로부터 사이트 지키기

플래시 플레이어와 리더 같은 어도비 제품은 교차 사이트 웹 요청을 일으킬 수 있다.

이는 루트에 crossdomain.xml이라는 파일을 추가해서 막을 수 있다.
어도비 제품은 도메인 밖의 파일을 로드할 때, 먼저 우리 도메인이 허용하는지 확인하기 위해 crossdomain.xml파일을 살펴본다.

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
    <site-control permitted-cross-domain-policies="none">
</cross-domain-policy>

위 내용을 가진 crossdomain.xml을 만들어서 루트경로에 놓으면 플래시 사용자가 우리 도메인에서 요청하지 않는 한 우리 사이트 밖에서 콘텐츠를 로드하지 못하도록 막는다.


MIME 스니핑 차단

MIME(Multipurpose Internet Mail Extensions) 타입이 없을 때, 혹은 클라이언트가 타입이 잘못 설정됐다고 판단한 어떤 다른 경우에, 브라우저들은 MIME 스니핑(sniffing)을 시도할 수도 있는데, 이는 리소스를 훑어보고 정확한 MIME 타입을 추측하는 것이다.

많은 브라우저에서 파일 형식이 자바스크립트용이 아니라면 실행하도록 허용하는데, 만약 굉장히 위험한 파일이 html과 유사하다면 이 파일을 html로 해석할 것이다.

MIME 스니핑은 X-Content-Type-Options 헤더를 nosniff라는 옵션으로 설정해 차단할 수 있다.

// Helmet을 사용한 HTTP 헤더를 설정
app.use(helmet.noSniff());

참고 및 출처

0개의 댓글