HTTP 실습해보기

샘플 코드 레포

https://github.com/innerbloo/frontend-security

실행방법

npm install
node server.js

호스트명 추가

Finder 폴더 이동 -> /private/etc/ -> hosts 파일 열기 ->
127.0.0.1 sitemap.example 마지막줄에 추가

HTTP Method

주요 메소드는 다음과 같이 5가지가 있습니다.

  • GET
  • POST
  • PUT
  • PATCH
  • DELETE

GET - 리소스 조회 메서드(Read)

전달을 희망하는 데이터가 있을 경우 쿼리스트링 을 통해 전달합니다.

GET /member/100?username=lisa

쿼리스트링 외에 메세지 바디를 사용하여 데이터를 전달 가능하지만 서버에서 따로 구성해야 하기 때문에 권장하지 않습니다.

POST또한 조회가 가능하지만 GET 메서드는 캐싱이 가능하기 때문에 GET을 쓰는 것이 유리합니다.

  • 정적 데이터 조회 : 쿼리 파라미터 없이 리소스 경로로 단순하게 조회 가능(이미지, 정적 텍스트 문서)합니다.
  • 동적 데이터 조회 : 검색, 게시판 목록에서 검색어로 이용하며 쿼리 파라미터를 통해 데이터를 전달합니다.

VScode에서 실습해보기

// routes/api.js
const express = require("express");
const router = express.Router();
router.get("/",(req,res)=>{
  res.send({message:"hello"});
});
module.exports = router;
// server.js 추가
const api = require("./routes/api");
app.use("/api",api);

console 창에서 실습해보기

const response = await fetch("http://localhost:3000/api");
await response.json();

fetch 함수는 HTTP 요청에 대한 응답을 받을 수 있게 하는 메서드입니다. 응답으로 받은 bod에 대하여 자바스크립트로 조작 가능하고 다양한 용도로 활용 가능할 수 있게 합니다.

쿼리스트링 받기 실습 - VSCode

// routes/api.js
router.get("/",(req,res)=>{
  let message = req.query.message;
  if (message === ""){
    res.statue(400);
    message = "message 값이 비었음";
  }
  res.send({message});
})
// http://localhost:3000/api/?message=hello

쿼리스트링 받기 실습 - console 창

await fetch("http://localhost:3000/api?message=hello")

POST - 전달한 데이터 처리/생성 요청 메서드(Create)

메시지 바디를 통해 서버로 요청 데이터를 전달하면 요청 데이터를 처리하여 업데이트 합니다.

신규 리소스 등록, 프로세스 처리에 사용합니다.

데이터를 GET 하는데 있어서 JSON으로 조회 데이터를 넘겨야 하는 애매한 경우에도 POST를 사용할 수 있습니다.

신규 자원 생성은 201이나 200으로 응답을 보냅니다.

POST /members
Content-Type: application/json
{
	"username":"young",
	"age":20
}

📖 form 데이터 전송

  • 회원가입, 상품주문, 데이터 변경에 이용
  • GET, POST 만 지원
  • input 태그안에 들어간 값들이 쿼리스트링으로 서버에 전송됨
  • Content-Type : application/x-www-form-urlencoded

VSCode 실습하기

// routes/api.js
router.use(express.json());
router.post("/",(req,res)=>{
  const body = req.body;
  console.log(body);
  res.end();
});
module.exports = router;

console에서 실습하기

await fetch("http://localhost:3000/api",{
  method:"POST",
  body: JSON.stringify({message:"안녕하세요."}),
  headers: {"Content-type":"application/json"}
});

네트워크 탭에서 다음과 같이 api에 대한 응답 헤더를 확인할 수 있습니다.

ℹ️ 이외 다른 Method

PUT - 리소스를 대체(수정)하는 메서드(Update)

요청 메세지에 리소스가 있으면 덮어쓰고, 없으면 새로 생성한다.
일부 데이터만 보낼 경우, 기존 데이터가 삭제되고 일부 데이터로 대체된다.

