블로그 이미지
자유로운설탕

calendar

1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Notice

2018. 11. 11. 20:36 보안

 이번 시간에는 인젝션과 비슷한 무게로 어플리케이션 보안을 이해하는 데 필요한 클라이언트 코드에 대해서 살펴보려고 한다. 개인적으로 인젝션이 "행위"라면, 클라이언트 코드는 그 행위가 일어나는 "환경"이라고 보면 어떨까 싶다. 이 시간에는 웹(이건 좀 자세하게)과 윈도우, 모바일 환경(이건 간단한 예를 들어서)에서 클라이언트 코드가 어떤 범위를 가지고, 어떤 의미를 가지고 있는지 설명해 보려고 한다. 

 

P.S.: 너무 점잖은 말투로 블로그를 적다보니 왠지 블로그 글 같지도 않고, 쓰는데도 부담이 많이 되서 다시 평상어로 적으려고 합니다~ 틀리면 말고 모드로 막 적겠습니다. 앞에 미리 쓴 글들도 여유가 생김 같이 바꾸려 해요. 이해 부탁 드려요. ㅎㅎ

 

 

[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 설정, 패치, 하드닝

10. 설계 문제

11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터

 

 

 

1. 들어가면서

  우선 클라이언트 코드(Client Code)의 의미는 무엇일까? 개인적으로 해당 의미를 "조작 가능한 코드"라고 말하고 싶다. 추가로 해당 범위는 코드 자체만을 가리킨다기 보다는 클라이언트 코드에 영향을 줄 수 있는 모든 행위와 매체(하드웨어 포함)를 포함하는게 맞지 않을까 생각한다. 

 

  예를 들어 아주 예전 코인 오락기에 전기 충격을 주어 코인을 늘리려고 했던 장치나, 게임을 조작하는 매크로 및 기계 장치들 부터 시작해, 로컬에 설치된 SQLite3 파일을 조작한다 거나, 쿠키 수정, 메모리 수정, 패킷 수정, 전통적인 웹 사이트 공격 기법 등이 모두 포함된다. 서버를 침투하려는 여러 시도의 경우도 서버 사이드 측면의 공격이라고 생각할 수도 있지만, 반대로 생각하면 필요없는 서비스를 실수로 오픈한 경우를 빼고는 모두 클라이언트(서버 끼리의 서비스라고 해도 넓은 범위에서는 마찬가지로 외부의 사람이나 프로그램등 클라이언트를 위한 거니까)를 위한 인터페이스를 제공하기 있기 때문에 클라이언트 측면의 공격이라고 우길 수도 있을 것  같다. 사실상 내부로 들어갈 수 있는 인터페이스가 없다면 공격은 불가능 한게 맞을테니까 말이다.

 

  또 비교적 안전하다고 생각하는 모바일 환경의 어플리케이션 등도 탈옥 등의 적절한 상황을 만나게 되면 웹, PC내의 프로그램과 마찬가지로 자유롭게 조작할 수 있다고 보는게 맞을 듯하다(물론 그러한 조작을 할 수 있는 능력이나 툴이 있느냐는 별개의 문제겠지만 말이다).

 

[오락실 딱딱이 - 나무위키]

https://namu.wiki/w/%EC%98%A4%EB%9D%BD%EC%8B%A4%20%EB%94%B1%EB%94%B1%EC%9D%B4

 

[자동사냥 - 나무위키]

https://namu.wiki/w/%EC%9E%90%EB%8F%99%EC%82%AC%EB%83%A5

 

  개인적으로는 이 클라이언트 코드를 이해하느냐의 문제가 어플리케이션 보안 문제의 70% 정도는 차지하지 않을까 하는 중요한 주제라고 생각된다. 이 클라이언트 코드를 제대로 이해하기 위해서, 프로그래밍 언어를 공부하고, 네트워크를 공부하고, 이런저런 프로토콜과, 웹, 컴퓨터 내부의 동작등을 이해하기 위해서 계속 노력해야 되지 않을까 싶다. 

 

   반대 관점에서 서버 사이드 코드는 서버안에 존재하고, 조작이 불가능하게 설계가 보장되어 있는 로직을 의미한다. 이 서버 사이드 코드를 안전하게 설계하기 위해서는 여러가지 노력이 필요하지만, 모순되게도 안전한 설계를 위해서는 클라이언트 코드가 어떤 범위와 의미를 가지느냐를 정확히 이해하고 있어야 하는 것 같다. 물론 외부의 변수를 받지 않는 폐쇄적인 로직을 구현한다면 가능하지도 모르지만, 현대의 프로그램 환경은 외부의 데이터와 연결 없이 프로그램 자체만 돌아가는 경우는 거의 없을 듯하기 때문에 불가능한 희망이라고 볼 수 있다.

 

  그럼 이 글에서 얼마나 해당 부분을 표현할 수 있을지 모르겠지만, 개인적으로 가지고 있는 관점을 이해시키기 위해 노력해 보려 한다. 물론 앞에도 얘기했지만 저는 딱 넘 못하지도 잘하지도 않는 중간 정도 레벨이라고 생각하기 때문에, 더 상위 레벨의 관점을 가진 사람들은 다르게 생각할 수도 있을 것 같지만... 제 블로그니까 뭐 제 맘대로...

 

 

 

 

2. 웹에서의 클라이언트 코드

  보안 공부하는 사람들이 많이들 하는 얘기중 하나가 웹 보안이 보안에 입문하기에 가장 쉽다고 하는 얘기다. 보안 공부를 가이드하는 글들을 찾다보면, 웹 보안을 먼저하고 점점 윈도우나 리눅스나 모바일 같은 보안으로 가는 식으로 얘기가 되서, 웹이 엄청 난이도가 낮은 것처럼 얘기되지만 해당 부분에는 좀 오해가 있다고 생각한다. 사실 웹 쪽 보안이 좀더 쉽게 느껴지는 이유는 기존 사람들의 노력과 표준적인 HTTP 프로토콜, 브라우저라는 제한된 환경 때문이라고 생각한다.

 

  만약 우리가 쿠키도 조작할수 없고, 버프, 피들러, 파로스 같은 좋은 프록시툴도 없고, 브라우저의 개발자 도구 같은 것도 없다면, 웹에 대한 보안의 난이도는 많이 높아질 것이다. 아마 제한된 소수의 사람만이 직접 http 패킷과 브라우저의 메모리 공간을 어렵게 조작하면서 웹 보안에 대한 지식을 독점했을 것 같다. 옛날에 SQL 인젝션 같은 기초적인 취약점들이 웹사이트 들에 엄청 퍼져 있던 것처럼 말이다.

 

  그리고 사실 웹은 서버사이드 코드로 들어가게 되면, SQL 이나 여러 외부 시스템과 맞물리게 된다. 물론 비교적 단순히 돌아가는 시스템도 많긴 한지만, 내부에 무엇이든 만들어 연결할 수 있다는 면에서 일반 어플리케이션과 딱히 틀린 점이 있나 싶은 측면도 있다. 요즘 같이 모든 기술이 점점 연결되어 가고, 데이터를 공유하는 상황에서는 웹에 대한 보안을 이해하기 위해서 서버 깊숙히 숨은 어플리케이션 부터 부터 전달되고 나오는 데이터를 처리하는 부분들에 대해서도 모두 이해해야 될 필요가 있기 때문에, 사실 꽤 오래전부터 해당 경계는 깨어져 버렸다고 생각한다. 그래서 웹에 대한 보안 공부를 하면서 웹에 제한된 관점으로만 공부하는 것은 잘못된 것이라고 생각한다. 어차피 모든 프로그램의 보안 문제는 원리적으로 보면 비슷비슷한 것도 같다.

 

  그래서 여기에서 얘기하는 웹이라는 것은 웹 환경의 전부가 아니라, 브라우저에 제한된 관점에서의 웹만을 얘기한다는 것을 우선 전제하고 싶다.

 

 

 

2.1 웹의 1세대 - 창고

  웹을 세대를 나누는게 맞을까 싶기도 하지만, 웹이 어떻게 진행되었느냐를 설명하기 위해서 개인적인 관점에서 나누자면, 1세대는 문서의 보관소 같은 창고 같은 역활을 하는 정적인 페이지로 이루어진 웹이라고 볼 수 있다.  

 

HTML 은 Hypertext Markup Language 의 약자로 HyperText 는 지금은 너무 평범하게 보이는 웹페이지에서 다른 페이지로 이동할 수 있는 링크를 얘기한다. 예전에 HTML 이 널리 퍼지기전 테스트 파일들만을 가진 환경에서 컴퓨터를 써보지 않았다면 해당 링크 기능이 얼마나 유용한 건지 알기 힘들 수도 있다. 하나의 문서를 보다가 연결된 다른 문서를 보기위해서는 편집기에 여러개의 텍스트 파일을 동시에 띄워놓고, 전체 검색을 통해서 열려진 문서를 왔다갔다 할수 밖에 없었다.  현재 비슷한 상황을 생각해 본다면, IDE 에서 메서드가 정의된 위치를 자동으로 연결해 이동해 찾는 것과, 파일 매니저를 통해 여러개의 소스 파일들을 뒤져서 해당 호출 위치를 찾는 것과 비슷한 차이가 있다.

 

  Markup Language 는 페이지의 구조를 정의 한다는 의미이다. 우리가 HTML 문서 안에서 보는 여러 태그(<html>,<body>,<td>,...)들이 그런 역활을 한다. 하얀 종이에 글을 적어서 의미있는 문장을 만들듯이, 미리 약속된 이런 태그와 속성들을 이용해서, 브라우저는 그림파일과 함께 배치해서 우리가 보는 네이버, 다음 같은 웹 페이지들처럼 예쁘게 화면을 표시한다. 사실 웹은 우리가 미적인 관점으로 화면을 보면서 이해하기 때문에 공감각적 의미가 있는 것이지, 내부적으로는 이런 마크업 언어들이 돌아다니는 세상이라고 보면 될듯 하다. 보안이나 개발에서는 이러한 세상을 주로 보면서 살고있다고 보면 될테고 말이다.

 

  웹의 1세대에에서는 우리가 URL(브라우저의 주소창에 우리가 적는 http://~~ 하는 사이트 주소들)경로를 호출하면 해당 웹사이트를 찾아가 원하는 위치에서 원하는 파일을 가져오는 역활에 제한 되었다. 해당 HTML 로 구성되어있는 문서를 가져다가(물론 꼭 HTML 문서만 있는건 아니고, 텍스트든 그림 파일이든 웹서버 폴더에 있는 파일들을 모두 지정해서 가져올 수 있다), 브라우저 엔진에서 구조적으로 표시해주는 역할을 할 뿐이였다. 이러한 브라우저 엔진이 IE냐 크롬이냐 사파리냐에 따라서 화면에 표시해 주는게 미묘하게 달라서 호환성 문제가 발생했으며, 이것은 다음에 설명할 자바스크립트에서도 마찬가지로 일어나서 XSS 취약점같은 경우 브라우저 종류 및 버전별로 해당 현상이 일어나기도 하고 안 일어나기도 하는 애매한 상황을 가져오기도 한다.

 

 

2.2 웹의 2세대 - 동적 페이지

  웹의 2세대(다시 말하지만 개인적인 분류다^^)에서는 페이지가 움직이고 체계를 잡기 시작했다고 보면 될것 같다.

 

  프로그래밍 코드와 디자인 코드를 분리하기 위해서 디자인 부분만을 담당하는 CSS 란 언어가 나왔고, 자바스크립트가 나와서 사용자 행동에 반응을 하며, 브라우저의 DOM(Document Object Model) 구조를 이용하여, 사용자에 행동에 따라 움직이는 것같이 생각되게 하는 동적인 웹 페이지를 지원하게 되었다. 또한 단순히 페이지 주소만을 호출하던 1세대와는 다르게 클라이언트와 서버사이에 Form 이라는 HTML 요소를 가지고 데이터를 전달(GET-HTTP 헤더, POST-HTTP 바디 내부)하였으며, 서버 쪽도 단순히 원하는 페이지를 찾아 전달해 주는 것을 벗어나 사용자의 입력을 통해 폼으로 전달해진 데이터를 기반으로 CGI(Common Gateway Interface), ASP, JSP 같은 로직을 이용해 처리함으로서 로직을 가지기 시작했다.

 

  그리고 가공된 데이터를 데이터베이스 안에 저장함으로서 1회적인 행동에서 벗어나, 사용자의 여러 행위나 상태등을 저장하여, 지금 우리가 사용하고 있는 여러가지 포털 사이트나, 은행 등의 사이트들이 돌아갈 수 있는 기반 구조가 만들어 졌다고 보면 될 듯 하다.

 

 

  HTTP 메시지는 아래와 같이 크게 헤더와 바디로 나누어져 있는데, 여기에서의 헤더, 바디는 HTML 에서의 문법에서의 헤더, 바디와는 조금은 다른 의미이다.

 

  나중에 피들러로 살펴보겠지만, HTTP Body 부분에 우리가 알고 있는 HTML 소스와(<header>, <body> 를 포함한 모두), 폼 전송시의 Post 데이터가 들어가게 된다(이미지 업로드시 MIME 인코딩된 데이터도 마찬가지이다). HTTP Headers 부분에 들어가 있는 데이터들이 웹 프로그래밍을 공부 하다보면 자주 보게 되는 사용자 에이전트 정보(user agent), 페이지를 어떤 언어로 보여줄것인가 하는 인코딩(encoding), 바로 이전 호출됬던 페이지를 나타내 주는 레퍼러(referer), http only 같은 쿠키 옵션들이 들어가게 된다.

 

 

  그럼 샘플 페이지를 피들러를 이용해 살펴 보면 HTTP 헤더, 바디 부분과 쿠키가 만들어지는 과정을 봐보도록 하자. ASP 코드의 실행 방법은 3교시 글을 참고하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<%
    ' 폼과 쿠키 값 받기
    strCustomID = request("txtCustomID")
    strLoginUser = Request.Cookies("LoginUserName")
%>
 
 
<html>
   <head>
      <title>쿠키 만들기</title>
   </head>
 
<body>
     <form name = "Custom" method="get" action="MakeCookie.asp">
        <table width = 600>
            <tr>        
                <td width = "60"> 아이디 </td>
                <td width = "100"> <INPUT maxlength="10" name="txtCustomID" size="10" type="text" value=<%=strCustomID%></td>
                <td>&nbsp;<input name=button type=submit value="쿠키 만들기"></td> 
            </tr>      
        </table>   
    </form>      
        <hr>
        <br><b>결과</b><br><br>
        
<%
    ' 1) 전달된 쿠키 표시    
    Response.Write "서버로 들어온 쿠키는 : " & strLoginUser & "<br><br>"
        
 
    ' 2) 등록된 회원일 경우 DB 에 저장하고, 쿠키를 생성함
    Select Case strCustomID
        Case "tom"
            Response.Write("tom 의 쿠키를 만듬"& "<br><br>"
            Response.Cookies("LoginUserName"= "tom"
        Case Else
            Response.Write("잘못된 ID 임"& "<br><br>"
            Response.end
    End Select
%>
 
</body>
</html>
cs

[makecookie.asp]

 

  편의상 하나의 페이지로 만들어 좀 헷깔리겠지만, 코드를 보면 맨 위에서, form 값인 txtCustomID 와 쿠키 값인 LoginUserName 을 받는다. 처음 페이지를 열게 되면 물론 쿠키 값이나 폼 값은 넘어오지 않으니 표시된 내용이 없을 것이다. 이후 코드를 보면 폼안에 txtCustomID 이름을 가진 입력 박스가 있고, 밑의 ASP 코드(VBScript 문법이다)를 보면 전달 되어온 쿠키 값을 표시하고, 아이디가 tom인 경우 LoginUserName 가 tom 인 쿠키를 만들어 낸다.

 

 

  해당 파일을 makecookie.asp 이름으로, c:\inetpub\wwwroot에 복사후, 피들러를 띄우고, 브라우저 주소창에 http://localhost/makecookie.asp 를 입력하여 이동하면, 아래와 같이 아이디를 입력하는 화면이 나오게 된다. 아직 쿠키나, ID가 전달 안 되었기 때문에, 밑에는 빈 칸으로 표시되거나 잘못된 아이디라고 나온다. 

 

 

  이때 피들러 쪽을 보면 아래와 같이 표시된다. 위쪽의 Request 된 항목을 보면 GET 으로 makecookie.asp 를 요청했고, 밑의 Response 된 헤더를 보면 SET-Cookie 라는 명령어와 함께 ASP 에서 디폴트로 만들어 주는 세션 쿠키가 생성된다.

 

 

  이 후 아이디 입력 란에 "tom" 을 넣고, "쿠키 만들기" 버튼을 누른다. 브라우저 주소창을 보면 ? 뒤에 인자로 txtCustomID=tom 이 날라가는게 보이고, 출력 화면을 보면 "tom 의 쿠키를 만듬" 이라는 메시지가 보인다. 서버 응답 쪽에서는 쿠키를 만들어 달라고 클라이언트 브라우저 쪽에 요청을 해서 브라우저는 쿠키를 만든 상태지만, 우리가 "tom" 을 넣고 전송한 과거 시점의 데이터는 아직 해당 쿠키가 없는 상태이기 때문에, 서버에서 인지한 쿠키 내용은 여전히 비어 있다. 

 

 

  피들러 쪽에서 보면, 위 쪽 request 에서는 아까와 조금 다르게 GET 부분에 txtCustomID=tom 이라는 인자가 날라가는 것이 보인다(WebForms 탭에서는 GET, POST 값을 모두 보여 주기 때문에 거기서 봐도 된다). 쿠키 쪽도 보면, 맨 처음 빈 페이지를 호출할 때와 다르게 아까 처음 페이지를 열때 서버 쪽에서 요청되온 ASP 세션 쿠키를 생성해서 날리고 있다. 밑의 response 부분을 보면, 현재 페이지를 만들어서 보여 주고 싶어한 부분이 보이게 되는데, "Set-Cookie" 라는 명령어 뒤에 우리가 ASP 페이지에서 쿠키를 만드는 코드에서 지정했던 "LoginUserName=tom" 이라는 쿠키를 만들어 달라는 명령어가 response 헤더를 통해 전달되게 된다.

 

 

  이 후에 다시 한번 jerry 같은 의미 없는 값을 넣고, 쿠키 만들기 버튼을 누르면, 헤더에는 아까 만들어진 LoginUserName=tom 이라는 쿠키가 전달되게 된다. 여기서는 브라우저 쪽을 보면, 아래와 같이 쿠키가 서버로 날라갔기 때문에, tom 이라는 쿠키를 표시하게 된다(jerry 로 쿠키를 만드는 코드는 없으니 잘못된 ID 라고 나오게 된다)

  

 

  해당 부분을 피들러로 보게 되면 이제 request 되는 쿠키 값에 "LoginUserName=tom" 이라는 항목이 날라가게 된다. 해당 쿠키는 다른 사이트를 호출할 때는 날라가지 않지만(예를 들어 네이버를 호출해 본다든지), 브라우저의 메모리 안에 계속 존재하며 언제든지 http://localhost 사이트를 호출 할때는 헤더에 첨부되어 날라가게 된다. 반대로 얘기하면 브라우저를 끄게 되면 사라지게 된다. 그래서 대부분의 사이트에서 ID/PASSWORD 를 통해 로그인 한 후, 브라우저를 모두 닫아 버리게 되면 해당 사이트에서 브라우저에게 만들게 했던 사용자 신원을 보장하는 쿠키가 날라가 버리게 되어서 로그아웃 되게 되어 버린다(만약 로그아웃이 안되는 사이트가 있다면, 로컬 파일 쿠키를 사용한다든지 하는 바람직하지 않은 경우일 것이다. 다만 모바일 기기의 경우는 같은 로컬이라도 좀더 소유기반으로 안전하게 보기 때문에, 로컬의 안전한 장소에 특정기간 인증코드를 남겨놓는 것은 약간은 별개의 문제이긴 한것 같다)  

 

  이 단순해 보이는 쿠키 생성을 통해, 사이트는 사용자의 신원을 보증하고 로그인 상태에서 사이트를 사용할 수 있게 한다. 정상적인 사이트의 경우는 저렇게 아이디만 던지는 경우는 없을 것이고 보통 아이디, 패스워드 쌍을 던져서, 사이트의 내부 디비에 저장한 아이디와 해시되어 보관된 패스워드 값과 비교한 후 인증이 된 경우만 쿠키를 생성하며, 일반적으로 쿠키 재사용을 방지하고 특정 시점의 만료처리를 할수 있는, 시간 및 기타 값들과 믹스하여 안전한 알고리즘으로으로 암호화 하여 관리한다. 

 

  이후 브라우저는 해당 값을 모든 브라우저가 닫히기 전에는 메모리에 안전하게 보관하면서, 해당 사이트의 페이지를 요청할때 위와 같이 헤더에 포함해 보내게 된다. 서버는 해당 쿠키 값을 받은 후 암호화를 해제해서 안의 사용자의 신원을 나타내는 고유값을 기반으로 사용자가 정상적으로 로그인 되었다고 믿고 작업을 하게 된다. 이 부분이 stateless 라는 http 통신에서 세션을 유지하게 되는 쿠키의 역할이며, 웹 어플리케이션 보안 쪽에서 많이 얘기하는 권한 상승, 세션 탈취 등의 어려운 용어가 저기에서 시작된다고 보면 된다. 해당 부분은 다른 측면도 있긴 하지만 주로 저렇게 인증을 위한 쿠키가 만들어 지는 부분을 파고들어 여러가지로 악용해 보려는 시도라고 봐도 될 듯 하다.

 

 

  앞의 얘기한 부분을 요약 하면 아래의 그림이라고 보면 될듯 하다.

 

 

 

 

2.3 웹의 3세대 - 비동기 호출 페이지(AJAX)

  개인적으로 3세대라고 얘기하고 싶고, 사실 현재 웹이 실질적으로 머무르고 있는 단계라고 생각하는 부분이 비동기 호출 페이지의 등장이다. 비동기 호출 이라는 것은 우리가 앞에서 봤던 것처럼 명시적으로 폼을 전송하는 방식이 아니라, 사용자 입장에서는 멈춰 있는 페이지의 뒷단에서, 자바스크립트 라이브러리인 AJAX 를 통해서 데이터를 주고 받는 것을 이야기 한다.

 

  아마도 2세대까지의 정식으로 HTML 페이지가 요청되고, 받아지는 부분과 상이한 움직이라고 해서, 비동기라고 지칭되긴 했지만, 사실상 엄밀히 따지면 요청하고 받는 행위가 페이지의 뒷단에서 벌어지는 것 뿐이므로 사전적 의미에서의 비동기 라는 말이 적합한가도 싶긴하다. 여하튼 페이지가 멈춰진 상태에서, 보통 사용자의 액션이나 주기적인 스케줄링에 기반해 트리거 되어 API 등을 호출해 데이터만을 교환하며, 가져온 데이터를 화면에 업데이트 하거나, 사용자가 인지 못하는 뒷단에서 광고, 사용성 추적 등을 목적으로 서버로 데이터를 전송하는데 사용하기도 한다.

 

 

  위의 그림에서 보듯 클라이언트와 서버 사이에 데이터의 교환만이 있기 때문에, 2세대와 같이 폼이 왔다갔다 하지 않는다. 아마도 초기에는 스트링 형태로 전송되다가 복잡한 데이터를 구조적으로 전송하기 위하여 XML 을 시용하다가, 점점 실용적으로 프로그램 간에서 교환할 수 있는 JSON 으로 변환되어 왔다고 보며, 현재는 거의 XML 과 JSON 으로 통일되어 데이터가 교환되는 것 같다.

 

  JSON 을 살펴보게 되면 항상 Serialize, Deserialize 라는 낯선 용어가 언급되게 되는데, Serailize 는 "일반 문자열로 만들기", Deserialize 는 "프로그램에서 사용하는 객체 형태의 데이터로 만들기" 정도로 해석하면 어떨까 싶다. 더 궁금하면 파이썬 19, 20교시에서 예제를 보면 된다. 

 

  클라이언트 프로그램 내(자바스크립트)에서 사용하는 딕셔너리나, 리스트 구조를 웹으로 전송하기 위해 {}, 등의 문자로 이루어진 문자열 형태로 압축해서 만들고, 네트워크 상에서는 문자열 형태로 전송 된다. 다시 서버 쪽의 언어에서 해당 값을 받은 후 다시 프로그램내에서 사용하는 자료구조인 리스트나 딕셔너리 구조로 바꾸어 다차원적인 데이터 형태를 무너뜨리지 않고 자연스럽게 데이터를 교환하는 형태이다. 뭐 만화의 한 장면 같은 예를 들자면 냉장고를 접어서 서류봉투안에 넣어 배달하고 다시 꺼내어 펼치면 냉장고가 되는 느낌이라고 할까. 그래서 단순한 교환을 위한 데이터 형태인 XML 보다, 프로그램에서 자주 사용하는 데이터를 유지할 수 있는 JSON 이 인기 인것 같다(물론 XML 도 같은 역활을 해주는 라이브러리가 있지만 두 개의 언어를 써보면 JSON 이 압도적으로 실용적인것 같다. 반대로 얘기하면 데이터 정합성에 엄격한 분야에서는 아직도 XML 을 선호할 것도 같다). 물론 여기서 보안적으로 중요한 사실은 전송되는 형태가 평범한 "문자열" 이라는 것이다.

 

 

  Ajax 에 대한 코드 샘플은 파이썬 글 18교시를 보거나, 뒤의 피들러로 시연하는 샘플을 통해 보면 될것 같고, 여기서는 일상속에서 많이 보이는 예제 하나를 보여주고 마무리 하려 한다. 아래는 구글의 검색어 추천 로직으로, 사용자가 c, a, t 즉 cat 이라고 적을 때마다 하단 박스에 해당되는 추천 검색어가 나오게 된다. 그럼 이 부분을 피들러를 켠 채로 실행시키고 피들러로 잡힌 호출을 보도록 하겠다.   

 

 

  피들러를 보게되면(요즘은 웹 뒤에서 몰래 돌아가는 호출이 많아서, 잡다한 호출 등은 가독성을 위해 정리했다), 아래와 같이 /complete/search 라는 url 이 3번 호출됬다. 상단 request 섹션의 Webforms 탭을 클릭해보면, 사용자가 입력하는 글자는 q 라는 이름을 가진 인자와 매핑되어 있다. 위의 요청 부터 하나하나 클릭해 보면 q 값이 c, ca, cat 로 변해가는 것을 볼 수있다. 하단 response 탭의 JSON 탭을 보면, 피들러가 왔다갔다 하는 데이터를 JSON 형태로 해석한 부분이 보인다.

 

  거기에 보면 트리 형태로(원래는 아까 얘기했듯이 {}, 등으로 구성된 문자열을 피들러가 해석해 트리 모양으로 보여주는 것이다) "cat", "cat<b>egory</b>" 등의 상단 그림에서 봤던 추천 검색어 들이 구성되어 있다, 아마도 구글의  메인 페이지를 찬찬히 뜯어보면 저 /complete/search url 을 AJAX 방식으로 호출하고, 결과 값인 JSON 데이터를 받아, DOM 구조를 이용해 숨켜놨던 네모 박스를 보여주면서, 그 안에 가져온 데이터를 뿌려주는 로직이 있을 것이다.

 

  그러면 평범한 사용자들은 해당 액션을 보고 내부에 이런 왔다갔다 함이 있음을 인지하지 못하고, 내가 입력한 값에 대해서 사이트가 신기하게도 추천 검색어를 뿌려주는 거나 생각을 할 것이다. 다만 보안적인 입장에서는 화면보다는 이 뒷단의 데이터로 주로 봐야하는 것이 다를 뿐이다(그러고 보면 웹은 거대한 눈 속임수 같은 느낌도 있지만 그건 뭐 디지탈화된 영상이나 사진 등도 실상은 마찬가지니...)

 

 

 

 

2.4 클라이언트 vs 서버 코드

  위의 장황한 이야기의 결론은 아래의 그림을 표시하기 위해서였다. 우리는 여러가지 예제를 피들러를 통해 살펴 봄으로써 HTML 코드나, CSS, 자바스크립트 등을 평문으로 볼수 있고(피들러의 Response 섹션의 TextView 탭을 보면 된다), 날라가는 폼 값도 피들러로 볼수 있고(이건 WebForms 탭), HTTP 헤더내에 있는 여러 쿠키나, 사용자 에이전트, referer 등도 볼수 있었다(이건 Headers 탭). 또 한 한발 더 나아가 브라우저의 뒷면에서 남 모르게 날라가는 AJAX 데이터의 입력과 출력도 하나하나 볼수 있었다. 결국 이러한 데이터들을 피들러로 볼수 있다는 얘기는 피들러로 "조작"이 가능하다는 얘기가 된다. 다음 섹션에서 이러한 코드를 하나하나 피들러로 조작하는 예제를 보여주면서 해당 부분을 살펴보려 한다.  

 

  반대로 우리가 피들러로 볼수 없어 조작할 수 없는 코드는 서버 사이드에서 돌아가는 로직 들이다. C++(레거시 CGI 에서 사용), C#, JAVA, PHP 같은 언어로 구성된 코드들은 우리는 내용을 볼 수도 조작할 수도 없다. 하지만 불행하게도 서버 뒷단의 로직은 사용자의 액션에 따라 특정한 로직을 지원하기 위해 생성된 경우가 대부분이기 때문에, 자바스크립트나 HTML 을 잘 분석하거나 폼이나 AJAX 의 입출력 인자를 살펴보거나, 히든 필드를 보거나, 비즈니스 로직을 살펴서 많은 유추가 가능하다.

 

  그리고 서버 뒷단의 로직은 결국 클라이언트에서 날라간 폼 값이나 AJAX 데이터 값을 기반으로 동작하기 때문에 해당 로직을 조작하여, 코드에 영향을 미치려는 시도를 할 수 있다(이게 웹 해킹의 많은 부분을 차지한다고 본다). 마지막으로 서버가 탈취 당했을 때도 공격자의 역량에 따라 서버 사이트 코드에 영향을 미칠 수도 있다(요즘은 빌드된 언어가 많아서, 스크립트 코드보다는 빌드된 형태의 코드를 원하는 데로 조작해야 되서 난이도가 더 높을 듯은 싶다) 

 

 

 

 

 

2.5 피들러로 클라이언트 코드 조작해보기

  그럼 앞에 설명한 클라이언트 예제들을 하나하나 조작하는 것을 시연해 보면서, 각 행위가 웹 어플리케이션의 보안적 측면에서 어떤 의미가 살펴보도록 하자. 앞에 만든 쿠키 페이지에 코드를 추가해 샘플 페이지로 만든 것이 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<script>
function validateForm() {
    var x = document.forms["Custom"]["outMoney"].value;
    var y = document.forms["Custom"]["customID"].value;
    
    if (x > 10000) {
        alert("10000원 초과 출금 안됨");
        return false;
    }
    else if (x == null || x == "") {
        alert("찾으실 금액을 넣어주세요");
        return false;
    }
    
    if (y == null || y == "") {
        alert("아이디를 넣어주세요");
        return false;
    }
    
}
</script>
 
<%
    ' 폼과 쿠키 값 받기
    strCustomID = request("customID")
    strOutPut = request("outMoney")
    strLoginUserID = Request.Cookies("LoginUserID")
%>
 
<html>
   <head>
      <title>인출 하기</title>
   </head>
 
<body>
     <form name = "Custom" method="get" action="GiveMe.asp" onsubmit="return validateForm()">
        <table width = 600>
            <tr>        
                <td width = "60"> 아이디 </td>
                <td width = "100"> <INPUT maxlength="4" name="customID" size="10" type="text" value=<%=strCustomID%></td>
                <td> | </td>
                <td width = "130"> output money </td>
                <td width = "100"> <INPUT maxlength="10" name="outMoney" size="10" type="text" value=<%=strOutPut%></td>
                <td>&nbsp;<input name=button type=submit value="현금 인출하기"></td> 
            </tr>      
        </table>   
    </form>      
    
    <hr>
    <br><b>결과</b>
    <br><br>
        
<%
    ' 1) 전달된 쿠키 표시    
    Response.Write "1. 서버로 들어온 쿠키는 : " & strLoginUserID & "<br><br>"
        
 
    ' 2) 등록된 회원일 경우 쿠키를 생성함
    Select Case strCustomID
        Case "tom"
            Response.Write("2. tom의 쿠키를 만듬"& "<br><br>"
            Response.Cookies("LoginUserID"= "tom"
        Case "lucy"
            Response.Write("2. lucy의 쿠키를 만듬"& "<br><br>"
            Response.Cookies("LoginUserID"= "jerry"
        Case "secret_code"
            Response.Write("2. secret_code의 쿠키를 만듬"& "<br><br>"
            Response.Cookies("LoginUserID"= "secret_code"
        Case Else
            Response.Write("2. 인식 못하는 에러 발생"& "<br><br>"
            Response.end
    End Select
    
    '3) 금액을 인출 해줌.
    Response.Write "3. 인출 금액 : " & strOutPut & " 원" & "<br><br>"
%>
 
</body>
</html>
cs

[GiveMe.asp]

 

  코드를 보면 맨 위에 자바 스크립트로 입력 값이 빈칸인지를 간단히 체크하고, 인출 하려는 금액이 10000원을 초과하면 alert 을 띄우는 체크 코드가 있다. 그 밑에 보면 넘어온 폼 내의 아이디와 금액 값과, LoginUserID 라는 쿠키 값을 받아서 프로그램 내부 변수에 담는다.

 

  그 아래의 HTML 코드는 사용자에게 값을 넣게 하거나, 처리 결과를 보여주게 하는 페이지이다(복잡성을 낮추기 위해서 하나의 페이지에서 입력과 출력을 동시에 하게 했다).

 

  그 다음 VBSCRIPT 코드(ASP 는 해당 VBSCRIPT 문법임)에서 1) 서버에서 인지한 쿠키를 표시하고, 2) 맞는 아이디가(tom, lucy, secret_code)가 들어왔을때 해당 쿠키를 만들어 준다. secret_code는 사실 이 페이지에서는 입력할 수 없는 디버그 아이디로 HTML 코드에 보면 custID 라는 name 속성을 가진 인풋 박스의 maxlength 값이 4로 되어 있어 페이지에서는 4글자 이상이 입력되지 않고 있다. 3) 마지막으로 아이디가 잘 맞는 경우 해당 입력한 금액을 인출해 준다(사실 이 부분도 해당 아이디에 지불할 금액이 있는지 등등의 체크를 하기 위해서 디비를 다녀오고 그래야 하지만 예제의 간결성을 위해서 과감히 생략했다).

 

 

  c:\inetpub\wwwroot 폴더에 GiveMe.asp 라고 저장하고, http://localhost/GiveMe.asp 를 호출해 실제 페이지가 뜬 화면을 보면 아래와 같다.

 

 

 

2.5.1 전송하는 폼 값 바꾸기

  첫 번째로 전송하는 폼 값을 바꿔 보는 예제를 보기로 하자. 현재는 자바스크립트로 막혀있어 10000원을 초과하는 금액을 인출할 수 없는데 1,000,000원을 인출해 보기로 한다. 우선 피들러를 키고 빈 페이지를 호출 한다(http://localhost/GiveMe.asp). 피들러의 숨은 기능 중 하나가 IDE 같이 호출 중간에 디버깅 기능을 걸수 있는데, Burf 나 Paros 같은 프록시 툴에서 하나하나 페이지를 보면서 넘기는 것과 비슷하다(피들러가 동작하는 원리는 파이썬 글 10교시에서 설명했으니 궁금하면 보고 오면 된다)

 

 

  피들러 상단 메뉴에서 "Rules > Automatic Breakpoints > Before Requests" 를 체크 한다.

(이 상태에서는 피들러가 브라우저의 모든 호출을 잡아버리기 때문에 작업을 끝내면 꼭 피들러를 끄거나 해당 값을 "Disabled" 로 바꾸어야 웹 페이지 들이 정상으로 호출된다)

 

 

  이후 GiveMe.asp 페이지에서 lucy 를 입력하고, 1000000 을 입력한 후, "현금 인출하기" 버튼을 클릭 한다. "1000000원 출금 안됨" 이라는 alert이 뜰 것이다. 자바스크립트에서 막은 것이기 때문에 브라우저 프로세스 내에서 일어난 일이며 실제 서버로 요청은 안 간것이기 때문에 피들러에는 아무것도 잡히지 않는다.

 

 

  이제 금액을 1000 으로 조정 한후, "현금 인출하기" 버튼을 클릭한다. 브라우저 화면을 보면 탭 이름 부분의 앞이 빙글 빙글 돌아가는 표시가 보이며 localhost 서버의 응답을 기다리는 중이라는 메시지가 표시된다. 현재 상태는 브라우저가 사용자가 입력한 폼 값을 서버로 전송했지만(여기서는 로컬 서버이긴 하지만), 피들러가 중간에서 잡고 서버한테 전달을 안하면서 땡깡을 부리는 중이라고 볼수 있다.

 

 

  땡깡을 부리고 있는 피들러 쪽으로 가보면 기존에 못보던 "Run to Completion" 이라는 버튼이 오른쪽 상단의 Request 섹션에 생겨 있다. "WebForms" 탭을 선택하면 우리가 입력한 lucy 와 1000 이라는 숫자가 있다. 아래 그림과 같이 outMoney 에 있는 값을 1000000 으로 살포시 바꿔보자. 이후 "Run to Completion" 버튼을 누른다.

 

 

  이렇게 되면 브라우저가 체크해 주던 자바스크립트의 방어 구역을 벗어나 네트워크 상에서 피들러가 해당 값을 수정한 것이기 때문에 서버에는 해당 값이 그대로 전달되어 버리게 된다. 서버의 페이지를 보면 아래와 같이 인출 되지 말아야될 lucy 의 돈 백만원이 인출 되고 만다.

 

  해당 케이스가 아주 간단해 보이지만, 거의 모든 가격 및 사용조작에 영향을 미치는 큰 행위의 원리가 되는 행위이다. 해당 부분은 브라우저의 사용자 인터페이스로 제한된 한계를 넘을 수 있느냐 없느냐를 인지하는 부분이기 때문이다.

 

  예를 들어 주문서 페이지의 HTML 과 자바스크립트 구조를 분석하여 서버로 날라가는 여러 값들을 잘 정리할 수 있다면(이 변수는 가격, 이 변수는 적립금, 이 변수는 쿠폰, 이 변수는 할인 금액, 이 변수는 본인인증 여부 등등) 해당 값들이 왔다갔다하는 적절한 타이밍에 값을 바꿔치기 해주면 원하는 값을 조작할 수도 있다. 신문에 가끔 나는 해커가 몇백만원 짜리 물건을 몇백원에 결제해서 악용하다 감옥에 갔다는 얘기는 사실 이 단순해 보이는 행위에서 시작된다. 물론 페이지를 분석해 관련 로직을 이해하고 원하는 요소들을 찾아내는 능력은 별개로 필요하긴 하지만 말이다. 

 

 

  그럼 이렇게 큰일에 대해서 어떻게 대비를 하냐 얘기한다면, 해결 방법은 클라이언트 코드를 신뢰하지 않음 된다고 얘기하고 싶다. 위의 ASP 코드 중 인출 부분을 방어하는 코드로 바꿔본다면, 아래와 같이 폼으로 넘어온 값을 한번 더 체크하는 수정을 하면 된다.

1
2
3
4
5
6
    '3) 금액을 인출 해줌.
    If strOutPut > 10000 Then
       Response.Write("3. 너무 많은 금액임<br><br>")
    Else     
       Response.Write "3. 인출 금액 : " & strOutPut & " 원" & "<br><br>"
    End If
cs

 

 

  해당 코드를 수정 한후 저장 후, 같은 행위를 하게 되면 아래와 같이 서버에서 너무 많은 금액이라고 막히는 화면이 나온다(확인 후에는 시연을 위해서 다시 예전 코드로 돌리거나 주석을 하자^^. 브레이크 포인트도 disabled 시키는거 잊지 말고..)

 

  이 아주 작은 차이 하나가 사이트를 취약하게 하느냐 취약하지 않게 하느냐의 차이를 만든다는 사실은 조금 우습기도 하지만, 사실 이 차이는 개발자가 클라이언트 코드와 서버 사이드 코드의 차이점을 정확하게 이해하고, 클라이언트 코드를 믿지않고 서버 사이드에 최종 방어 코드를 넣어두었냐는 큰 차이기도 하다. 기능 구현과 성능과 버그에 집중하고 있는 개발자가 보안적인 관점에서 코드와 코드가 돌아가는 시스템 환경을 보는 것은 어느날 쉽게 얻을 수 있는 부분은 아닌 것도 같다.

 

 

 

2.5.2 HTML DOM 구조 바꾸기

  두 번째는 HTML 소스 구조를 바꾸는 부분이다. 이 부분은 개발자 도구를 이용해서, 브라우저 메모리 상에서 바꿔치기 할수도 있지만, 여기서는 피들러를 통해서 네트워크 상에서 바꾸는 예제를 보려한다. 시나리오는 아까 아이디 입력 부분이 maxlength 로 제한되어 4글자 이상 안들어 가고 있는 상황에서, 숨겨진 아이디인 secret_code 를 넣어서 돈을 인출 하게 하는 것이다. 물론 앞에 있던 전송되는 폼 값에서 아이디를 바꿔치는 방법도 있겠지만, 여기서는 다른 측면을 보려고 한다.

 

  이번엔 피들러의 브레이크 포인트를 아까와는 반대로 "Rules > Automatic Breakpoints > After Reponses" 를 선택하여 건다(아까의 아래 메뉴니 굳이 화면은 첨부 안해도 될듯 하다)

 

  피들러가 reponse 를 잡는 부분을 확실히 보기 위해서 브라우저를 모두 종료한 후 다시 띄운 후 빈 창에서 http://localhost/GiveMe.asp 페이지를 호출 한다. 그럼 다시 아래와 같이 페이지가 안 뜨고 완전히 빈 화면만 나오게 된다(브라우저에 따라서 아까 처럼 빙글빙글 도는 표시가 있을 수도 없을 수도 있다). 현재는 브라우저가 서버 쪽에 요청을 한 후, 서버 쪽은 브라우저에게 표시할 페이지에 대한 HTML 을 전달해 줬는데 피들러가 잡고 브라우저에게 아직 안 주고 있는 상황이다.

 

 

  피들러 화면으로 가면 이번에 하단 reponse 쪽에 "Run to Completion" 버튼이 보이고 있다. "TextView" 탭을 클릭하면 브라우저로 전달될 HTML 코드가 보이고 있다(참고로 인코딩이 뭔가 호환이 안되는지 한글은 깨져보인다). 해당 소스에서 name=custumID 인 인풋 박스를 찾아서 maxlength 를 4에서 30으로 바꾸어 본다. 이후 "Run to Completion" 버튼을 누른다(이제 브레이크 포인트는 풀어보자)

 

  이제 브라우저로 돌아가면 피들러가 이제야 전달해준 소스가 보이게 된다(한글이 깨지지만 신경쓰진 말자). 이제 인풋 박스 제한이 30글자가 되어 secret_id 가 입력이 되며, 실제 브라우저 소스 보기를 하면 30으로 조정된 값이 보인다. secret_id를 넣어 전송하는 것은 뻔하니까 굳이 시연하진 않는다.

 

 

  이 부분도 그래서? 라고 생각할 수도 있지만, 서버에서 클라이언트가 사용하기를 원해 전달했던 HTML 소스를 마음대로 변경할 수 있다는 부분에서 큰 의미를 가지게 된다. 그런 일은 없겠지만 마치 XSS 코드가 외부에서 들어온것 처럼 커스텀 코드가 들어갈 수 있으며(물론 자바스크립트 문법은 준수해야 페이지가 깨지지 않고 동작한다). 여러 설정해 놓은 히든 값이나, 숨겨놓은 코드, 더 나아가면 다른 페이지의 코드를 가져다가 붙이는 행위도 할 수 있다(원래는 특정 아이디나 조건에서만 쓸수 있는 결제 화면을 넣는다든지). 이 부분은 공격하는 사람의 상상력에 따라 얼마든 창의적일 수 있으며, 브라우저는 서버가 전달해준 코드라고 생각하기 때문에 XSS 공격같이 방어해주는 일도 없다(설사 방어해줘도 의미는 없겠지만 말이다).

 

 

 

2.5.3 자바 스크립트 조작하기

  이번에는 아까 백만원을 입력할 수 있었던 예제를 자바 스크립트를 조작해서 시연해 보도록 한다. 아까는 request 의 폼인자를 조작했다면 이번엔 response 를 조작해 본다. 3.5.2 와 마찬가지로 피들러의 브레이크 포인트를 "Rules > Automatic Breakpoints > After Reponses" 를 선택하여 건다.

 

  이후 똑같이 브라우저를 모두 끄고, GiveMe.asp 페이지를 호출 한다. 역시 마찬가지로 Response 섹션의 "TextView" 탭으로 가면 아까 ASP 소스내에서 코딩해 놨던 자바 스크립트 코드가 보인다. 해당 코드를 선택해서  함수 이름과 {} 스크립트 뼈대만 남기고 모두 지운다(뼈대를 남기는 이유는 아까도 얘기했지만 문법을 깨뜨리지 않기 위해서다. 문법을 깨뜨리면 브라우저가 동적으로 동작을 안하게 된다. 물론 저 함수를 지우고 HTML 쪽 이벤트를 수정해도 되지만 이게 더 편해 보인다). 이후 "Run to Completion" 버튼을 누르고 1000000 이상의 금액을 넣고 전송하면 자바스크립트 체크로직이 없어져서 전송이 된다.

1
2
3
4
<script>
function validateForm() {
}
</script>
cs

 

 

  여기에서 사실 자바스크립트는 페이지의 일부 이기 때문에 3.5.2 와 같이 당연히 조작 가능하긴 하지만,  보통 프로그램에서 자바스크립트 파일은 따로 떨어져 있는 경우가 많은데 해당 부분도 역시 마찬가지이다. 해당 현상을 이용해서 여러가지 브라우저 단에서 체크하는 검증 로직들을 이론적으로는 모두 우회할 수 있다고 보면 될것 같다(물론 서버쪽에 최종으로 검증하는 설계가 없다는 가정하에지만 말이다). 

 

 

 

2.5.4 쿠키 조작

  이번엔 지금까지 서버가 돈을 지불해야 하는 사람을 판단하는데 사용했던 쿠키 값를 수정해 보려고 한다. 브라우저에서 GiveMe.asp 페이지를 연 후 lucy, 100 을 입력 후, "현금 인출하기" 버튼을 클릭해 쿠키를 생성한다. 이후 그대로 한번 더 "현금 인출하기" 버튼을 클릭해 만들어진 쿠키를 서버로 전송해 쿠키가 만들어진 아래 화면을 확인 한다.

 

 

  그리고 피들러에 가서 3.5.1 에서 했던 것처럼 "Rules > Automatic Breakpoints > Before Requests" 를 체크 한다. 이후 다시 "현금 인출하기" 버튼을 누르면 피들러가 전송되는 요청을 잡게 된다.

 

  피들러 화면으로 가서 상단 request 섹션에서 Headers 탭을 선택하게 되면 HTTP 헤더로 전송되는 여러 데이터를 볼 수 있다. 그중 쿠키 부분의 값을 선택하고, 마우스 오른쪽 버튼을 클릭해서 컨텍스트 메뉴를 띄워 "Edit Header..." 메뉴를 클릭한다.

 

 

  편집 화면이 나오면 Values 부분에서 LoginUserID 를 lucy 에서 tom 으로 바꾼다. 이후 Save 버튼을 눌러 저장한다. 이후 앞에서와 마찬가지로 "Run to Completion" 버튼을 클릭하여 전송한다.

 

 

  이제 브라우저 쪽을 체크해 보면 넘어온 쿠키 값이 tom 으로 바뀌어서, 서버쪽에서 tom 쿠키를 출력해 주게 된다.  

 

  물론 실제 현실에서는 이렇게 쿠키를 평문 상태로 사용하는 경우는 별로 없지만, 암호화 하더라도 해당 값을 바꿔치기 할 수 있다는 사실은 변하지 않는다. 4교시에도 얘기했지만 암호화 하더라도 내부의 값의 고유성은 변경되지 않거나 혹은 1회성 토큰이 아닌 경우는 특정 시간 동안은 유효하기 때문에 때문에 relpay 등에 재사용 되는 경우는 있기 때문이다.

 

 

 

2.5.5 비동기 호출 조작(AJAX, API)

  마지막으로 AJAX 등의 비동기 호출을 조작하는 방법에 대해서 알아보자. 이 부분은 비동기 적인 호출 뿐만 아니라 API 등의 호출도 마찬가지이다(API 는 파이썬 글 10교시, AJAX 는 파이썬 글 18교시에 비교적 자세히 설명되어 있다)

 

  AJAX 예제를 보여주기 위해서 위의 페이지를 조금 수정 하고 getMoneyInfo.asp 라는 AJAX 호출시 호출할 페이지를 추가로 만든다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<script>
function validateForm() {
    var x = document.forms["Custom"]["outMoney"].value;
    var y = document.forms["Custom"]["customID"].value;
    
    if (x > 10000) {
        alert("10000원 초과 출금 안됨");
        return false;
    }
    else if (x == null || x == "") {
        alert("찾으실 금액을 넣어주세요");
        return false;
    }
    
    if (y == null || y == "") {
        alert("아이디를 넣어주세요");
        return false;
    }
    
}
</script>
 
<script>
function showMoneyInfo() {
  var xhttp;
  var loginUserCookie = getCookie('LoginUserID');
 
  xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (xhttp.readyState == 4 && xhttp.status == 200) {
      document.getElementById("txtMoneyInfo").innerHTML = xhttp.responseText+"원";
    }
  }
  xhttp.open("GET""getMoneyInfo.asp?myID="+loginUserCookie, true);
  xhttp.send();
}
 
 
function getCookie(name) {
  var value = "; " + document.cookie;
  var parts = value.split("; " + name + "=");
  if (parts.length == 2return parts.pop().split(";").shift();
}
 
 
</script>
 
<%
    ' 폼과 쿠키 값 받기
    strCustomID = request("customID")
    strOutPut = request("outMoney")
    strLoginUserID = Request.Cookies("LoginUserID")
%>
 
<html>
   <head>
      <title>인출 하기</title>
   </head>
 
<body>
     <form name = "Custom" method="get" action="GiveMe_Ajax.asp" onsubmit="return validateForm()">
        <table width = 600>
            <tr>        
                <td width = "60"> 아이디 </td>
                <td width = "100"> <INPUT maxlength="4" name="customID" size="10" type="text" value=<%=strCustomID%></td>
                <td> | </td>
                <td width = "130"> 출금 금액 </td>
                <td width = "100"> <INPUT maxlength="10" name="outMoney" size="10" type="text" value=<%=strOutPut%></td>
                <td>&nbsp;<input name=button type=submit value="현금 인출하기"></td>
            </tr>
            <tr>
            <td colspan=6>
                내 잔고: <input type="button" value="잔고 보기" onclick="showMoneyInfo()">
                : <span id="txtMoneyInfo"></span>
            </td>
        </tr>  
        </table>   
    </form>      
    
    <hr>
    <br><b>결과</b>
    <br><br>
        
<%
    ' 1) 전달된 쿠키 표시    
    Response.Write "1. 서버로 들어온 쿠키는 : " & strLoginUserID & "<br><br>"
        
 
    ' 2) 등록된 회원일 경우 쿠키를 생성함
    Select Case strCustomID
        Case "tom"
            Response.Write("2. tom의 쿠키를 만듬"& "<br><br>"
            Response.Cookies("LoginUserID"= "tom"
        Case "lucy"
            Response.Write("2. lucy의 쿠키를 만듬"& "<br><br>"
            Response.Cookies("LoginUserID"= "lucy"
        Case "secret_code"
            Response.Write("2. secret_code의 쿠키를 만듬"& "<br><br>"
            Response.Cookies("LoginUserID"= "secret_code"
        Case Else
            Response.Write("2. 인식 못하는 에러 발생"& "<br><br>"
            Response.end
    End Select
    
    '3) 금액을 인출 해줌.
    'If strOutPut > 10000 Then
       'Response.Write("3. 너무 많은 금액임<br><br>")
    'Else     
       Response.Write "3. 인출 금액 : " & strOutPut & " 원" & "<br><br>"
    'End If
%>
 
</body>
</html>
cs

[GiveMe_AJAX.asp]

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%@ Language=VBScript %>
 
 
<%
    ' 폼 값 받기
    customID = request("myID")
    
 
    '등록된 회원을 검색해 잔고를 얻어옴
    Select Case customID
        Case "tom"
            Response.Write("100,000")
        Case "lucy"
            Response.Write("200,000")
        Case "secret"
            Response.Write("100,000,000")
        Case Else
            Response.Write("error")
    End Select
%>
cs

[getMoneyInfo.asp]

 

  기존 자바 스크립트 코드 아래를 보면 showMoneyInfo 라는 새로운 메서드가 만들어져 있으며, 그 안에 보면 특정 쿠키 값을 받고(var loginUserCookie = getCookie('LoginUserID');), 해당 쿠키 값을 AJAX 호출을 통해 myID 라는 인자로 getMoneyInfo.asp 페이지에 전달해  잔고를 받아오며, 해당 응답 값을 받아오는게 성공 한다면 가져온 값을 HTML 소스에 추가한 txtMoneyInfo 라는 span 태그의 값으로 치환한다.

 

 

  그래서 아래와 같이 우리가 잔고보기 버튼을 눌렀을때, showMoneyInfo 메서드를 호출해서 해당 아이디의 잔고를 받아와 화면에 표시하게 된다. 조금 복잡해 보이는 AJAX 코드는 스트레스 받지 말고 그 라이브러리를 설계한 사람이 그렇게 만든거니 그렇게 쓰면 되려니 생각하면 된다.

 

 

  피들러를 켜고 위의 그림과 똑같이 lucy 와 100을 입력하고 현금 인출하기 버튼을 2번(쿠키 값을 페이지에 표시하기 위해 한번 더 눌렀다) 클릭한다. 이후 "잔고 보기" 버튼을 눌러보자. 그럼 페이지가 리프레쉬 되지 않고도 200,000원 이라는 잔고를 가져와 화면에 뿌려주게 된다(참고로 AJAX 호출을 할때, 같은 값이 인자로 넘어가게 되면 브라우저는 실제 호출을 안하고 캐시된 값을 보여주게 된다. 그래서 같은 쿠키 ID 값으로 다시 AJAX 호출이 되게 하고 싶으면 인자에 랜덤 값을 추가로 첨부하거나 브라우저를 껐다 켜면 된다)

 

 

  이 상황에서 tom 의 잔고를 알려면 어떻게 해야할까? 앞의 예제들을 생각해 보면 전송되는 쿠키 값을 바꿔치기 한 후 페이지에 들어와 "잔고 보기" 버튼을 눌러도 될것 같다. 그런데 조금 더 쉽고 직접 적인 방법이 있다. 피들러 화면으로 가면 "잔고 보기" 버튼을 눌렀을때, 우리가 만들어 놓은 로직에 의해 getMoneyInfo.asp 페이지(사실 여기서는 API 같은 역활이다)를 호출한 내역이 있다. request 의 WebFroms 부분을 보면 쿠키 값인 lucy 가 myID 에 담겨 날라가고 있고, response 의 TextView 를 보면 200,000 이라는 숫자가 넘어오는 것이 보인다(잘 시연할 수 있을듯 해서 스크린샷은 생략한다).

 

  그럼 피들러의 다른 기능인 Replay 기능을 이용해 보도록 하겠다. 해당 기능은 기존에 호출한 URL을 다시 똑같이 호출해 주게 되는데, 비단 URL 뿐만 아니라 우리가 앞에서 살펴본 쿠키등이 담겨있는 HTTP 헤더를 포함해 통채로 보내주게 된다. 그래서 만약 인증에 사용되는 쿠키나 토큰이 만료되지만 않았다면, 이전에 실행시켰던 환경과 똑같이 로그인 한 듯이 재생을 하게 해주는 것이다.

 

  추가로 아래 화면 처럼 마우스 오른쪽 버튼을 눌러서 Replay > Reissue and Edit 메뉴를 선택하면 재생 하면서 날라가는 값을 임의로 편집 할 수 있게 해준다 

 

 

  팝업 창이 뜨면 위쪽 request 쪽의 WebForms 탭에서 lucy 를 tom 으로 바꾼다. 이 후 "Run to Completion" 버튼을 누르면 우리가 조작한 값이 getMoneyInfo.asp 페이지로 넘어간다.

 

 

  이후 response 의 TextView 을 보면 tom 의 잔고인 100,000 원이 보이게 된다.

 

  이 사용자에게 숨겨진 AJAX 호출의 문제 중 하나는 개발자 들이 이 호출이 브라우저 상에서 보여지지 되지 않기 때문에, 서버 단에서 호출되는 것으로 착각하는 경우가 있다는 것이다. 그래서 이 호출을 조작 불가능한 호출이라고 생각하는 경우가 많다. 물론 서버 사이에서 서버끼리 API 를 호출하는 경우도 실제  있지만, 해당 경우도 전달되는 인자가 클라이언트에서 전달되는 값의 영향을 받는 다면, 마찬가지로 인젝션에서 자유로워 질수는 없다(결국 클라이언트 코드의 조작도 넓게 보면 인젝션의 일종이라고 볼수 있다)

 

  요즘은 많은 페이지들이 MVC 타입의 패턴을 채용하고 있다(이 부분이 궁금하면 파이썬 19, 20 교시를 보면 된다). 페이지는 화면에 뿌려주는 기능을 주로 맡고, 대부분의 데이터를 API 형태의 다른 서버에 질의해 가져오게 된다. 문제는 그러한 경우가 대부분 AJAX 코드로 이루어져 있고, 피들러로 보게되면 이렇게 잘 보이고 조작이 가능하다는 것이다. 뒷단에서 호출되는 AJAX 페이지나 API 들이 적절한 사용자 권한 체크를 하지 않는다면(예를 들면 이 종류의 데이터를 해당 사용자에게 전달해 줘도 되는가?), 합법적인(?) 경로를 통해서 시스템의 중요 데이터를 웹을 통해 외부에서 가져갈 수 있게 된다. 이런 합법적인 호출은 하루에도 다른 사용자 들에 의해서 수없이 호출되기 때문에, 운이 좋지 않은 이상 이러한 부분을 모니터링 하기도 무척 힘들다. 실제로 이런 사고들이 종종 나고 있고, 앞으로도 종종 날 것으로 예상된다.

 

 보통 일반적으로 개발자나 테스터는 시스템이 올바르게 돌아가는 측면에 대부분의 노력을 쏟기 때문에, 이렇게 페이지 뒤에서 돌아가는 상황에 대한 자세한 고찰을 하지 않는 면이 많다. 하지만 반대로 어플리케이션 보안을 생각하는 사람들은 이렇게 프로그램의 어두컴컴한 뒷골목의 쓰레기통을 뒤져야 하는 경향이 좀 있다. 

 

  수많은 개발자들이 만들어놓은 코드와 페이지를 소수의 사람들이 이렇게 삿삿이 뒤지는 것은 사실 시간이나 리소스상 어려운 일이고 그래서 열심히 여러 부가적인 활동을 통해서(설계 단계에서의 참여라든지, 소스 리뷰라든지, 가이드 라든지, 개발자 교육이라든지) 개발자의 도움을 받으려고 하는 노력들이 있다. 물론 스캐너 같은 자동화 툴도 일부 도움은 되지만 해당 부분에 대한 유용한 점과 한계에 대해서는 뒤의 다른 시간에 찬찬히 살펴보려고 한다. 

 

 

 

2.5.6 모든것에 대한 믹스 및 정리

  위의 시연한 예제를 기반으로 생각해 보면, 우리가 HTTP 통신을 사용할때 사용하는 폼, 히든 필드, HTML 소스, 자바스크립트, 쿠키 및 referer 등의 헤더 값들은 모두 피들러로 볼수 있었고, 결국 피들러로 수정 하여 조작할 수 있다고 볼 수 있다. 반대로 우리가 처음 1000000원 인출을 막았을 때 ASP 내에 넣었던 서버사이드 코드는 클라이언트 코드에서 영향을 미칠 수는 없다(다만 코드 설계에 취약점이 있다면 교묘하게 조작을 통해 우회만 할수 있을 뿐이다).  공격자는 사이트의 로직에 대해서 외부의 비즈니스와 내부의 볼수 있는 코드(스크립트, HTML)를 분석하여 공격 시나리오를 만들 수 있다. 웹 어플리케이션의 불행한 점 중 하나는 대부분의 방어를 위한 내부 로직들이 사용자 에러를 막기위해 자바스크립트 단에도 똑같이 복사되어 있어 내부 로직을 파악하기에 일반 어플리케이션 보다 좀 편하다는 것이다.  

 

  해당 클라이언트 쪽의 조작을 막을 수 있는 방법 중 하나로는 인자 값의 암호화 같은 수단이 있을텐데, 클라이언트 쪽에서 자바스크립트 등을 이용해 암호화 하는 것은 어차피 공격자도 훤히 볼 수 있는 상황이기 때문에 별 소용이 없고(자바스크립트 난독화를 한다고 해도 능숙한 공격자에게는 사실 시간끌기 퀴즈 풀이 정도 밖에 안된다고 본다. 현실에선 항상 히어로가 빌런보다 강한건 아니니까). 서버 쪽에서 암호화를 해서 클라이언트에 주더라도 난수, 시간 등의 더미 값을 넣어 항상 다른 값으로 만들어 재사용이 힘들게 하고, 상황에 따라 적절히 만료 관리를 해줘야 하는 부분이 있다. 실제로 전달되는 값이 암호화 되었다고 안심하다가 돌 맞는 경우도 있으니, 해당 부분은 설계 부분에서 잘 고려해야 한다. 결국은 클라이언트 코드를 조작하는 부분에 대해 최선을 다해 막는것이 맞겠지만, 한편으로는 어느 정도 마음을 비우고 서버사이드 설계와 사용자 액션에 따른 데이터에 기반한 모니터링에 초점을 맞춰야 하는것 같다. 이 보안적으로 안전한 설계 부분도 나중에 이 글의 마지막 시간 쯤에 함 다뤘음 좋겠다.

 

 

 

2.6 클라이언트 코드의 OWASP TOP 10 에서의 의미

  그럼 웹 클라이언트 코드의 마지막으로 지금 것 다뤄온 클라이언트 코드들이 실제 의미가 있는 요소인지를 보기 위해서 OWASP TOP 10 항목을 한번 보도록 하겠다. OWASP 는 자원자들이 모인 커다란 보안 커뮤니티로 2년에 한번 정도 빈도수와 중요도에 따라 세계에서 가장 많이 일어나는 10개의 웹 취약점을 정리해서 발표한다.

 

[OWASP 2017 한글판]

https://www.owasp.org/images/b/bd/OWASP_Top_10-2017-ko.pdf

 

  

  A1인 Injection 은 3교시에서 설명한 것 같이 외부에서 악의적인 코드를 실행하는 인자가 들어와서 이리저리 돌아다니다가 해당 코드를 해석하는 파서와 만나게 되면 실행되어 문제를 일으키는 SQL 인젝션, CMD 인젝션 등등을 얘기하고, A2 Broken Authentication 은 위에서 본 평문 ID 로 계정 관리를 한다든지, 암호화 하지만 만료 정책이 적절하지 않다든지, 패스워드 찾기에 취약점이 있다든지 하는 인증에 대한 설계가 외부에서 봤을때 악용할 수 있는 부분이 있을 경우를 얘기한다.

 

  A3 Sensitive Data Exposure 는 평문으로 중요 데이터를 전송해 공공 네트워크에서 사용했을때 피들러 비슷한 네트워크 툴들로 패킷이 해석되어 내용이 노출된다든지, 와이파이 등에서 암호화 방식이 부실해 내용을 크랙할 수 있다든지 하는 부분이고, A4 XML External Entities 는 XML 코드안에 이상한게 들어가서 문제가 나는 XSS 의 XML 버전이라고 봐도 될듯 하다.

 

  A5 Broken Access Control 은 허용되지 않은 방식으로 API 나 AJAX 를 호출한다거나, 인자를 바꾼다거나, 권한 없는 사용자로 권한 있는 데이터를 얻으려 한다던가 하는 부분이다. A6 Security Misconfiguration 은 9, 10교시에서 다룰게 될 내용으로, 이런저런 웹서버나 서버, 어플리케이션의 세팅 중, 나름 보안에 적절한 수학의 정석 같은 설정 항목들을 잘못 세팅한 경우이다.

 

  A7 Cross-Site Scripting 은 자바스크립트가 들어와 브라우저에게 영향을 주어 원하는 이득을 얻으려 하는 시도로 동기적, 비동기적으로 일어 날수 있으며(저장되어 영구적이냐 인자로 임시적이냐는 측면도 있다), A8 Insecure Deserialization 은 사실 A5의 하위 부분에 포함되어야 할 항목이라고 생각되는데, JSON 을 인자로 받아들이는 프로그램에서 조작에 대한 방비가 안되 있어 당하는 것이라서, AJAX 가 대중화된 요즘 워낙 자주 일어나는 일이라서 좀더 주의를 기울게 하기 위해서 따로 번호를 분리한게 아닐까 싶다.   

 

  A9 Using Components with Known Vulnerabilities 는 취약점이 있는 컴포넌트를 사용하는 것으로 네이버, 제로보드 같은 웹 게시판의 취약 버전을 패치 안하고 사용한다든지 취약할 가능성이 있는 오픈 소스를 사용하는 것인데, 사실 최신 버전을 사용해도 취약점이 없다는 보장을 100% 할순 없고(모든 최신은 또 조금만 지나면 과거기도 하고, 기능 변경등에 의해서 새로운 취약점이 나올 수도 있고 하니), 요즘 오픈 소스들은 사실 워낙 많은 라이브러리들을 공유해 사용하는 거 같아서, 하나의 핵심적인 모듈만 걸리면 우르르 걸리는 문제가 되는 부분이라서 참 애매한 것 같다. 여튼 상용으로 오랫동안 많은 회사에서 사용되거나, 소스가 공개되어 사람들이 많이 사용하고, 유지보수 하고, 안정화 된 오픈 소스를 사용하는 것이 최대한 현실적인 관점일 것 같다. 뭐 능력이 된다면 전체적으로 소스를 검토하고 쓰면 더 좋을 것 같고 말이다.

 

  A10 Insuffient Loging&Monitoring 은 2017년에 새롭게 나온 항목(내용이 새롭다긴 보다 항목이)으로 보안 팀들이 점점 데이터의 분석에 관심을 많이 기울이고 있다는 것을 증명하는 것 같다. 사용성과 보안은 반비례 한다고 하던 과거의 관점에서, 점점 두 가지를 양립시켜야 살아남을 수 있다는 관점의 변화와, 점점 살펴봐야 할 시스템 들의 다양함에 따른 복잡성과 상이한 데이터가 늘어나고 있는 상황에서는 어쩔 수 없는 부분인 것 같다. 하지만 여전히 개발자 입장에서 보안성에 필요한 데이터라는 주제는 피부에 닿기 힘든 부분이며(보통 개발자가 모니터링에 필요한 데이터를 개발 때 명시적으로 저장해 줘야 쉽게 가져갈 수 있지만, 기능에 필요없는 데이터가 저장되긴 참 힘든 것 같다), 정말 어떤 데이터가 모니터링에 필수적인 데이터인지를 찾아내는 작업도 쉽진 않기 때문에 꽤 어려운 주제 같긴 하다.

 

 

  음 이렇게 보면, 세계에서 제일 중요하다고 하는 10개의 취약점 중 앞에 별이 표시된 7개의 주제가 앞에서 설명한 클라이언트 코드에 대한 이슈라고 볼 수 있다. 그래서 앞에 설명한 간단한 예제들이 꽤 보안적으로 중요한 개념들이라고 말하고 싶다. 물론 자꾸 반복해 말하지만 해당 클라이언트 코드에 대해 제대로 이해하려면 시스템, 프로그램, 네트워크를 데이터의 흐름 관점에서 잘 이해해야 하고, 그 부분을 기반으로 새로 개발에 사용되는 주요한 시스템들을 계속 배워나가며 한다고 생각한다. 게다가 이 부분은 PC 나 모바일 코드로 넘어가게 되면 또 다른 측면의 이해가 필요하게 된다. 하지만 웹이든 OS 환경이든 클라이언트 코드라는 측면에서는 두 개의 타입과 원리는 비슷하다고도 말하고 싶다(물론 관련 기술을 이해할수 있다면 말이다). 그래서 보안 분야를 제대로 이해하고 싶다면 시스템과 데이터를 이해하려고 끝없이 노력해야 하는것 같다(물론 기술 이외에도 가끔 더 현실로 느껴지는 프로세스나 법률적, 관리적 측면에 대한 이해도 균형이 맞춰져야 하겠지만 말이다)

 

 

 

 

3. 모바일, PC 에서의 클라이언트 코드

  이 부분은 간단히 하나의 현실 예제를 보여주면서, 해당 영역에서의 클라이언트 코드의 의미와 가능한 방어 전략에 대해서 생각해 보려고 한다.

 

  앞에서 얘기한 것과 같이 이러한 일반 어플리케이션 레벨에서의 조작은 웹에 비해서 진입 장벽은 높은 것 같다. 일단 HTTP 같이 표준되는 규약으로 투명하게 데이터 흐름이 이루어진 것도 아니고, 피들러 같이 쉽게 특정 주제를 간단히 조작할 수 있는 툴은 드물다. PC 는 거의 디버거나 헥사 에디터의 도움을 통해서 분석 및 조작해야 하는듯 하고, 모바일은 디컴파일러나(보통 공통 가상 환경에서 돌아가는 코드들이 이 부분이 대부분 가능한 것 같다) 마찬가지로 디버거를 사용해서 코드를 해석하거나 조작 해야하는 것 같다. 그래서 기본적으로 어셈블리나 해당 저수준 API 레벨에서의 프로그래밍 지식에 익숙해져 있지 않으면, 관련 툴이 있어도손가락만 빨고 있을 수 밖에 있다. 또 프로그램들은 그러한 분석을 어렵게 하기위해 여러 디버깅 방지 로직이나, 난독화등을 적용하고 있다. 또한 플랫폼에 따라 OS 나 언어도 다양하기도 하지만, 무언가를 자세히 분석하는데에 그렇게 호의적인 환경을 제공하는 것 같지도 않다.

 

  또한 해당 프로그램 및 시스템의 지식은 낮은 레벨의 동작 분석에 집중되어 있기 때문에 파이썬 같은 다방면으로 쓸수 있는 언어보다 일반적인 관점에서 투자한 시간에 비해 유용성이 떨어지기 때문에, 해당 분야의 잡을 가지기 원하는 사람이 아닌 이상 시간을 많이 투여하기가 망설여지는 측면도 있다. 하지만 잘 경험해 놓는다면 남들이 가지지 못한 독특한 다른 시각을 하나 보유하게 되는 것은 부정하기 힘들 듯 싶다.

 

 

3.1 Flex2 살펴보기

  그럼 본론으로 들어가자. 일반적으로 모바일 어플 조작 방지를 위한 가이드를 보면 탈옥, 루팅시 설치되는 파일의 위치를 체크, 시스템 함수를 호출해 사용자의 권한을 체크, 바이너리 해시 크기를 체크, 프로세스를 체크하는 등 공식화 된 방어 방법을 권고 한다. 실제로 은행 등의 중요한 앱들은 이러한 부분들을 의무적으로 적용하고 있다. 여기에서는 해당 방식의 방어 로직들이 정교한 레벨의 클라이언트 코드 방어이긴 하지만, 공격자 입장에서는 완벽한 방어는 아니라는 부분을 주장해 보려 한다.

 

  아래의 어플은 flex2 라는 IOS 탈옥을 했을 경우 시디아라는 탈옥용 웹스토어 같은데서 구입할 수 있는 제한된 어플이다. 현재는 3버전이 나온 것으로 안다. 해당 어플은 탈옥 환경에서만 돌아가며 실행 중인 프로그램이 사용하고 있는 메서드 들을 열람할 수 있으며, 원하는 메서드를 선택 하여 지정한 값을 항상 반환하게 할 수 있다. 

 

  밑의 어플은 중국에서 서비스하는 스트리밍 음악을 들을 수 있는 무료 음원 어플이다. 아직 중국은 음원에 대한 무료 어플들이 일부 있지만 점점 저작권이 강해지는 추세라서(아마 글로벌 기업들이 중국 시장의 미래 수익를 고려하여 중국 정부와 협의해 특정 기간의 유예 특례를 준듯하다), 중국 내부의 IP 에서는 들을 수 있지만 그 외의 IP 에서는 재생을 막는 제약이 걸려있다. 아래 항목은 누군가 해당 부분의 코드 실행을 회피하게 만들어 flex2 에서 바로 다운 받을 수 있는 클라우드 환경에 올려놓은 회피 코드를 다운 받은 것이다. 

 

 

  해당 부분을 클릭하여 들어가게 되면 프로그램 내부에서 사용하는 중국내 IP 체크에 사용하는 여러 메서드 들이 보이게 된다. 물론 해당 패치 파일을 올린 사람은 실제로 디버거나 디컴파일러로 해당 프로그램을 분석해서, IP 체크를 수행한다고 판단되는 메서드 들을 한땀 한땀(이 표현이 맞지 않을까--;) 정리했을 가능성이 높다.  

 

 

  그 중 하나의 항목인 isChinaUser 항목을 클릭해 들어가 보면 "Return Value" 값에 1을 강제로 리턴하도록 설정 하고 있다. 해당 패치를 활성화 한 상태에서 조작하려는 특정 음악 어플을 실행하면 실행 시점에서 중국내 IP 를 체크하는 몇개의 메서드들의 반환 값이 강제되어 버려, 중국 외부에서도 중국 내부 처럼 음악을 들을 수 있게 된다(보안 교육 목적이지만 블로그에 올리기엔 불법적인 사항이라 판단하여 가능한 블라인드 처리를 많이 했다)

 

  실제로 이 어플을 이용해서 은행의 탈옥 감지등을 우회하는 예제도 종종 공유되고 있다. 우회 원리는 위와 마찬가지로 아까 얘기한 탈옥 경로 체크, 프로세스 체크 등의 여러 방어 기법들이 결국은 사용자 스마트폰에 설치되어 있는 모바일 바이너리 안에서 특정 API 를 호출하는 메서드로 구현되기 때문에, 해당 메서드를 정확히 분석하여 탈옥이 안됬다는 거짓된 값만을 리턴한다면 가능한 듯 싶다(제가 이 부분을 잘못 이해하고 있다면 누군가 잘 아시는 분이 교정 댓글 좀 부탁드려요^^) 

 

 

  물론 앞에서 얘기했듯이 해당 코드를 발견하는 부분은 어플리케이션의 난독화, 새로운 보안적 개발언어, 디버그 탐지 코드 등으로 더 어려워 질수는 있지만, 해당 트릭을 풀수 있는 능력을 가진 사람이 관심을 가진다면(금전적 이익을 얻을 수 있는 앱이라면 더 더욱 가능성이 높다고 본다), 파이썬 21교시에서 얘기한것 처럼 그러한 개인의 지식은 소프트웨어나 데이터 형태로 구현되어 일반적인 사람이 쉽게 사용할 수 있는 형태로 제공 될 수도 있다.

 

  결국 방어하는 입장에서는 강력해 보이는 바이너리 기반의 방어 방법이, 환경적 한계로 클라이언트 상의 방어일수 밖에 없기 때문에(OS 단에서 제공해 주는 보안 기능도 외부 공격자에게는 강력하지만, 내부 시스템 어드민 권한을 가진 악의적인 사용자의 조작에는 생각보다 취약할 수 있을 것 같다), 해당 방어 부분에 100% 기대지 말고, 프로그램이 탈옥, 루팅이 되어 많은 부분을 회피가능하다고 가정하고, 해당 상황에서도 최대한 방어할 수 있는 서버 연동 기반의 설계를 하는게 맞을 듯 싶다. 

 

  반면에 사용자 인증 측면에서는 한번 소유기반의 기반한 안정성에 이렇게 의심을 가지게 되면, 많은 민감한 부분들이 과도하게 위험하게 느껴지는 모순이 생기는 듯 싶기도 하다. 하지만 선의의 사용자와 악의의 사용자가 같은 무기를 지닐 수 있을 때는 악의의 사용자에게 촛점을 맞추는게 방어자 입장에서는 맞을 것은 같다. 이런 모호한 상황때문에 OWASP 같은 데서도 점점 데이터에 기반한 모니터링을 강조하는지도 모르겠다. 

 

  또한 웹 쪽과 비교하자면 서버 역할과 비슷한 부분은 OS 환경인것 같다. 백신이 상위권한을 가지고 엑셀을 열기전에 가로채 파일을 검사하는 것과 마찬가지로, 언제든지 OS의 취약점이 있어 어플리케이션의 입출력이 악용되거나 중간 통신을 가로챌 수 있기 때문에 클라이언트 코드 측면의 방어를 너무 믿으면 안 된다고 생각한다. 또한 현재의 많은 어플리케이션의 경우 껍데기는 해당 OS 의 프레임워크를 사용하지만, 백단에서는 실제로는 외부 API 서버와 HTTP 기반의 통신을 하는 경우가 많기 때문에, 피들러 같은 툴을 프록시 서버로 설정해 전송되는 데이터를 살펴보거나 조작할 수 있다. 이런 타입의 어플리케이션의 경우는 웹 프로그램의 관점에서 좀더 깊게 체크 하는 것이 더 맞을 듯 싶다.

 

  앞 부분에서 flex2 어플을 샘플로 보여주며 설명한 행위를 실제 코드 관점에서 설명하고 있는 글이 아래 블로그 글 일듯 싶다.

[IOS Hooking#2(Frida) - Bach's Bolg]

http://bachs.tistory.com/entry/iOS-Hooking2Frida?category=892887

 

 

 

 

4. 마무리 하면서

  여기 까지 오면서 웹 코드로 예시를 들면서, 클라이언트 코드에 대해 개인적으로 가지고 있는 생각을 전달하려 했는데, 얼마나 공감을 얻었을지 모르겠다. 개인적으로는 웹이나 어플리케이션이나 기술의 결이 다를 뿐이지 프로그래밍 보안이란 관점에서는 그리 큰 차이는 없는 듯 싶다. 모든 걸 깊게 잘 알고 있는 건 아니기 때문에 이런 종류의 의견엔 크게  자신은 없기만, 공부를 시작하는 사람들 한테는 이런 부분에 대한 소개도 의미가 있진 않을까 싶어서 글로 남겨 본다.

 

  개인적으로 점점 시간이 지날 수록 보안 뿐만 아니라 IT 전체에 있는 많은 분야에서 하는 일들이  결국은 다른 관점에서 시스템과 데이터를 이해하는 본질적으로는 같은 업무가 아닌가 하는 생각이 들곤 한다. 그 덕분에 왠지 알고 있던 사실들이 갑자기 무의미 해지기도 하고, 쉽게 생각했던 분야들이 어렵게 느껴지거나 그 반대의 일들도 종종 일어나고 있다.

 

 

2018.12.16 by 자유로운설탕
cs

 

  

 

posted by 자유로운설탕