ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SISS/웹해킹 스터디] 2학기 2, 3주차 스터디
    24-2 SISS/웹해킹 2024. 9. 14. 19:30

    2학기 2, 3주차 스터디
    수강 인증 캡쳐 화면

    2주차 09/09 ~ 09/15 [드림핵] Cookie & Session

     

    쿠키


    • 클라이언트의 IP 주소, User-Agent는 매번 변경될 수 있음
    • HTTP 프로토콜은 Connectionless, Stateless함
      • Connectionless → 한 요청 당 하나의 응답을 한 후 연결을 종료
      • Stateless → 통신 종료 후 상태 정보를 저장하지 않음

    → 웹 서버는 클라이언트를 기억할 수 없으므로 쿠키를 이용

     

    • 쿠키
      • 클라이언트의 정보(인증 정보 포함)와 요청의 내용을 구체화하는 데이터
      • Key + Value
      • 이용
        • (클라이언트) 서버에 요청 전송 시 쿠키를 함께 전송
        • (서버) 쿠키를 통해 클라이언트 구분
      • 용도
        • 정보 기록 → 클라이언트 정보 저장 (팝업 다시 보지 않기 등)
        • 상태 정보 → 로그인 상태 및 이용자 구별
    • 쿠키 변조
      • 변조된 쿠키를 검증 없이 사용할 경우 악의적인 정보 탈취 가능

     

    세션


    • 인증 정보를 변조하지 못하도록 함 (인증 상태는 쿠키에 저장)
      • 이용자가 데이터를 저장하는 쿠키와 달리 서버에 데이터를 저장
    • 인증 정보를 서버에 저장
    • 세션 ID(정보 데이터에 접근할 수 있는 키)를 클라이언트에 전달

     

    쿠키 및 세션 실습


    • 쿠키

    쿠키

    • 세션 → 이름 변경이 불가 (키 값으로 접근하므로)

    세션

     

    •  실습
      • 세션 키 확인하기 (Session Storage)
      • 세션 키 입력하기

    세션 키 확인하기 (Session Storage)
    세션 키 입력하기

     

    쿠키 적용법


    • 쿠키 설정
      • 서버 → 헤더(HTTP 응답)에 쿠키 설정 헤더를 추가하면 클라이언트의 브라우저가 쿠키를 설정
        HTTP/1.1 200 OK
        Server: Apache/2.4.29 (Ubuntu)
        Set-Cookie: name=test;
        Set-Cookie: age=30; Expires=Fri, 30 Sep 2022 14:54:50 GMT;
        ...
        
      • 클라이언트 → 자바스크립트를 사용해 쿠키 설정
        document.cookie = "name=test;"
        document.cookie = "age=30; Expires=Fri, 30 Sep 2022 14:54:50 GMT;"
        

     

    • 쿠키 열람
      • 크롬 콘솔 활용
        • 페이지 우클릭 후 [Console] 탭을 누름
        • document.cookie를 입력
      • 크롬 애플리케이션 활용
        • 페이지 우클릭 후 검사 버튼 누르기
        • [Application] 탭에서 [Cookies]을 통해 쿠키 정보 확인

     

    드림핵 세션을 통한 연습


    • 로그인 응답 확인
      • (로그인 페이지) 우클릭 후 검사 선택
      • [Network] 탭의 Preserve log를 체크 (로그인 성공 시 응답을 볼 수 있음) → 응답의 set-cookie 헤더를 통해 브라우저의 쿠키에 세션 정보를 저장함을 확인
    • 설정된 쿠키 확인
      • 크롬 검사의 [Application] > [Cookies]의 주소(https://dreamhack.io)를 누를 경우 설정된 쿠키 확인 가능
    • 저장된 세션 값 삭제
      • (sessionid 헤더 값 메모장에 복사하기) sessionid 헤더 값을 메모장에 복사한 후 새로고침하면 로그아웃
    • 세션 값 재설정
      • 쿠키의 빈 칸에 sessionid 헤더 및 복사한 세션 값 입력 시 로그인
    • 세션 하이재킹
      • 이용자의 쿠키 탈취를 통해 세션에 해당하는 이용자의 인증 상태를 훔치는 것

     

    퀴즈 1


    퀴즈 1

     

    실습 - 쿠키


    • 웹 서비스 분석
      • 엔드 포인트 → URL (API가 서버에서 자원에 접근할 수 있도록 하는)
    • @app.route(’/’) → 인덱스 페이지
      • 요청에 포함된 쿠키를 통해 이용자 식별 (문제에서는 username이 admin일 경우 플래그를 출력)
      # 인덱스 페이지 코드
      @app.route('/') # / 페이지 라우팅 
      def index():
          username = request.cookies.get('username', None) # 이용자가 전송한 쿠키의 username 입력값을 가져옴
          if username: # username 입력값이 존재하는 경우
              return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not admin"}') # "admin"인 경우 FLAG 출력, 아닌 경우 "you are not admin" 출력
          return render_template('index.html')
      

     

    • @app.route('/login’) → 로그인 페이지
      • GET → username과 password 입력 페이지 제공
      • POST → 입력된 username, password를 users 변수의 값과 비교
      # 로그인 페이지 코드
      @app.route('/login', methods=['GET', 'POST']) # login 페이지 라우팅, GET/POST 메소드로 접근 가능
      def login():
          if request.method == 'GET': # GET 메소드로 요청 시
              return render_template('login.html') # login.html 페이지 출력
          elif request.method == 'POST': # POST 메소드로 요청 시
              username = request.form.get('username') # 이용자가 전송한 username 입력값을 가져옴
              password = request.form.get('password') # 이용자가 전송한 password 입력값을 가져옴
              try:
                  pw = users[username] # users 변수에서 이용자가 전송한 username이 존재하는지 확인
              except: 
                  return '<script>alert("not found user");history.go(-1);</script>' # 존재하지 않는 username인 경우 경고 출력
              if pw == password: # password 체크
                  resp = make_response(redirect(url_for('index')) ) # index 페이지로 이동하는 응답 생성
                  resp.set_cookie('username', username) # username 쿠키 설정
                  return resp 
              return '<script>alert("wrong password");history.go(-1);</script>' # password가 동일하지 않은 경우
      
      # users 변수 선언
      try:
          FLAG = open('./flag.txt', 'r').read() # flag.txt 파일로부터 FLAG 데이터를 가져옴.
      except:
          FLAG = '[**FLAG**]'
      users = {
          'guest': 'guest',
          'admin': FLAG # FLAG 데이터를 패스워드로 선언
      }
      

     

    • 취약점 분석
      • username 변수가 쿠키에 의해 결정(요청에 포함) → 이용자의 임의 조작이 가능
        # index 페이지 코드
        @app.route('/') # / 페이지 라우팅 
        def index():
            username = request.cookies.get('username', None) # 이용자가 전송한 쿠키의 username 입력값을 가져옴
            if username: # username 입력값이 존재하는 경우
                return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not admin"}') # "admin"인 경우 FLAG 출력, 아닌 경우 "you are not admin" 출력
            return render_template('index.html')
        

     

    • 풀이
      • username을 admin으로 변경(Application > Cookies > username - Value) → 웹 브라우저의 개발자 도구를 사용 (Application > Cookies > username - Value)
      • 페이지 소스 (파이썬 플라스크 프레임워크)
        • 존재하는 사용자는 guest, admin
      • 로그인
        1. 서버 생성 후 아래에 나오는 링크를 클릭
        2. URL 뒤에 /login 을 입력하여 로그인 페이지로 이동
        3. 개발자 도구 > Elements 에서 guest 계정의 비밀번호 확인 (guest/guest)
      • 쿠키 변경 (guest로 로그인 되어있는 상태)
        • 개발자 도구 > Application > value - username 을 변경 → 플래그 확인

    페이지 소스
    로그인
    쿠키 변경

     

    실습 - 쿠키와 세션


    • @app.route('/') → 인덱스 페이지
      • 세션을 통해 이용자를 식별 (쿠키의 sessionid를 통해 session_storage에서 username을 조회)
      • username이 admin일 경우 플래그를 출력
      # index 페이지 코드
      @app.route('/') # / 페이지 라우팅 
      def index():
          session_id = request.cookies.get('sessionid', None) # 쿠키에서 sessionid 조회
          try:
              username = session_storage[session_id] # session_storage에서 해당 sessionid를 통해 username 조회
          except KeyError:
              return render_template('index.html')
      
          return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not admin"}')
      

     

    • @app.route('/admin') → 관리자 페이지
      • 무작위 값 생성을 통해(os.urandom(32).hex()) session_storage에 admin의 세션 정보를 생성
      # admin 페이지 코드
      @app.route('/admin')
      def admin():
          # developer's note: review below commented code and uncomment it (TODO)
      
          #session_id = request.cookies.get('sessionid', None)
          #username = session_storage[session_id] session_storage에 저장된 username을 불러옴
          #if username != 'admin': # username이 admin인지 확인
          #    return render_template('index.html')
            
          return session_storage
      
      # admin 세션 생성
      if __name__ == '__main__':
          import os
          # create admin sessionid and save it to our storage
          # and also you cannot reveal admin's sesseionid by brute forcing!!! haha
          session_storage[os.urandom(32).hex()] = 'admin' # username이 admin인 Session ID를 무작위로 생성
          print(session_storage)
          app.run(host='0.0.0.0', port=8000)
      

     

    • 취약점 분석
      • session_storage가 인증을 거치지 않으므로 조회 가능 (주석 처리 되었으므로)

     

    • 풀이
      • /admin 페이지를 통해 이용자 별 세션 정보를 조회
      • sessionid를 admin의 것으로 생성
      • 페이지 소스 (파이썬 플라스크 프레임워크)
        • 존재하는 계정 정보 확인
      • sessionid 확인 및 변경
        • /admin 페이지에 접속해 admin의 sessionid 복사하기
        • guest로 로그인해 생성되는 쿠키에서 sessionid변경 → 플래그 획득

    페이지 소스 - 존재하는 계정 정보 확인
    sessionid 확인 및 변경

     

    동일 출처 정책 (Same Origin Policy)


    • 악의적인 페이지에서 데이터를 읽을 수 없도록
      • 쿠키(인증 정보)를 브라우저 내부에 보관하여 HTTP 요청에 포함시킴 (자동 로그인 등의 서비스에 이용됨)
    • 오리진(Orgin / 정보 출처) 구분 방법
      • 오리진 → 프로토콜(Protocal, Scheme) + 포트(Port) + 호스트(Host)로 구성
        • 동일한 오리진이려면? → 세 개가 모두 일치해야함
          URL 결과 이유
          https://same-origin.com/frame.html Same Origin Path만 다름
          **http://**same-origin.com/frame.html Cross Origin Scheme이 다름
          https://**cross.**same-origin.com/frame.html Cross Origin Host가 다름
          https://same-origin.com:1234/ Cross Origin Port가 다름
        • SOP 실습 (자바스크립트를 이용해 SOP 테스트)
          • window.open → 새 창
          • object.location.href → 객체가 가리키는 URL 읽기
          # same origin
          sameNewWindow = window.open('<https://dreamhack.io/lecture>');
          console.log(sameNewWindow.location.href);
          // 결과: <https://dreamhack.io/lecture>
          
          # cross origin
          crossNewWindow = window.open('<https://theori.io>');
          console.log(crossNewWindow.location.href);
          // 결과: Origin 오류 발생
          
          • Cross Origin 데이터 읽기/쓰기 → 읽기는 불가하지만 쓰기는 가능
            # 가능한 코드
            crossNewWindow = window.open('<https://theori.io>');
            crossNewWindow.location.href = "<https://dreamhack.io>";
            

     

    SOP 실습


    • 입력

    같은 입력
    다른 scheme (https → http)
    다른 호스트 (dreamhack → dreamhackhack)
    다른 포트 (https 기본 443 → 1234)

     

    • Same Origin Policy 작동 예시
      • 코드
        • 2 → (iframe) 현재 웹 페이지 안에 새로운 웹 페이지를 삽입
        • 12 → (onload) 객체가 성공적으로 로드되었을 때 동작
        • 16~17 → iframe 객체의 location.href를 읽어 my-frame-log (div)에 출력
        <!-- create iframe object -->
        <iframe src="" id="my-frame"></iframe>
        <div id="my-frame-log"></div>
         
        <!-- begin javascript code -->
        <script>
          /* assign iframe object to myFrame variable */
          let myFrame = document.getElementById("my-frame");
          let myFrameLog = document.getElementById("my-frame-log");
         
          /* on assigning address to iframe object (myFrame.src), execute following code */
          myFrame.onload = () => {
            /* try ... catch for handling errors */
            try {
              /* upon load, print location.href value of iframe object to div element */
              let link = myFrame.contentWindow.location.href;
              myFrameLog = link;
            }
            catch (err) {
              /* print logs in case of errors */
              myFrameLog.innerText = err;
            }
          }
         
          /* functions for assigning Same Origin/Cross Origin addresses to iframe object */
          const loadSameOrigin = () => { myFrame.src = window.location.origin; }
          const loadCrossOrigin = () => { myFrame.src = "https://dreamhack.io/"; }
        </script>
         
        <!-- create two buttons (Same Origin, Cross Origin) -->
        <button onclick=loadSameOrigin()>Same Origin</button><br>
        <button onclick=loadCrossOrigin()>Cross Origin</button>

    same origin을 눌렀을 경우
    cross origin을 눌렀을 경우 (로그 참고)

     

    교차 출처 리소스 공유 (Cross Origin Resource Sharing)


    • 브라우저가 SOP와 상관 없이 외부 출처에 대한 접근을 허용 → <img>, <style>, <script> 태그 등
    • 메일 등에서 여러 호스트의 오리진이 다르다고 인식할 경우 여러 불편 발생 → CORS와 관련된 HTTP 헤더를 추가하여 자원(이미지, JS, CSS 등) 공유 (JSONP 방법도 있음 - JSON with Padding)
    • CORS
      • SOP를 완화한 방법으로 HTTP 헤더에 기반하여 Cross Origin 간 리소스 공유 (CORS 헤더 설정)
      • 수신 측의 응답이 발신 측의 요청과 상응하는지 확인한 후 수신 측의 웹 리소스를 요청하는 HTTP 요청을 보냄
        Header 설명
        Access-Control-Allow-Origin 헤더 값에 해당하는 Origin에서 들어오는 요청만 처리합니다.
        Access-Control-Allow-Methods 헤더 값에 해당하는 메소드의 요청만 처리합니다.
        Access-Control-Allow-Credentials 쿠키 사용 여부를 판단합니다. 예시의 경우 쿠키의 사용을 허용합니다.
        Access-Control-Allow-Headers 헤더 값에 해당하는 헤더의 사용 가능 여부를 나타냅니다.

     

    • 과정 예시
      • 웹 리소스 요청 코드 → POST 방식으로 HTTP 요청을 보냄
        /* 웹 리소스 요청 코드 */
        
        /*
            XMLHttpRequest 객체를 생성합니다. 
            XMLHttpRequest는 웹 브라우저와 웹 서버 간에 데이터 전송을
            도와주는 객체 입니다. 이를 통해 HTTP 요청을 보낼 수 있습니다.
        */
        xhr = new XMLHttpRequest();
        /* <https://theori.io/whoami> 페이지에 POST 요청을 보내도록 합니다. */
        xhr.open('POST', '<https://theori.io/whoami>');
        /* HTTP 요청을 보낼 때, 쿠키 정보도 함께 사용하도록 해줍니다. */
        xhr.withCredentials = true;
        /* HTTP Body를 JSON 형태로 보낼 것이라고 수신측에 알려줍니다. */
        xhr.setRequestHeader('Content-Type', 'application/json');
        /* xhr 객체를 통해 HTTP 요청을 실행합니다. */
        xhr.send("{'data':'WhoAmI'}");
        
      • 발신 측의 HTTP 요청 → OPTIONS 메소드를 가진 HTTP 요청이 전달 (CORS) → (헤더) Access-Control-Request
      • 서버 측의 HTTP 응답 → (CORS preflight) OPTIONS가 추가됨
    // 발신 측의 HTTP 요청
    OPTIONS /whoami HTTP/1.1
    Host: theori.io
    Connection: keep-alive
    Access-Control-Request-Method: POST
    Access-Control-Request-Headers: content-type
    Origin: https://dreamhack.io
    Accept: */*
    Referer: https://dreamhack.io/
    // 서버 측의 HTTP 응답
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: https://dreamhack.io
    Access-Control-Allow-Methods: POST, GET, OPTIONS
    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Headers: Content-Type

     

    JSON with Padding (JSONP)


    • <script> 태그를 이용해 Cross Origin의 데이터를 불러옴
    • Callback 함수 이용 → <script> 태그 내에서는 데이터를 자바스크립트의 코드로 인식하므로
    • CORS 이전 사용되던 방법으로 현재는 사용하지 않음

     

    • 과정 예시
      • 웹 리소스 요청 코드
        • (13) Cross Origin의 데이터를 불러오며 callback 파라미터를 통해 myCallback을 함께 전달
      • 웹 리소스 요청에 따른 응답 코드
    /* 웹 리소스 요청 코드 */
    
    <script>
    /* myCallback이라는 콜백 함수를 지정합니다. */
    function myCallback(data){
        /* 전달받은 인자에서 id를 콘솔에 출력합니다.*/
    	console.log(data.id)
    }
    </script>
    <!--
    https://theori.io의 스크립트를 로드하는 HTML 코드입니다.
    단, callback이라는 이름의 파라미터를 myCallback으로 지정함으로써
    수신측에게 myCallback 함수를 사용해 수신받겠다고 알립니다.
    -->
    <script src='http://theori.io/whoami?callback=myCallback'></script>
    /* 웹 리소스 요청에 따른 응답 코드 */
    
    /*
    수신측은 myCallback 이라는 함수를 통해 요청측에 데이터를 전달합니다.
    전달할 데이터는 현재 theori.io에서 클라이언트가 사용 중인 계정 정보인
    {'id': 'dreamhack'} 입니다. 
    */
    myCallback({'id':'dreamhack'});

     

    퀴즈 2


    퀴즈 2

Designed by Tistory.