DELETE - 리소스를 제거하는 메소드

상태코드는 기본적으로 200을 사용하고 상황에 따라 204를 사용한다.

PATCH - 일부 리소스만 변경하는 메서드(Update)

patch를 지원하지 않는 서버일 경우, 대신 POST를 사용할 수 있다.

HTTP Header

  • General Header(공통 헤더)
  • Request Header(요청 헤더)
  • Response Header(응답 헤더)
  • Entity Header(엔티티 헤더)

Response Header

위치 또는 서버 자체에 대한 정보와 같이 응답에 대한 부가적인 정보를 갖는 헤더입니다.

  • Access-Control-Allow-Origin → CORS 허용 세팅
  • Allow
  • Content-Disposition
  • Location
  • Content-Security-Policy

공통 헤더

요청 및 응답의 메세지 모두에서 사용되지만 컨텐츠에는 적용되지 않는 헤더

  • Date
  • Connection
  • Cache-Control
  • pragma
  • Trailer

Request Header - 서버로 요청할 데이터의 정보가 담겨있는 헤더

요청 헤더는 HTTP 요청에서 사용되지만 메시지의 컨텐츠와 관련 없는 HTTP 헤더

fetch될 리소스나 클라이언트 자체에 대한 정보를 포함하여 서버로 보내집니다.

  • Host
  • User-Agent
  • Accept
  • Authorization
  • Origin
  • Referer

Http 엔티티 관련 헤더

컨텐츠 길이나 MIME 타입과 같이 Entity Body에 대한 자세한 정보를 담은 헤더

  • content-type
    • 해당 개체에 포함되는 미디어 타입 정보
    • Content-Type:text/html; charset-latin-1 - 해당 개체가 html 문서이고 iso-latin-1 문자 인코딩 방식으로 표현
    • 공통헤더
  • Content-Language
    • 해당 개체와 어울리는 사용자 언어(자연언어)
  • Content-Encoding
    • 데이터 압축 방식
    • Content-Encoding:gzip, deflate
    • 위의 두개 항목을 기반으로 합축 해제 가능

헤더 요청 실습

// routes/api.js
router.get("/",(req,res)=>{
  let message = req.query.message;
  res.setHeader("X-Timestamp",Date.now());
  const lang = req.headers["x-lang"];
  if (message === ""){
    res.status(400);
    if(lang ==="en"){
      message="message is empty"
    } else {
      message = "message 값이 비었음"
    }
  }
  res.send({message});
})

headers 옵션을 통해 임의로 요청 헤더를 설정할 수 있습니다.

// console 창
const res = await fetch("http://localhost:3000/api?message=",{
  headers:{"X-Lang":"en"},
});
await res.json();

HTTPS

기존 HTTP의 문제점

  1. 통신 데이터 도청 가능성 - HTTP는 통신 데이터를 암호화하는 시스템이 없어 공격자가 통신 경로의 도청이 가능하다면 사용자가 주고받은 데이터를 훔쳐볼 수 있습니다.
  2. 통신 상대의 진위 여부 확인이 어려운 점 - HTTP는 통신 대장 서버가 실제 서버인지 진위 여부를 확인하는 시스템이 없어 공격자가 요청 URL로 서버인 척 할 수 있습니다.
  3. 통신 과정에서 데이터 수정 여부가 확인이 안되는 점 - 통신 경로에서 통신 내용이 수정되지 않았는지 검증하는 구조가 없습니다.

때문에 기존의 보안 문제를 해결하고자 HTTP에 TLS 통신 프로토콜을 결합한 HTTPS 사용이 권장됩니다.

TLS

SSL의 업데이트 버전으로 기존의 SSL에 대한 소유권 변경을 위해 이름이 바뀐 것입니다. 때문에 SSL 3.0과 TLS 최초 버전은 거의 유사합니다.

ℹ️ TMI
SSL은 넷스케이프에서 개발되었으며 이후 국제 인터넷 표준 관리 기구 (IETF)로 넘어가게 됨에 따라 TLS로 이름이 변경되었습니다. 때문에 현재 사용되는 SSL은 TLS입니다.

TLS의 역할

  1. 암호화:제 3자로부터 전송되는 데이터를 숨김.
  2. 인증 : 정보를 교환하는 당사자가 요청된 당사자임을 보장
  3. 무결성: 데이터가 위조되거나 변조되지 않았는지 확인

ℹ️ SSL/TLS는 웹에서 전송되는 데이터를 암호화하고, 복호화가 거의 불가능하며, 클라이언트와 서버간의 헨드세이크를 통해 인증이 이뤄집니다. 또 데이터 무결성을 위해 디지털 서명을 하여 조작 여부를 확인할 수 있습니다.

  • 클라이언트가 서버에 랜덤한 데이터와 지원되는 암호화 방식을 서버에 전달
  • 서버에서 클라이언트가 전달한 데이터와 암호화방식에 인증서를 얹어 클라이언트에 다시 전달
  • CA 인증서 목록과 받은 인증서 대조 후 공개키로 복호화하여 검증 → 검증되면 대칭키 생성
  • 공개키로 대칭키 암호화하여 서버 전달
  • 서버에서 암호화된 대칭키 복호화
  • 임시키가 세션키로 생성됨

Mixed Content의 위험성

ℹ️ Mixed Context란?
HTTPS와 HTTP 통신을 사용하는 리소스가 혼재되어 사용되는 상태를 말합니다.

Passive Mixed Content - 이미지와 영상, 음성 파일과 같인 리소스가 Mixed Content를 발생시키는 경우. 브라우저에 실행되는 코드를 포함하지 않기 때문에 영향이 적다고 할 수 있습니다.

Active Mixed Content - 자바스크립트와 CSS 등 브라우저에서 실행되는 코드에 대한 Mixed Content 패턴. 보안 공격에 치명적입니다.

대부분의 브라우저들은 이러한 Mixed Content 패턴에 대하여 Active Mixed Content 하부 리소스 접근이 차단되어 있습니다. 때문에 HTTP로 전송되는 자바스크립트와 css는 변경이 없었더라도 차단되어 애플리케이션이 제대로 동작하지 않을 수 있기 때문에 사전에 Mixed Content가 없도록 해야 합니다.

HSTS로 HTTPS 통신 강제하기

HTTPS 등장 이전까지 HTTP 통신 외에 존재하지 않았기에, 여전히 http://로 시작하는 URL이 남아 있습니다. 웹 애플리케이션에서 이러한 HTTP통신을 멈추게 되면 해당 경로로 접근할 수 있는 URL에 접속 자체가 불가능해지기 때문에, HTTPS를 사용하는 웹 애플리케이션도 HTTP 전송을 계속 허가하는 경우가 있습니다.

하지만 HTTPS가 적용된 웹 애플리케이션을 HTTP로 통신이 가능하게 한다면, 보안을 강화한 이유가 사라집니다. 때문에 유저가 http를 사용하더라도 내부적으로 HTTPS 통신을 사용할 수 있도록 강제할 수 있습니다. 이 기능이 바로 HSTS(HTTP Strict transport security)입니다.

ℹ️ 사용법
Strict-Transport-Security: max-age=
Strict-Transport-Security: max-age=; includeSubDomains
Strict-Transport-Security: max-age=; includeSubDomains; preload

includeSubDomains를 사용하면 하위 서브 도매인에도 HSTS를 적용할 수 있으며, max-age를 통해 유효 시간을 지정할 수 있습니다.

HSTS Preload

HSTS Preload를 통해 처음 접속부터 HTTPS 통신을 사용할 수 있게 할 수 있습니다. 해당 방법은 한 번이라도 접속을 시도하여 HSTS를 유효화해야 합니다. HSTS preload 리스트는 브라우저에 내장되어 해당하는 도메인은 HTTPS로 처음부터 접속할 수 있게 합니다.

애플리케이션 간 접근 제한

인터넷에 공개된 컨텐츠는 언제 어디서 이용되는지 알 수 없습니다. 때문에 민감 정보가 포함된 데이터 설정 실수로 인해 노출 위험이 존재합니다.

발생할 수 있는 공격

보호 정책이 없는 웹 애플리케이션은 다음과 같은 위험에 노출될 수 있습니다.

클릭재킹

공격자가 웹 페이지를 조작하여 사용자가 의도하지 않은 동작을 수행하도록 속이는 공격 형태입니다. 웹 애플리케이션의 사용자 인터페이스를 가려서 숨긴 상태로 사용자가 보고 있는 것과 다른 UI 요소나 링크를 클릭하게 유도하는 공격입니다.

클릭재킹 예제

크로스 사이트 스크립팅

공격자가 상대방의 브라우저에 스크립트가 실행되도록 해 사용자의 세션을 가로채거나, 웹사이트를 변조하거나, 악의적 콘텐츠를 삽입하거나, 피싱 공격을 진행하는 것을 말합니다.

크로스 사이트 스크립팅 실습

크로스 사이트 요청 위조

사용자가 자신의 의지와 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 하는 공격을 말합니다.

크로스 사이트 위조 예제

iframe을 사용한 개인 정보 유출

iframe - 페이지 내부에 다른 페이지를 삽입할 수 있는 HTML 요소입니다.

만약 접속 제한이 없는 브라우저의 경우, 피싱 사이트에서 iframe을 이용해 로그인 정보 화면을 삽입하고, 다른 사용자의 로그인 정보를 훔쳐볼 수 있습니다.

보호 정책

출처란?

https://example.com:3000/path

http - 스키마

Example Domain - 호스트명

3000 - 포트

/path - 경로

여기서 스키마 ~ 포트까지를 출처라고 하며, 스키마, 호스트명, 포트 중 하나라도 다를 경우 다른 출처로 인식합니다.

동일 출처 정책에 의한 보호

동일출처 정책 - 브라우저에 내장된 접근방식 중 하나로, 브라우저는 웹 애플리케이션 사이에 출처라는 경계를 설정하여 서로의 접근을 제한합니다. 이러한 정책을 통해 별도의 대책을 세울 필요 없이 다른 웹 애플리케이션의 접근 제한이 가능합니다.

ℹ️ 접근 제한 예

  • 자바스크립트를 사용해 교차 출처로 요청 전송
  • 자바스크립트를 사용해 iframe 내 교차 출처 페이지에 접근
  • 교차 출처의 이미지를 불러오는 <canvas>요소의 데이터에 접근
  • web storage와 indexedDB에 저장된 교차 출처 데이터에 접근

동일 출처 정책 실습

자바스크립트를 사용한 교차 출처로 요청 전송 제한

동일 출처 정책은 fetch와 XMLHttpRequest를 사용해 교차 출처에 요청 전송을 제한합니다. 해당 접근 제한을 피하고 교차 출처에 요청을 전송하기 위해서는 CORS를 사용해야 합니다.

// 교차 출처로 인해 요청 차단됨
await fetch("http://localhost:3000/api",{
  headers:{"X-Token":"aBcDeF1234567890"}
});
// 동일 출처 정책에 의해 요청이 승인됨
await fetch("http://site.example:3000/api",{
  headers:{"X-Token":"aBcDeF1234567890"}
});

자바스크립트를 사용해 iframe의 페이지에 접근 제한

동일 출처 정책에 의해 iframe은 교차 출처일 경우 접근이 제한됩니다. 해당 접근 제한을 피하기 위해 postMessage 함수를 사용하여 데이터를 전송하는 출처를 확인 가능하게 하고 안전하게 데이터를 주고받게 할 수 있습니다.

// public/user.html
<!DOCTYPE html>
<html>
  <head>
    <title> 로그인 사용자 정보 </title>
  </head>
  <body>
    <ul id="user_info">
      <li>로그인 ID : frontend_security</li>
      <li>메일 주소 : frontend-security@mail.example</li>
      <li>주소 : 서울시 광진구 </li>
    </ul>
  </body>
</html>
// 악의적인 페이지 작성하기 public/attacker.html
<!DOCTYPE html>
<html>
  <head>
    <title>attacker.example</title>
    <script>
      function load() {
        const userInfo = frm.document.querySelector("#user_info");
        console.log(userInfo.textContnet);
      }
    </script>
  </head>
  <body>
    <div>
      <!-- 사용자를 유인할 피싱 콘텐츠 -->
    </div>
    <iframe
      name="frm"
      onload="load()"
      src="http://site.example:3000/user.html"
      width="80%"
    />
  </body>
</html>

<canvas> 요소 데이터에 접근 제한

<canvas>는 교차 출처의 이미지를 불러올 때 동일 출처 정책에 의해 접근이 제한됩니다. 해당 제한을 회피하기 위해서는 CORS로 이미지를 불러와야 합니다.

web storage와 IndexedDB에 저장된 교차 출처 데이터에 접근 제한

브라우저에 내장된 데이터 저장 기능인 web storage와 indexedDB는 동일출처 정책에 따라 교차 출처의 접근이 제한될 뿐만 아니라 새로 연 탭과 윈도우 간의 접근도 제한됩니다. 때문에 사용자가 피싱 사이트에 접속하더라도 브라우저에 저장된 데이터는 피싱 사이트에 유출되지 않습니다.

⚠️ 동일 출처 정책 예외 사항
아래의 요소들은 모두 기본적으로 동일 출처 접근 제한 정책을 따르지만 예외적인 상황이 존재합니다.

  • <script> 요소에서 자바스크립트 등을 불러오기
  • <link> 요소에서 CSS 등을 불러오기
  • <img> 요소에서 불러오는 이미지(.jpg, .png)
  • <video> 요소와 <audio> 요소에서 미디어 파일을 불러오기
<link rel="stylesheet" href="" />
<script src=""></script>
<img src="" />

출처: 🌐 악명 높은 CORS 개념 & 해결법 - 정리 끝판왕 👏 [Inpa Dev 👨‍💻:티스토리]

  • <form> 요소로 폼 전송하기
  • <iframe>요소와 <frame> 요소에서 페이지 불러오기
  • <object> 요소와 <embed>요소에서 리소스 불러오기
  • @font-face를 사용해 css에서 폰트 불러오기
    해당 사항들은 CORS와 crossorigin 속성을 사용하여 접근을 제한할 수 있습니다.

ℹ️ iframe 동일 출처 접근
기본적으로 iframe 또한 동일 출처 접근 제한 대상입니다. 하지만 태그로 화면을 보여주는 것은 교차 출처에서도 가능합니다.

다음과 같이 localhost:3000 에서 site.exampl:3000 교차 출처를 호출할 경우, 해당 iframe 자체는 화면에 표시되지만 javascript로 접근하여 유저의 정보를 console.log로 출력 시도한 코드가 접근이 제한된 것을 확인할 수 있습니다.

정리

  • HTTPS는 통신을 함호화하고 상대를 증명하는 과정을 통해 HTTP의 약점을 보완합니다.
  • HSTS를 활용해 HTTP 접근을 HTTPS 요청으로 강제할 수 있습니다.
  • 사용자의 정보 노출을 최소화 하기 위해 동일 출처 정책에 의해 교차 출처의 접근이 제한됩니다.
  • 접근 제한은 CORS 정책을 이용하여 접근 제한을 우회 혹은 제한할 수 있습니다.

참고

그림으로 쉽게 보는 HTTPS, SSL

profile
👨🏻‍💻 Front-End Developer

0개의 댓글