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

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

Notice

2019. 3. 24. 21:38 보안

  API 는 사실 보안적으로 봤을때(사실 프로그램적으로 봤을 때도 비슷한 상황인거 같지만) 일반적인 다른 프로그램 요소와 특별히 다르진 않다. 다만 일반적으로 웹에서는 AJAX 와 연관되어 돌아가기 때문에 피들러 같은 특수한 툴로 살펴보지 않는 이상 호출이 된다는 부분조차 인지하지 못할 수 있어서 개발자의 경험이 없으면 체크를 놓치는 요소라고 보는게 더 맞을 거 같다. 어떤 측면에서는 일반적인 어플리케이션의 인터페이스보다는 표준화(보통 XML 이나 JSON 으로)가 많이 된 요소이기 때문에, 더 투명하게 검증 할 수 있는 측면도 있어 보인다.

 

 

[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

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

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 설정, 패치, 하드닝

10. 설계 문제

11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터

 

 

 

 

1. 들어가면서

  그럼 API 는 무엇일까? 사실 이 얘기는 파이썬 글 10교시 Whois API 편을 보면 이미 필요한 부분에 대해서 얘기한듯 싶다. 샘플도 API 가  무언지를 보여주는 토큰 까지 있는 좋은 샘플이라고 생각하기 때문에 먼저 해당 글을 보고 이어서 보길 바란다. 여기서는 해당 글을 보고 왔다고 생각하고 얘기를 이어간다.

 

 

  거기에서 나왔던 얘기같이 API는 머리 꼬리를 띈 생선의 몸통 같이 데이터만이 왔다 갔다하는 프로그램이다. 사실 이 API는 소프트웨어의 시작 부터 계속해서 있어 왔다. API 의 약자 자체가 Application Programming Interface 이기 때문에, 어플리케이션에서 무언가를 호출해서(예전엔 주로 Windows API 등 OS 에서 제공하는 함수들-선을 그린다던가, 창을 띄운다던가, 화면에 글자를 뿌린다던가... 이였지만), 원하는 일을 하거나, 결과 값을 받아오는 형식을 말한다.

 

 

  인터페이스는 두 개의 다른 존재가 연결되는 방식을 얘기한다고 볼수 있는데, 우리 현실과 연계를 지으면 쉽게 비슷한 부분이 많다. 예를 들어 우리는 여러가지 인터페이스를 통해 타인 또는 타 물체와 소통 한다. 편의점에 가면 물건을 사기위해서 카드나, 현금을 주고 포스기로 결제한 후 물건을 담아오게 되고, 친구를 만나면 대화를 나누거나, 음식을 먹거나, 술을 먹거나 하면서 우정을 나누고, 은행에 가면 은행에 정해진 규칙에 따라서 상담원과 은행 업무를 본다.

 

  우리가 아이폰이나 안드로이드 폰을 사용하는 것도 폰의 인터페이스(터치, 드래그 및 여러 메뉴에 의한 사용자 인터페이스)를 이용한다고 보면 된다. 앞의 Whois API 예제에서도 해당 사이트에서 설계한 인터페이스 대로 데이터를 보내고 받아야 하는 것을 볼 수 있다. 우리가 생각하는 예의란 부분도 마찬가지여서, 예의에 대한 가치관이 다른(인터페이스가 다른) 두 사람이 만나면 관계가 엉망이 되어버리기도 한다. 

 

  프로그램 언어를 배우는 초입 부분에서 API 와 비슷한 부분을 볼수 있게 되는데, 해당 부분이 바로 함수나 메서드이다. 함수를 보면 정해진 타입의 입력 값을 넣어서, 정해진 타입의 출력 값을 얻게 된다. 파이썬의 모듈도 마찬가지로 해당 모듈에 정해진 방식대로 사용해야 한다. 어떻게 보면 프로그래밍 영역 자체가 수많은 인터페이스들간의 커뮤니케이션으로 이루어져 졌다고 봐도 될것 같다.

 

  

  추가로 API 에는 하나의 더 중요한 부분이 있는데, 호출 권한에 대한 인증 이다. 해당 부분은 3가지  정도의 측면이 있는데, 성능 및 데이터의 중요도, 모니터링 이다.

 

  첫째로 아무리 성능이 좋은 서버가 지원하는 상태라도 많은 사람들이 익명으로 계속 호출하게 된다면 병목 문제가 생길 수 있다. 이 부분은 크롤링 툴 등에 의해 페이지를 자주 호출 하는 경우에도 비슷한 문제가 생길수 있지만 API 는 좀더 특정한 목적을 가지고 내부에서 조회한 데이터를 전달하는 목적을 가지고 있기 때문에 좀더 민감하지 않을까 싶다. 

 

  둘째는 데이터의 중요도 인데, 특정 개인에게만 전달되야 되는 데이터가 다른 사람에서 전달되거나, 특정 권한을 가진 경우에만 전달 가능한 데이터가 아무에게나 보내지는 것은 바람직하지 못할 것이다.

 

  세번째는 모니터링 및 통제다. 현재 데이터를 가져가는 주체가 누구인지 특정할 수 있다면, 데이터가 어디로 나가고 있는지(호출하는 서버나 IP 정보만으로는 불충분하다), 얼만큼 데이터를 제공해야 하는지를 컨트롤 할수 없다.

 

 

 

 

2. 간단한 API 만들어 보기

  앞의 Whois API 를 봄으로서 API 를 사용하는 측면은 살펴봤으니 이번엔 한번 예제를 만들어 보자. 파이썬의 flask 모듈을 이용해 API 를 호출해 데이터를 가져오는 기능을 만들어 보려고 한다. 밑의 글들과 같은 REST API 샘플을 만들수도 있겠지만, 원리를 설명하기에는 간단한 쪽이 좀더 날거 같아서, 앞의 AJAX 예제와 비슷하게 만들었다.

 

[파이썬 Flask 로 간단한 REST API 작성하기 - readbetweentheline 님 블로그]

https://medium.com/@feedbots/python-flask-%EB%A1%9C-%EA%B0%84%EB%8B%A8%ED%95%9C-rest-api-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0-60a29a9ebd8c

 

[Designing a RESTful API using flask restful - miguelgrinberg.com]

https://blog.miguelgrinberg.com/post/designing-a-restful-api-using-flask-restful

 

 

2.1 간단한 API 호출 예제

  우선 파이썬 코드 쪽을 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from flask import Flask, render_template, request, jsonify
 
# flask 웹서버를 실행 합니다.
app = Flask(__name__)
 
@app.route("/call_api", methods=['GET'])
def test_search():
    return render_template('call_api.html')
 
 
@app.route("/getSecret", methods=['GET'])
def getSecret():
    name = request.args.get('myname')
    secret = name + "'s secret is A" 
    return jsonify({'var1': secret})
 
# 이 웹서버는 127.0.0.1 주소를 가지면 포트 5000번에 동작하며, 에러를 자세히 표시합니다 
if __name__ == "__main__":
    app.run(host='127.0.0.1',port=5000,debug=True)
cs

[flask_api.py]

 

  간단히 코드를 보면 call_api 가 기본 페이지이고, getSecret 가 API 역할을 한다. 해당 API 는 인자로 myname 을 받아서 해당 이름의 비밀을 알려준다(원래는 회원이 맞는지, 해당 회원의 비밀이 무언지를 아마 어딘가에 조회해야 할테지만 했다고 친다...). UTF-8 인코딩으로 c:\python\code 폴더에 flask_api.py 라고 저장하자.

 

 

  다음으로 템플릿 파일을 만든다.

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
<html>
  <head>
    <script src="http://code.jquery.com/jquery-3.3.1.min.js" ></script>
      <title>Call API</title>
      
    <script type="text/javascript">
      $(document).ready(function(){
 
        $("[id^=checkData]").click(function() {
          var myname = "tom"
           
          $.ajax({
            url: "/getSecret",
            data: { myname : myname },
            contentType: 'application/json;charset=UTF-8',
            success: function(data){
              $("#spanName").html(data.var1);
            }
          });
        });
        
      });
 
    </script>
  </head>
  <body>
    <table>
      <tr>        
        <td><input type="button" id="checkData" value="체크"></td>
      </tr>
      <tr>               
        <td><span id="spanName"></span></td>
      <tr>
    </table>
  </body>
</html>
cs

[call_api.html]

 

  내용을 보면 "checkData" 라는 버튼이 하나 있고, 해당 버튼을 누르게 되면 AJAX 기능을 이용해서 getSecret API 에 myname 을 "tom" 이라는 GET 인자를 포함해서 호출 한다. 역시 UTF-8 인코딩으로 c:\python\code\templates 폴더에 call_api.html 이란 이름으로 저장한다.

 

 

  아래와 같이 플라스크 사이트를 실행 후 주소창에, http://localhost:5000/call_api 라고 입력 후, "체크" 버튼을 클릭한다. 밑과 같이 tom 의 비밀이 API 를 통해 전달되어 나오게 된다(이해가 안되는 경우는 앞에 소개한 피들러로 함 살펴봐도 좋다)

c:\Python\code>python flask_api.py

 

 

  그럼 위의 getSecret API 가 가질 수 있는 문제점은 무엇일까? 앞에서 얘기했듯이 아무나 해당 API 를 호출하면 문제가 생길 수 있다. 웹에서는 쿠키에 저장된 암호화된 인증 값을 이용해서 신원을 확인 하는데, API 의 경우는 HTTP 방식으로 ID/PASS 를 통해 인증할수도 있지만, 보통 호출하는 주체가 자동화된 프로그램일 경우가 많기 때문에 일일히 패스워드를 입력하여 넘기기는 어렵다(물론 어딘가에 저장해 두었다 프로그램이 넘겨도 되긴 한다). 그래서 좀더 자주 쓰이는 방식이 토큰(Token)을 사용하는 것이라고 본다(해당 토큰 개념은 스크립트 문제에서 얘기한 CSRF 의 방어에도 쓰인다).

 

 

 

2.2 토큰 추가 예제

  그럼 간단한 토큰을 사용하는 예제를 만들어 보자. 똑같이 플라스크 쪽 코드 부터 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask import Flask, render_template, request, jsonify
 
# flask 웹서버를 실행 합니다.
app = Flask(__name__)
 
@app.route("/call_api_token", methods=['GET'])
def test_search():
    return render_template('call_api_token.html')
 
 
@app.route("/getSecret", methods=['GET'])
def getSecret():
    name = request.args.get('myname')
    token = request.args.get('mytoken')
    if token == "@$ABC77":
       secret = name + "'s secret is A"
    else:
       secret = "Need valid token!" 
    return jsonify({'var1': secret})
 
# 이 웹서버는 127.0.0.1 주소를 가지면 포트 5000번에 동작하며, 에러를 자세히 표시합니다 
if __name__ == "__main__":
    app.run(host='127.0.0.1',port=5000,debug=True)
cs

[flask_api_token.py]

 

  내용을 보게되면 앞의 코드와 거의 비슷하지만, mytoken 이라는 사용자가 보낸 토큰을 확인하여, "@$ABC77" 값이 아니라면 "Need valid token!"라고 에러메시지를 대신 보낸다. UTF-8 인코딩으로 c:\python\code 폴더에 flask_api_token.py 라고 저장하자.

 

 

  마찬가지로 템플릿 코드 쪽을 보면, API 를 호출하는 인자에 mytoken 값이 추가됬다. 일부러 토큰은 앞에 "X" 가 추가로 들어가 값이 틀리게 했다.

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
<html>
  <head>
    <script src="http://code.jquery.com/jquery-3.3.1.min.js" ></script>
      <title>Call API</title>
      
    <script type="text/javascript">
      $(document).ready(function(){
 
        $("[id^=checkData]").click(function() {
          var myname = "tom"
          var mytoken = "X@$ABC77"
           
          $.ajax({
            url: "/getSecret",
            data: { myname : myname },
            contentType: 'application/json;charset=UTF-8',
            success: function(data){
              $("#spanName").html(data.var1);
            }
          });
        });
        
      });
 
    </script>
  </head>
  <body>
    <table>
      <tr>        
        <td><input type="button" id="checkData" value="체크"></td>
      </tr>
      <tr>               
        <td><span id="spanName"></span></td>
      <tr>
    </table>
  </body>
</html>
 
cs

[call_api_token.html]

 

  UTF-8 인코딩으로 c:\python\code\templates 폴더에 call_api.html 이란 이름으로 저장한다.

 

  아래와 같이 플라스크 서버를 실행 후 주소창에, http://localhost:5000/call_api 라고 입력 후, "체크" 버튼을 클릭한다. 의도했던 대로 토큰 값이 틀리다고 에러가 난다.

c:\Python\code>python flask_api.py

 

 

 

3. 어플리케이션 보안 측면의 API

  그럼 어플리케이션 보안 측면에서 API 를 어떻게 봐야하는지를 생각해 보자. 

 

 

  첫번째로 앞의 예제에서 본 것처럼 요즘의 API 들은 사실 엄청 단순한 모양을 가지고 있다(약간 인자들이 암호같은 Windows API 등에 비해서 말이다--;). 사실 하는 일도 단순하다. 프로그램의 함수가 입력을 받거나 받지 않을 수도 있고, 출력을 주거나 주지 않을 수 있듯이 API 도 마찬가지 이다.

 

  입력을 받아 단순히 로그로 저장할 수도 있고, 구입 목록 같이 입력에 대한 특정한 정보를 줄수도 있고, 빌드나 다른 API 의 호출 등의 특정한 액션을 할수도 있다. 그 부분은 API 가 어떤 기능을 하느냐에 따라서 달라지는 일이라서 블랙박스적으로 보면된다고 생각될수도 있지만, 보안적으로 봤을때는 한가지 중요한 점이 있다. 그것은 API 의 입력으로 들어가는 값들이 API 내부의 로직에 영향을 미칠수 있다는 것이다.

 

  이렇게되면 앞에서 얘기했던 클라이언트 코드와 인젝션에 대한 이야기로 다시 주제가 돌아가게 된다. API 가 사용하는 인자 중 나쁜 영향을 미칠수 있는 인자를 파악하는 방법은 API 내부의 동작을 이해해야하는 문제가 되버린다. 

 

 

  두번째는 앞의 예제에서 봤던 토큰이다. 이 토큰이 변조되거나, 바꿔치기 되는 문제에 대해서 웹에서의 세션관리와 동일한 종류의 문제가 발생되게 된다. 추가로 어떻게 토큰을 최초 사용자에게 발급하고, 토큰으로 인한 자격의 유지 기간에 대해서도 고민해 봐야한다. 프로그램이 주로 호출하는 API 의 특성상 보통 한번 발급한 토큰을 특별한 제약없이 영구히 사용하는 경우도 많은듯 하지만, 관리의 실수로 토큰이 노출되었을 경우 골치아픈 문제가 발생할수 있으므로 여러 측면에서 리스크를 검토해야 한다(마치 암호화페에서 열쇠에 해당하는 개인키가 도난당하는 것 같은 일이 생길 수도 있다).

 

 

  세번째로 전송되는 데이터를 평문으로 보내도 될까 하는 부분이다. HTTPS 로 감싸 보내는 방법도 있겠지만 해당 부분은 보내는 쪽의 의도적인 조작이나 중간자 공격에 100% 안전하지는 않을 것이기 때문에, 아예 공개키/개인키 등을 사용해서 암호화 하여 보내는게 맞을 수도 있다. 해당 부분은 법적인 부분과, 데이터의 중요성, 여러 위험 요소를 고려해서 선택해야하는 부분인것 같다.

 

 

  마지막으로 OWASP 에서도 중요하게 강조하고 있는 모니터링 부분이다. 중요한 API 일수록 해당 토큰을 발급한 쪽에서 필요한 만큼 적절하게 API 를 호출 하는지를 체크해야 하는 경우가 있게된다. 해당 정보가 법적으로 보호되어야 하는 민감한 정보라면 더더욱 그렇다. 해당 부분은 어플리케이션 쪽에서 토큰 및 여러 클라이언트 쪽 정보를 기반으로 기준을 정해 모니터링을 해야되는 문제이다.

 

 

 

4. 마무리 하면서

  조금 싱거운 감이 있지만 개인적으로 API 는 일반 어플리케이션과 별로 다르게 볼 필요는 없다고 생각하기 때문에 여기서 마무리를 하려한다. API 는 전송하는 데이터와 토큰이라는 관점에서 보게 되면, 일반적인 어플리케이션과 특별히 다르지는 않다. 다만 해당 형태의 구조를 명확히 이해하지 못한다면 미지의 영역이 되어 전혀 다른 것처럼 보일 수가 있다. 

 

  요즘처럼 API 로 조각조각 나누어진 어플리케이션을 테스트 하는것은, 인터페이스들이 늘어나는 결과를 가져오기 때문에 테스트를 해야되는 입장으로는 꽤 귀찮은 일이긴 하다. 2교시에서 얘기했던 것처럼 소스 레벨의 관점에서 문제를 바라보는 것도 나쁘진 않을 거라고 생각한다.

 

 

 

2019.3.31 by 자유로운설탕
cs

 

 

posted by 자유로운설탕
2019. 3. 2. 22:32 보안

  사실 스크립트 문제는 명시적으로 클라이언트나 서버에서 돌아가는 모든 스크립트가 문제이긴 하지만, 여기서는 클라이언트 쪽(정확히는 브라우저)의 자바스크립트 만을 대상으로 얘기하려 한다.

 

  일반적으로 자바스크립트 문제로 얘기되어 지는 XSS(Cross Site Script)는 인젝션 문제와 기본적으로 유사한 형태라고 생각된다. 다만 브라우저와 밀접하게 움직이는 클라이언트 코드인 자바스크립트의 특성 때문에, 최악의 상황에는 브라우저를 뒤에서 조정한다고도 할 수 있는 비동기 루프까지 일으킬 수 있기 때문에 더 특별히 다뤄지는게 아닐까도 싶다. 여튼 상황에 따라 단발성이 아닌 살아 있는 것 같이 사용자를 계속 괴롭힐 수 있는(물론 브라우저를 끄기 전까지에 한정되 있지만) 특이한 요소이다.

 

 

 

[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

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

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 설정, 패치, 하드닝

10. 설계 문제

11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터

 

 

 

1. 들어가면서

  자바스크립트 문제에 영향을 줄 수 있는 요소들은 브라우저, 프레임워크 특성, 요구사항 정도 일거 같다.

 

  먼저 브라우저는 워낙 다양한 브라우저가 존재하고, 해당 브라우저 마다 XSS 시도에 대해서 서로 다른 디폴트 대응을 하고 있다(자바스크립트를 이용해 웹 프로그램을 만들어 보면 브라우저 호환성이 얼마나 속을 썩이는 지를 알수 있다). 어떤 브라우저는 모든 XSS 공격 시도에 대해서 허용하고 있고, 어떤 브라우저는 특정의 XSS 공격 시도에 대해 명시적으로 방어해 주기도 한다. 다만 상황에 따라 특정 자바스크립트 코드의 실행이 XSS 시도인지 브라우저 쪽에서 판단하는 것은 참 애매한 일이다. 잘못하면 정상적인 자바 스크립트 코드의 실행을 막을 수도 있기 때문이다. 해당 부분은 관련된 코드를 직접 보면서 한번 생각해 보도록 하자.

 

 

  두 번째로 프레임워크 특성 부분이다. 보통 요즘의 프로그래밍 언어들은 웹프로그래밍을 위해 제공하는 웹프레임워크 들이 있다. 해당 프레임워크마다 권고하는 설계가 있는데, 해당 설계를 따라 프로그램을 만들었을때, 자동으로 어디까지 XSS 로 의심되는 시도가 막히게 되느냐에 대한 문제가 있다. 어떤 언어는 해당 부분을 특정한 라이브러를 제공해서 개발자에게 명시적으로 해당 라이브러리를 사용해 방어코드를 넣도록 하는 경우도 있을테고, 어떤 경우는 프레임워크 자체에서 모든 전달된 변수에 대해 해당 방어 매커니즘을 자동으로 반영하는 경우도 있을 것이다. 그런데 여기서도 마찬가지로 지나친 안전을 보장하게 되면 모든 코드를 XSS 위험요소가 있는 것같이 다루게 되어 기능의 유연성이 떨어지는 문제가 생기게 될수도 있다. 이 부분도 뒤에서 플라스크 코드를 통해서 살펴보도록 하자. 

 

 

  마지막으로 요구사항 측면도 많은 영향을 미칠 수 있다. 예를 들어 게시판을 하나 만든다고 생각해 보자. 해당 게시판의 컨텐츠의 구성요소가 텍스트 기반이냐, HTML 기반이냐, 자바스크립트도 포함하느냐에 따라서 XSS 방어의 구현 난이도는 많이 차이가 나게 된다.

 

  텍스트 기반인 경우는 XSS 로 악용이 될 수 있는 모든 이상한 문자들을 HTML 인코딩 또는 제거를 통해서 나름 안전하게 데이터를 필터링 할 수 있다. 하지만 블로그와 같은 HTML 코드의 허용이 필요해버리면, 어떤 HTML 태그나 요소는 허용하고, 어떤 것은 제한 시킬지에 대해서 정교한 정책의 설계와 보안 테스트가 되어야 한다. 게다가 해당 부분은 위에 얘기한 브라우저 별로 검토되어야 하며, 흔히 여러 환경에서의 호환 테스트가 그렇듯이 그렇게 될경우 테스트 케이스의 수는 컨트롤 하기 힘들 정도로 계속 증식되게 된다.

 

  아래 링크에서 자바스크립트 체크 회피를 시도하는 XSS 공격에 악용 될수 있는 HTML 태그나 이벤트, 쿠키, 스타일 태그 등의 예제를 보여주고 있다. 보기만 해도 머리가 아파지지 않나 싶다. 만약 요구사항에 관리자들에게 필요하다며 게시판 작성시 자바스크립트 기능도 넣어달라고 해버리면 머리는 더 아파진다. 이런 경우 자바스크립트 방어를 포기하고 사용자 권한 관리 쪽에 포커스를 두거나, 커스텀한 편집 마법사를 만들어서, 사용가능한 자바스크립트 코드 기능을 제한하면서, 사용자가 직접 자바스크립트 편집을 하지 않게 하고 실제 코드의 생성 및 수정 처리는 서버단에서 하게 하는 방법도 있을 듯 싶다(공수는 엄청 들겠지만 말이다).

 

[XSS Filter Evasion Check Sheet - OWASP]

https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet

 

 

  특정 사이트 들에서는 BBCODE 라는 게시판용 커스텀 스크립트 언어를 쓴다고도 하지만, 아무래도 일반성 부분에서의 HTML 호환성을 포기하는 대가가 있을 것은 같다. 그리고 아마 저런 새로운 스크립트가 유명해지고, 자바스크립트 만큼 기능성이 확장된다면 역시 복잡도에 기반한 악용을 하는 시도는 늘어날 것이기 때문에 현실에 완벽한 해답은 없어 보인다.

 

[BBCode - WIKI]

https://ko.wikipedia.org/wiki/BBCode

 

 

 

 

2. 자바스크립트 공격의 종류

  보통 웹을 찾아보면 자바스크립트 공격은 3가지 종류로 나눌 수 있게 되는데, Reflected(반사), Stored(저장), DOM Based(돔 기반) 정도로 나누어지고, 부록으로 CSRF(Cross Site Request Forgery) 같이 연관된 항목이 있다.

 

  개인적으로 Reflected 와 DOM Based XSS 는 비슷한 종류라고 보고 싶다. 왜냐하면 두 항목의 차이는 브라우저 외부에서 입력된 스크립트 조각이 프로그래밍 언어의 출력 명령(PHP의 echo 같은)을 통해 화면에 표시되느냐, HTML 코드내에 포함된(또는 외부 링크일수도 있지만) 기존 자바스크립트에 의해서 표시되느냐의 차이일 뿐이기 때문이다. 여튼 두 가지는 클라이언트 코드, 인젝션 시간에 얘기한 브라우저 파서가 외부에서 들어온 자바스크립트를 어떻게 처리하느냐의 측면이라고 보면 될 듯 싶다.

 

 Stored 의 경우는 보통 Reflected 보다 심각한 문제라고 보는데 해당 부분에는 2가지 측면이 있다. 첫 째는 보통 해당 데이터가 게시판의 게시글 같이 DB에 저장된 상태로 보관되어 게시글을 보는 불특정 다수에게 노출이 된다는 부분이다. 둘째는 사실 이 글에서 얘기하고 싶은 요점이기도 한데 XSS 를 적극적으로 막아주는 브라우저도 이 타입의 스크립트에 대해서는 쉽게 선뜻 악용의 여부를 판단하기 힘들어 소극적이라는 부분이다. 왜냐하면 이 타입은 사실 Stored 란 이름 때문에 저장된 타입이라는 관점에서 보는 일이 많지만, 프로그램의 관점에서 보면 프로그램 내부에서 생성한 자바스크립트를 브라우저가 막을수 있겠냐는 딜레마의 측면이 있기 때문이다.

 

  마지막으로 CSRF 는 이름만 보면 난해해 보이지만, Forgery 가 '위조'라는 의미므로 요청(Request)을 위조한다는 의미라서 보통 인증에 사용하는 쿠키를 보유한 사용자가 모르는 사이에 XSS 공격을 이용해서, 대상 쿠키를 만들어낸 사이트의 주요 URL을 호출해 원하는 기능을 실행(예를 들어 패스워드 변경, 주소지 변경)하게 하는 요소로서 사이트의 주요한 기능에 대해서 쿠키 이외의 제대로된 추가 인증을 하고 있느냐가 문제가 되는 부분이라서, 사실 XSS 와는 관계가 있으면서도 관계가 없는 부분이기도 하다.

 

  그럼 주요 요소인 Reflected 와 Stored 공격에 대해서 브라우저들이 어떻게 반응을 하고 있는지 코드와 함께 살펴 보도록 하자.

 

 

 

 

3. Reflected vs Stored 공격 살펴보기

  아무래도 옛날 웹 프로그램 언어들이 이런 보안 부분에 아무 관심이 없는 편이므로, 3교시에 봤던 ASP 코드로 관련 코드를 만들어 본다.

 

 

 

3.1 POC 코드 만들고 엣지브라우저 또는 IE 에서 실행해보기

   해당 코드는 아래와 같다. 간단한 코드로 하나의 페이지에서 폼 전송과, 받기가 동시에 이루어진다. 위 쪽으로 보면 strFromGet 이란 변수는 폼에서 전송된 fromGet 이름을 가진 사용자가 입력한 input 박스 내용을 받고, strFromDatabase 변수는 원래는 데이터베이스를 갔다와야 하지만, 어차피 쿼리 결과를 변수에 담는거니 저런 스크립트 코드 데이터가 데이터베이스 안에 저장되어 있었다고 가정하자(이 부분이 앞서 얘기한 프로그램 내부에서 생성한 데이터 측면이 된다). 하단을 보면 Response.Write(PHP 의 echo 와 동일하다고 보면 된다)를 이용해서 해당 두 변수를 웹 화면에 뿌려준다.

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
<%
    ' 폼 값 받기
    strFromGet = request("fromGet")
    strFromDatabase = "<script>alert('stored xss run')</script>"
%>
 
<html>
   <head>
      <title>XSS 시연</title>
   </head>
 
<body>
     <form name = "Custom" method="get" action="show_xss.asp">
        <table width = 500>
            <tr>        
                <td> ID </td>
                <td> <INPUT maxlength="100" name="fromGet" size="50" type="text"> </td>
                <td> <input name=button type=submit value="ID 보내기"> </td>
            </tr>      
        </table>   
    </form>      
    
    <hr>
    <br><b>결과</b>
    <br><br>
        
<%
    ' 두 개의 변수 표시    
    Response.Write "Reflected XSS : " & strFromGet & "<br><br>"
    Response.Write "Stored XSS : " & strFromDatabase & "<br><br>"
%>
 
</body>
</html>
cs

[show_xss.asp]

 

  그럼 위의 코드를 c:\inetpub\wwwroot 에 ANSI 포맷으로 show_xss.asp 라고 저장한다. 이 후 우선 엣지브라우저를 열어 http://localhost/show_xss.asp 라고 입력한다(윈도우 10 기준이고, IE11 이나 엣지 브라우저 모두 같은 결과를 가져오니 아무거나 해도 된다).

 

 

  처음에 뜨는 Stored XSS Run 이라는 alert 은 일단 무시하고, ID 입력 창에 "<script>alert('reflected xss run')</script>" 라고 적은 후,  "ID 보내기" 버튼을 누른다.

 

 

 그렇게 되면 처음에 입력한 값을 웹 화면에 표시했기 때문에 페이지에서 자바스크립트가 실행되어 "reflected xss run" 이라는 alert 이 뜨고, 확인을 누르면 다시 "stored xss run" 이라는 alert 이 뜬다.

 

 

 

  F12 를 눌러 개발자 도구에서 페이지 소스를 보면 아래와 같이 2개의 alert 을 띄우는 스크립트 코드가 내부안에 들어가 있다.

 

 

 

3.2 크롬에서 실행해 보기 

  이번엔 IE 가 아닌 크롬에서 같은 페이지를 열어 시연해 보자. 현재 크롬 최신 버전은 72 버전이다. 크롬에서 실행해 보면 처음 "stored xss run" 얼랏이 뜨는건 마찬가지지만, 입력 박스에 스크립트를 넣어 전송하게 되면 아래와 같이 XSS_AUDITOR 에 의해 에러가 났다는 페이지가 뜨면서 실행이 막히게 된다.

 

 

  마우스 오른쪽 버튼을 클릭해 소스 보기를 하면 크롬이 페이지를 막은 부분의 스크립트가 표시된다. 크롬이 자체적으로 외부에서 들어온 악성 스크립트가 실행된다고 판단해서 실행을 막은 것이다.

 

 

 

3.3 브라우저 입장에서 생각해 보기.

  자 그럼 브라우저 입장에서 한번 해당 코드를 생각해 보자. 우선 위의 코드 실행 결과를 보고 어떤 브라우저가 더 안전하구나 생각하는 건 사실 좀 애매한 관점이다. 그것보다는 XSS 보안에 대해서 어떤 접근법을 취하느냐에 대해 살펴보는게 더 정확할 듯 하다.

 

 

  Reflected XSS 경우 브라우저는 해당 현상이 일어나고 있다는 것을 누구 보다 잘 알고 있다. 왜냐하면 해당 스크립트 코드 조각을 보낸 것이 바로 자기 자신이고(사용자가 입력한 값을 브라우저가 전달했으니), 응답 받은 코드에 보낸 코드 조각이 똑같이 있으므로(일단 브라우저는 요청된 결과를 소스로 받아서 이리저리 해석하니까 처음 받는건 스크립트가 포함된 HTML 소스이다), 자기가 보낸 코드가 반사(reflected) 되었다는 사실을 알수 있다(사실 다른 시간에 다시 얘기하겠지만 웹 취약점 스캐너의 주요 원리도 대충 이런 식이다).

 

  그래서 refelected XSS 의 경우는 엣지나 크롬 모두 잘 인지하고 있지만, 엣지는 해당 부분은 통과시켜주고, 크롬은 막고 있다.

 

  ※ 엣지 브라우저에 XSS 필터가 제거된 것은 2018년 7월 인거 같다. 자세한 것은 아래 글 같은 부분을 참고하면 될 듯 하다. Content Security Policy 로 보호가 대체 될수 있다고 얘기하고, 여기서도 XSS 필터가 reflected XSS 만 막을 수 있다는 부분을 얘기하고 있다.

[edge to remove xss auditor - scotthelme 사이트]

https://scotthelme.co.uk/edge-to-remove-xss-auditor/

 

 

  문제는 XSS 취약점 중 영향이 크다고 얘기되고, 실제로 영향이 큰 경우는 stroed XSS 라는 것이다. 위의 예제에서 봤듯이 두 브라우저 모두 해당 코드를 실행 시켜준 이유는, 이게 해킹이 된 내부 데이터로 부터 날라온건지, 원래 프로그램이 실행시키기를 원해 만든 정상적인 스크립트 인지를 판단하는 것이 종합적인 배경지식이 없다면 현실적으로 불가능 하다는 점이다(저 위의 Content Security Policy 라는 나름 이런 부분을 정의하여 접근을 구분하겠다는 것으로 보이긴 하지만, 모든 브라우저가 지원해야 되고, 페이지 마다 권한을 정의해야 되니 설계가 많이 복잡해지긴 할 듯 싶다.

 

  사실 그런 종합적인 배경지식은 페이지를 설계한 기획이나 개발자가 제일 잘 알고 있다. 그래서 어느 정도 브라우저에도 의존할 수도 있고, 해당 스크립트 코드가 들어간 링크를 누군가 명시적으로 클릭하기 전에는(물론 인지 못하고 클릭할 가능성이 높지만) 일어날 수 없는 reflected XSS 와는 달리 stored XSS 는 100% 의도적인 보안 설계로 풀어야 한다. 

 

 

 

 

4. flask 방어 코드 예제 보기

   XSS 필터를 이용한 예제도 괜찮겠지만, flask 코드에서 자동 방어해주는 부분을 한번 보도록 하자. 정확히 얘기하면 플라스크에서 사용하는 jinja2 템플릿에서 지원해준다고 보면 될것 같다. 역시 간단한 코드를 보도록 하겠다(flask 동작에 대해 잘 모르겠으면 파이썬 19교시를 보고 오자).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Flask, make_response, render_template
 
# flask 웹서버를 실행 합니다.
app = Flask(__name__)
 
@app.route("/xss", methods=['GET'])
def xss():
 
    xss_string = "<script>alert('flask reflected xss run')</script>"
    return render_template('xss.html', name = xss_string)
 
 
# 이 웹서버는 127.0.0.1 주소를 가지면 포트 5000번에 동작하며, 에러를 자세히 표시합니다 
if __name__ == "__main__":
    app.run(host='127.0.0.1',port=5000,debug=True)
cs

[flask_xss.py]

 

  위의 코드를 c:\python\code 에 UTF-8 포맷으로 flask_xss.py 이름으로 저장한다. 

 

 

  이후 템플릿 파일을 만들어 보자. 한줄짜리 간단한 html 이다.

1
<td>{{ name }}</td>
cs

 

  위의 코드를 c:\python\code\templates 폴더에 UTF-8 포맷으로 xss.html 로 저장한다. 이후 플라스크 웹서버를 실행 시킨다.

 

c:\Python\code>python flask_xss.py
 * Serving Flask app "flask_xss" (lazy loading)
 * Running on
http://127.0.0.1:5000/ (Press CTRL+C to quit)

 

  이후 엣지 브라우에서 http://localhost:5000/xss 주소를 열어본다. 아래와 같이 스크립트가 실행되지 않고, 일반 문자로 표시된다. html escape 처리가 된 것이다.

 

 

  실제 이스케이프 처리가 된 것을 보기위에 소스보기를 하면 아래와 같이 이스케이프 처리가 자동으로 된 것을 볼 수 있다. 

 

 

  그럼 여기서 이런 생각이 들 수 있다. "그런데 만약 자바스크립트를 flask 로직쪽에서 생성해서 동적으로 HTML 페이지 쪽에 랜더링하는 경우라면 어떻게 할까?" 이다. 이 경우 해당 필터를 제거하는 방법은 간단하다. 아래와 같이 xss.html 파일에서 받은 변수의 뒤에 "|safe" 문장을 추가하여 저장하면 된다. 

1
<td>{{ name|safe }}</td>
cs

 

 

  이후 다시 같은 url 을 실행하면, 자바스크립트가 이스케이프 처리가 안되고 그대로 실행되는 것을 볼 수 있다. 다만 이 경우는 무조건 좋아할 건 아니고, 스크립트를 무효화 하는 기능이 해제된거니, 본인이 직접 해당 넘어온 코드의 안전성을 보장하는 부분을 고민해야 하는 문제가 생긴다. 세상에 완전한 공짜 점심은 없다. 

 

 

 

 

5. 응용편 - 비동기 호출과의 만남.

  추가로 공격의 XSS 의 최악의 상황을 묘사해 보기 위해서, 극단적이긴 하지만 비동기 코드에서 일어나는 상황을 한번 만들어 보자.

 

 

  우선 flask 코드는 아래와 같다. 보게 되면 2개의 라우팅 경로가 있는데, /xss_ajax 는 기본 인젝션 페이지로 stored XSS 가 전달되고 있고(reflected 보다 POC 코드 만들기가 편해서 이렇게 했다^^;), xss_ajax.html 을 랜더링 패이지로 쓴다.

 

  밑의 /getData 경로는 웹페이지에서 AJAX 호출을 하는 경로인데, 내용을 보면 단순하게 id 값 인자를 받아서 "hello, 받은 id" 이라는 값을 조합해서 반환한다. 랜더링 때 인젝션 되는 스크립트 코드를 보면 setInterval 함수를 이용해서 매 3초(3000)마다 GetData() 함수를 반복 호출하는 코드이다.

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
from flask import Flask, make_response, render_template, request
 
# flask 웹서버를 실행 합니다.
app = Flask(__name__)
 
@app.route("/xss_ajax", methods=['GET'])
def xss_ajax():
    
    xss_string = """<script>
                       setInterval(function() {
                          GetData();
                       }, 3000);
                    </script>"""
    return render_template('xss_ajax.html', name = xss_string)
    
    
 
@app.route("/getData", methods=['GET'])
def getData():
    your_id = request.args.get('id')
    return "hello, " + str(your_id)
 
 
# 이 웹서버는 127.0.0.1 주소를 가지면 포트 5000번에 동작하며, 에러를 자세히 표시합니다 
if __name__ == "__main__":
    app.run(host='127.0.0.1',port=5000,debug=True)
cs

 [flask_xss_ajax.py]

 

  일단 c:\python\code 폴더에 UTF-8 포맷으로 flask_xss_ajax.py 라고 저장을 한다.

 

 

 다음은 템플릿 소스이다(기본적인 AJAX 코드 동작을 모르는 경우는 클라이언트 코드 편을 보고 온다). 스크립트를 보면 단순하게 페이지가 로딩이 완료(document.ready)되는 순간에 GetData 함수를 호출 한다. GetData 함수는 my_id 인자에 tom 이라는 값을 넣어, flask 의 getData 라우팅 주소를 호출한다. 그리고 반환된 결과를 아래의 span 태그 안에 표시한다. 그 밑의 {{ name|safe }} 는 일부러 XSS 취약점을 일으키게 위해 HTML 인코딩을 제거했다.

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
<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
        <title>AJAX XSS Sample</title>
    </head>
    <body>
        <script type="text/javascript">
 
            $(document).ready(function(){
                GetData();
            });
 
            function GetData(){
                var my_id = "tom"
                $.ajax({
                    url : '/getData',
                    type : 'GET',
                    data : {id: my_id},
        
                    success: function(res){
                        $("#spanName").html(res);
                    }
                });
            }
        </script>
                        
        <span id="spanName"></span>
        <td>{{ name|safe }}</td>
    </body>
</html>
cs

[xss_ajax.html]

 

  해당 소스를 c:\python\code\templates 폴더에 UTF-8 포맷으로 xss_ajax.html 이름으로 저장한다. 이후 페이지를 한번 실행해보자. XSS 공격만 없다면 최초 페이지를 로딩했을때, getData 경로를 한번 호출하는 ajax 코드를 이용해 결과를 받아, 페이지에 hello, tom 이라고 표시해주는 작업을 하게 되는 페이지이다. 이후 플라스크 웹서버를 실행 시킨다.

 

c:\Python\code>python flask_xss_ajax.py
 * Serving Flask app "flask_xss_ajax" (lazy loading)
 * Running on
http://127.0.0.1:5000/ (Press CTRL+C to quit)

 

 

  엣지나 크롬 브라우저에 http://localhost:5000/xss_ajax 경로를 실행 하면, 아래와 같이 평범한 페이지가 실행이 된다. 사용자 입장에서는 특별한 일이 없는 것으로 보인다.

 

 

  하지만 피들러를 띄운 상태에서 해당 페이지를 띄워보면 전혀 다른 상황이 되어 버린다. 아래와 같이 한번 호출 하고 말아야 했던 /getData 경로가 3초에 한번씩 무한으로 실행되고 있다(브라우저를 끄면 종료된다). 이론상 스크립트를 어떻게 작성하고 어떤 AJAX 기능을 호출하냐에 따라서 마음대로 할수 있다(이렇게 되면 SQL Injection 이 일어났을때 공격자가 마음대로 쿼리를 만들어 내는 거나, 업로드 취약점으로 웹쉘이 올라갔을때와 비슷한 느낌이 된다). 인증된 토큰이나 쿠키는 이용하지 않지만, 위의 예제를 CSRF 공격의 AJAX 버전이라고 봐도 될듯 하고 말이다.

 

 

  보안 교육 할때 브라우저를 마음대로 조정한다고 많이 시연되는 Beef 라는 툴도 결국은 이와 비슷한 원리로 잘 짜여진 ajax 스크립트가 기반일 것이다.

 

[beef project]

https://beefproject.com/

 

 

 

 

6. XSS 방어 설계

  그럼 우리가 생각했던 여러 포인트 들을 정리해서 XSS 이슈에 어떻게 접근해야 될지 한번 고민해 보자. 우선 XSS 의 패턴은 너무 많다. 그러므로 가장 바람직한 경우는 영향을 최소화 하기 위해서 스크립트나 HTML 을 표시하는 기능을 필요한 만큼만 최소화해서 구현 하는 것이다. 사용할 수 있는 태그 제한이 가능하다면 가능한 태그와 문법만 허용하는 화이트 박스 방식으로 체크하면 좋을 서이다.(이런 이상적인 경우는 드물긴 하겠지만...)

 

 

  reflected XSS 는 브라우저가 인지해서 어느정도 방어할 수는 있지만, 세상 사람들이 가진 브라우저는 워낙 종류와 버전이 다양하므로 브라우저만의 방어를 믿을 수는 없다. 앞에서 보듯이 IE 계열은 그대로 실행도 시켜주고 있다. 그래서 적절한 방어 코드를 넣어야 하는데, 원칙상으로는 페이지의 자바스크립트나 프로그래밍 언어에서 참조하는 외부 모든 인자에 대해서(HTTP 헤더의 모든 값 포함) 사용 전에 안전하게 필터링 해줘야 한다. 

 

  그런 부분을 충분히 설계하기 힘들다고 판단된다면, 아래와 같은 신뢰성 있는 기관에서 만든 필터를 이용하는 것이 현실적이다. 하지만 기본적인 XSS 에 대한 이해와, 자바스크립트의 이해, 방어의 한계 등을 잘 인지하고 있어야지 문제가 있을때 대처 할 수 있다는 것은 잊으면 안될것 같다(때로는 과하게 막아 프로그램과의 호환성을 위해서 해당 필터 라이브러리의 일부를 커스터마이즈 해야 될 수도 있을 듯 싶다). 또 위의 flask 예에도 보았듯이 괜찮은 웹 프레임워크나, 여러 자바스크립트 라이브러리를 쓰는 것도 해결책일 거 같다. 해당 라이브러리에 문제가 생긴다면 그쪽에서 빠르게 패치를 제공할 것이기 때문에 말이다(사실 보안은 어느정도 이렇게 신뢰성 있는 외부 프로젝트 들에 많이 의지하는 것 같다. 회사에서 구입해 사용하는 상용 백신 솔루션 같은 경우도 어찌보면 백신 회사의 지적인 프로젝트를 믿고 의지하는 것이라고 볼수 있다).

 

[Cross Site Scripting Prevention Cheat Sheet - OWASP]

https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md

 

[XSS 나무위키] - 4.1.1.2 에 네이버에서 제공되는 필터가 언급되어 있다.

https://namu.wiki/w/XSS

 

 

  reflected XSS 의 경우는 웹서버로 전달되는 모든 인자를 체크해야 하는 부분이지만, 사실 명확히 얘기하면, 프로그램이나 자바스크립트에서 실제 사용하는 인자가 실제 대상이다(뭐 미래에 사용 안하던 다른 인자가 사용될 수도 있지만 그때는 적절한 개발 단계의 보안 점검을 통해서 변화를 인지하고 팔로우업해야 될듯 하다).

 

  또 브라우저가 reflected XSS 를 인지할 수 있다는 것은 브라우저에 들어가는 입력과(여러 GET, POST 인자와 HTTP 헤더) 출력(서버로부터 전달되어 화면에 뿌려지는 HTTP 소스)을 알 수 있다면, reflected XSS 가 일어나는 후보 값들을 쉽게 발견할 수 있다는 것이다. 이 부분이 웹 취약점 스캐너가 XSS 를 찾는 원리라고 볼 수 있고, reflected XSS 또는 DOM based XSS 영역의 취약점을 점검하는데 많은 도움이 된다. 스캐너는사람이 하면 거의 불가능한 모든 인자를 리스트업 해서, 스크립트를 입력하고 결과 HTML 에서 해당 스크립트가 어떻게 되었는지 확인하여 판단을 해준다. 다만 이경우 앞에서 얘기한 실제 사용안하는 외부 값까지도 모두 검사하는 낭비 측면이 있지만 뭐 그건 자동화의 단점이자 장점인 듯 싶다.

 

  반대로 얘기하면 좀더 민감한 stored 같은 경우는 운이 좋지 않은 이상 스캐너로 발견하기 무척 힘들다. 해당 데이터에 우연히 접근되거나, 해당 데이터를 전송한 후에 바로 확인이 가능해야 한다. 전략적으로 스캐너를 이용해 stored XSS 테스트에 접근한다면, 기존 기능을 기반으로 입력 후 바로 디비에 저장된 결과를 보여주는 테스트 페이지를 만들어 스캐너를 돌린다든지 하는 작업이 필요할 듯 하다. 추가로 스캐너는 문제를 발견하는 것이지 문제가 없음을 100% 증명하는 툴은 아니라는 것을 인지하자. 스캐너가 사용한 수많은 XSS 패턴들이 모든 현존하는 XSS 패턴을 커버한다는 보장은 할수 없기 때문에, 스캐너 검사에 통과 되었더라도 여러 다양한 요소에 해당하는 취약점 패턴이 숨어있을 수도 있다.

 

 

 

 

7. 마무리 하면서

  추가로 위의 예에서는 유입 변수에 <script> 태그가 들어간 예제만 보았지만, 실제로는 해당 태그가 없어도 스크립트 동작이 가능한 경우가 많다. 적절한 코딩 방법은 아니지만 <script> 태그내의 스크립트 내용 중 일부를 외부 값을 사용하는 경우다(실제 이렇게 코딩하는 개발자들이 가끔 있다). 위의 플라스크 템플릿을 예를 들면 아래의 코드 형태일 것이다.

1
2
3
4
5
<script>
 
var a = {{ name|safe }};
....
</script>
cs

 

 

  name 변수에 "1;setInterval(function() {GetData();}, 3000);" 을 넣는다면 아래와 같은 반복 실행을 하는 문법이 똑같이 만들어질 것이다.  

1
2
3
4
5
<script>
 
var a = 1setInterval(function() {GetData();}, 3000);
....
</script>
cs

 

 

  뭐 여튼 결론적으로 스크립트 문제 및 인젝션 문제에 대해서는 요구사항이나 설계의 자유도가 높아질 수록 점점 방어하기는 복잡하게 되어 버린다. 모든 보안 분야가 마찬가지로 자유도를 높게 줄수록 방어하기는 더 어려워지며 포기해야할 트레이드 오프도 생길 수 있다. 결국 해당 부분의 영향을 최소화 하기 위해서는 대상이 되는 시스템과 요구사항, 기술, 프로세스, 관련된 사람들을 잘 이해하는 방법 밖에는 없을듯 싶다. 생각보다 실제에서는 보안은 명확하지 않은 애매한(놓여진 코드나, 요구사항, 여러 외부 환경에 따라 달라지는) 영역들이 상호 작용하는 분야이기도 한거 같다. 그나마 어플리케이션 보안 부분이 제일 명확한 면이 있어 보이는 듯 싶다. 점점 진행될 수록 앞의 다른 챕터의 얘기들이 조금씩 반복되곤 하는데 맨 처음 얘기한 듯이 보안의 원리가 결국 비슷비슷하기 때문이다. 어플리케이션 보안은 프로그램과 데이터 간의 술래잡기 같은 측면이 있다.

 

 

2019.3.3 by 자유로운설탕
cs

 

 

 

 

posted by 자유로운설탕
2018. 1. 1. 19:15 보안

  이번 시간에는 코드 읽기라는 주제로, 어플리케이션 보안에 있어 코드를 읽는 부분이 어떤 의미를 가지고 있는지에 대해서 이야기 해보고, 현실적으로 코드 읽기를 적용하려 할때 어떤 문제가 있는지에 대해서도 잠시 얘기해 보려 한다. 개인적으로는 일반적인 테스트든, 보안에 관한 테스트든 가능하면 블랙이나, 그레이 박스 관점에서 진행하는 것에 추가해 소스를 이용하는 것을 선호하는 편이다. 그렇게 함으로써 미지의 범위에 대한 불안감도 많이 덜어주고, 적절히 사용하면 효율성 면에서도, 원래대로 라면 당연히 해야 되는 단순 작업을(인형눈 끼우기 같은...) 많이 줄여주기 때문이다.

 

 

[목차]

[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

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

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 설정, 패치, 하드닝

10. 설계문제

11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터

 

 

 

1. 들어가면서

  만약에 아래와 같은 단순한 터치스크린 화면 기반의 프로그램에 대한 보안 테스트를 한다고 가정해 보자. 로직은 단순해서 금액을 넣을 경우 돈이 인출되는 프로그램이다(단순화를 위해 인증이나, 비밀번호 등의 체크는 앞에서 이미 진행했다고 가정).

 

 

  그럼 일반적인 테스트나 보안 테스트 요소를 적당히 리스트업 해 보면, 아래와 같은 여러가지 케이스 들이 생길 수 있다.

1) {-1}, {0, 1, 2}, {999, 1000, 1001} - 마이너스 값, 경계 값 
2) {10000}, {32768} - 아주 큰 값
3) SQL 등 Injection 에 관련된 문자나 특수 문자 

4) show me the money 같은 치트키 입력

5) 그 밖의 예상 못한 결과들(버튼을 빠르게 두 번 누르거나, 화면의 구석에 "Hi" 라고 그리면, 비밀 메뉴가 열린다 든지)

 

 

  위의 케이스 중에서 1~3번 정도는 어떻게든 커버할 수 있겠지만, 4~5의 영역은 블랙박스(외부의 UI만 보고 하는)나 그레이 박스(외부의 UI 와 일부 내부 테이터를 보면서 하는) 테스트 기법을 가지고는 사실 커버하기가 힘들다. 4번 같은 경우는 잘 악용되는 문구의 사전식 대입 정도가 최선일 것이다(보안에서 스캐너가 어드민 페이지 경로를 찾을때 하듯이 말이다). 결국 보안 테스트에 대한 케이스는 요즘 같은 복잡한 어플과 인터페이스 및 서비스 들이 엮어 있는 상태에서는 사실 거의 무한대라고 봐도 될 것이다.

 

 

  그런데 만약 우리가 해당 화면에 대한 소스를 볼 수 있어서, 입력 박스가 "intMyMoney"라는 이름을 가지고 있고, 인출 버튼이 "btnWithdraw" 라는 이름을 가진것을 알수 있고,

 

 

  버튼을 눌렀을 때의 실행되는 코드가 아래와 같이 이루어 졌다는 것을 볼 수 있다면 어떻게 될까?(참고로 아래의 코드는 예를 들기 위한 가상의 코드이다)

1
2
3
4
5
6
7
8
On_btnWithdraw{
 
If(isNumeric(intMyMoney) & intMyMoney >= 1 & intMyMoney <= 1000)
    { 인출 하는 함수 }
Else 
    { error 를 처리하는 함수 } 
 
}
cs

 

 

  충분히 코드의 관리나 빌드 및 배포가 안전한 프로세스 상에서 이루어지고 있다는게 보장되고 있는 상태라면, 우리가 앞에 리스트업 했던 여러 상상의 테스트 케이스 들은 현실적인 케이스로 좁혀질 수 있게된다. 예를 들어 아래와 같은 코드라면 isNumeric 함수가 잘 동작되는지 보기위해 문자, 특수문자 정도를 넣어서 예외처리가 잘되는지 보고, 1과 1000 사이의 경계값 정도를 체크해 보면 테스트가 마무리 될 것 같다.

 

  다만 한가지 눈에 코드가 보인다고 해서, 해당 코드의 동작을 증명하는 실제 테스트 조차 안 하는건 아주 위험한 행위이다. 사람이란 언제나 편견에 의한 착각과 실수를 하기 때문에 눈으로 보기에 확실해 보이는 것도, 실제로는 아닌 경우가 많기 때문이다(개인적으로 자신있게 그러다가 몇번 돌을 맞은 후 얻게 된 교훈으로, 개발자와 같이 동시에 보더라도 서로 의견을 나누다가 편견이 생기기도 한다. 만약 두사람이 라면 서로 상대방의 가정을 모르는 상태에서 보는게 더 날것 같다-비슷한 경험으로 백화점 알바할때 재고 숫자를 세기 위해서 두 명의 알바가 따로 따로 세서 서로 숫자가 맞으면 넘어갔던 적이 있다).  

 

  게다가 만약 위의 코드에 개발자가 테스트를 위해서(또는 나중에 이익을 얻기 위해서) 아래와 같은 코드를 심어 놓았다면 어떻게 될까?  물론 저런 사고를 막기위해서, 개발자 간 피어 리뷰 또는 커밋이나 빌드 전 커미터라고 불리우는 시니어들의 최종 코드 리뷰를 하기도 하고, 개발 환경에서만 유효한 안전한 디버킹 코드를 넣는 방식을 가이드 하기도 한다지만 해당 부분은 한쪽으로 밀어놓고 무시해 보자.

1
2
3
4
5
6
7
8
9
10
11
On_btnWithdraw{
 
If(intMyMoney == "Show Me the Money")
    { 무한대 인출 가능 함수 }
 
If(isNumeric(intMyMoney) & intMyMoney >= 1 & intMyMoney <= 1000)
    { 인출 해줘 함수 }
Else 
    { error 처리 함수 } 
 
}
cs

 

  아마 블랙박스 스타일의 검증으로는 위의 부분을 절대 잡을 수 없을 것 이다. 실제 저런 일은 드물거나, 어딘가 안 들키고 조심스럽게 숨겨져 있겟지만, 비슷한 느낌의 사건이 맥 OS 쪽에서 발생했었다(아마 해당 경우는 최초 root 계정이 비활성화 된 상태에서, 패스워드를 지정하면서 명시적으로 활성화 할수 있는데, 어떤 이유인진 모르지만 패스워드가 없는 상태에서 디폴트로 활성화 된것으로 추정되긴 한다).

 

  여튼 위와 같은 방식의 코드는 아니겠지만, 백도어 기능이 생겨버렸던 것도 맞고, 리그레이션 케이스(새로운 빌드가 나왔을 때 기존의 주요 취약점이나 버그등을 확인하는 것)로도 잡기는 힘들었을 것이다(왜냐하면 나름 저런 희귀 케이스들은 보통 사건이 일어난 후 리그레이션 테스트에 추가되기 때문이다). 저런 사실을 해커가 아는 상태에서 모르는 척 사용한다면 얼마나 큰일이 될까 생각해보면, 화이트 박스 개념의 취약점 체크가 블랙 박스 만큼 중요한 의미를 가진다는 것을 조금은 어필이 되지 않을까 싶다.

 

 [애플, 하이 시에라 ‘루트’ 취약점 수정한 보안 업데이트 배포 - IT World]

http://www.itworld.co.kr/news/107371

 

 

 

 

2. 다른 예 - 복잡성의 단순화

  또는 만약 특정 사이트에 있는 모든 이미지 업로드 기능을 체크 하거나, 이미지 업로드 기능이 많은 관리자 페이지에 대해서 업로드 취약점이 있는지 체크를 한다고 할 경우, 만약 소스의 내용을 전혀 파악할 수 없는 상태라고 해보자. 이 경우 모든 페이지를 일일히 살펴보면서 체크하거나, 시간이나 리소스가 충분하지 않다면 업로드 로직 호출 패턴이나, 감에 의해서 같아 보이는 종류로 묶어서 유형별로 한개씩만 테스트 하고 무사하길 빌 수 밖에 없다(물론 운이 좋은 경우는 공통 업로드 URL 이 존재해서, 해당 URL만 호출 할 경우도 있을 수는 있을 것 같지만...). 

 

 

  이렇게 내부적으로 공통적인 로직이 들어있는지 알 수 없는 경우 막막하게 느껴지지만, 반대로 소스를 볼수 있을 경우에는 문제가 달라지게 된다. 소스내에서 공통된 업로드 처리 함수가 있는지 체크하고, 해당 함수내에서 외부에서 들어온 업로드 관련 인자들을 어떻게 처리하는지, 어느 폴더에, 어떤 이름으로 저장하는지를 체크 후, 실제 존재하는 업로드 관련 페이지들이 해당 안전한 공통 로직을 이용하는지만 여러 방법으로 찾아 증명만 하면 된다(해당 부분은 나중에 파일 업/다운로드에 대한 시간에 자세히 살펴 보려고 한다).

 

  최악의 경우 개발자 들이 각각 공통 함수를 무시하고, 자신만의 로직으로 업로드 코드를 개발하였다고 하더라도 어떤 부분이 공통이 아니여서 좀더 세심하게 들여다 볼 필요가 있는지를 정확하게 알 수 있게 된다. 물론 이런 작업을 페이지 소스를 살펴보면서도 할 수 있지만, 들이는 에너지와 정확도 면에서 소스를 보는 쪽이 휠씬 유리하다.

 

 

 

 

3. 일반 언어와의 비교

  그럼 프로그래밍 언어에만 한정 짓지 말고 일반 언어와 한번 비교해 보려고 한다. 언어에도 읽기, 쓰기, 듣기, 말하기가 있듯, 코드에 대해서도 비슷한 부분이 있다.

 

 

3.1 읽기
  읽기 측면에서 코드를 통해서 여러 점검이 필요한 중요 로직들을 얻어낼 수 있다. 특히 전형적인 취약점이 아닌 설계에 대한 보안 문제는 전체적인 로직을 잘 살펴보아야만 발견할 수 있을 가능성이 높다. 물론 관련 기획 문서를 보거나, 실제 해당 페이지나 기능들을 실행해 보는 것도 효과가 있지만, 소스 읽기는 밖에선 잘 안 보이는 숨겨진 부분들을 다른 관점에서 살펴보게 해준다.

 

  다만 여기서 한가지 짚고 넘어가고 싶은 것은 해당 효과를 꼭 소스 읽기를 통해서만 얻을 수 있는 것은 아니라는 점이다. 기존에 알고 있는 여러 지식으로 부터의 유추나, 관련 개발자와의 대화 및 문의에 의해서도 많은 것을 얻어낼 수 있다. 다만 이 경우도 소스를 어느정도 이해하고 있는 것은 정확한 질문을 던져서 원하는 해답을 얻어낼 수 있을 가능성을 높여주게 된다. 보안과는 조금 먼 관점의 해당 개발자 스타일의 설명을 잘 이해해, 점검에 도움이 되는 형태로 스스로 재가공 할 수도 있다. 경험상 맘에 맞거나, 일하는 스타일이 맞는 개발자 동료가 있는 것은, 구글 검색엔진이 도와주는 것과 비슷하게 든든한 효과를 주게 되므로, 상황이 어쩔 수 없다면 모르겠지만 꼭 혼자서 모든 걸 부딛치려고 하는 것은 좋은 것은 아니다. 

 

  또한 로직을 이해하기 위해 코드를 읽는 것은 개발을 하기위해 코드를 읽는 것과는 조금 다른 측면이 있다. 만들기 위한 코드 읽기는 프로그램의 세부 로직과 문법의 디테일한 구조를 동시에 살펴봐야 하지만, 보안을 위한 코드 읽기는 데이터의 흐름에 따른 요점만을 읽는다고 보면 된다. 예를 들면 입력 인자들이 어떤 외부에서 들어오고, 해당 인자들이 내부 로직에 어떻게 전달되고, 어떤 처리를 거쳐서, 어떻게 안전하게 사라지게 되는가의 라이프 사이클을 살펴보는 작업이다. 또한 현실과 차단되어 코드 만을 보는 것 보다, 블랙박스 같이 바깥 쪽의 흐름도 참고하면서 비교해 보는게 좀더 난듯 하다.

 

 

3.2 코드 쓰기
  현대의 코드 쓰기 작업은 많은 부분이 수정(modify)의 측면에서 사용되는 경우가 많은 듯 하다. 예를 들어 API 를 호출해 결과를 화면에 뿌려줄 경우도 처음 부터 맨땅에서 시작하는게 아니라, API 를 만든 사람이 제공하는 샘플을 기반으로 구현하게 된다. 또 구글링을 통해서 많은 코드를 얻고 있고, 해당 언어에 대한 샘플이 없더라도, 다른 언어로 이루어진 샘플을 보면서도 아이디어를 얻게 된다.

 

  그래서 패턴이 되는 조각 코드들을 적절히 만들거나 참조해 쓸 수 있게 되면(예를 들어 HTTP 요청과 받기, HTTP 소스 분석, 주기적인 처리), 어느 언어를 사용하든 구글을 이용하여 적당히 원하는 동작을 하는 코드를 참조해 만들어낼 수 있다(어떤 사람들은 이런 웹에 돌아다니는 코드는 돌아가긴 하지만 유지보수가 성능이 보장 못 한다고 저평가 하는 측면도 있지만 해당 부분은 관점에 따라 맞는 것도 틀린 것도 같다. 그렇게 따지면 우리가 쓰는 오픈소스도 웹에서 돌아다니는 코드니까 말이다). 이러한 작업이 가능하기 위해서는 정규 표현식, 여러 데이터베이스, 스크립트언어, 빌드 방식 언어, 빅데이터 오픈소스 등등 여러 언어와 배경 지식들을 평소에 습득하되, 특정한 기술에 의존적인 부분 보다는 기반적인 부분에 초점을 익히는 것이 좀더 바람직 하지 않을까 싶다.

 

  또 이러한 "서당개 3년" 스타일의 쓰기는 보안 테스팅을 할때도, 실제 환경상으로 불가능하거나, 실 사이트에 영향을 미쳐 수행하기 힘든 부분을, 코드를 적절히 변경하여 테스트 환경에서 체크함으로서, 최소한의 안전을 보장하게 만들 수도 있다(예를 들어 위의 예를 든 업로드 코드를 실제 운영 환경에서 돌려볼 순 없지만, 방어를 하는 필터 함수만을 잘라내어 체크한다든지 하는 상황이 있을 수 있다). 

 

  그래서 코드를 백지부터 만드는 능력이 떨어져도(요즘 이런 사람이 얼마나 될까는 싶다), 우선은 기존 프로그램들을 조금씩 수정해 보는 모드로 공부하는 것을 추천한다. 언어로 따지면 쓰기 전에 우선 많이 읽어서 여러 패턴을 익힌후, 해당 패턴을 이용해서 더듬더듬 쓴다고 봐도 될것 같다. 프로그램의 좋은 점은 언어의 말과는 달리 잘못 쓰게 되면 아예 안돌아가거나 버그가 바로 나오기 때문에 짧은 한마디를 할때부터 자동 교정의 훈련이 되는 점이다. 다만 충분한 경험이 쌓이기 전에는 이해가 안 되는 에러가 나서 답답함이 많긴 하지만 말이다(그 경우 구글을 도움을 받으면 된다).

 

 

3.3 듣기 & 말하기
  취약점이 발견됬을때 해당 취약점을 올바르게 고치려면 해당 언어와 돌아가는 환경을 올바르게 이해할 수 있으면 좋다. 해당 지식을 기반으로 개발자와 의논하게 되면, 앞에서 얘기한 바와 같이 개발자의 언어로 설명하는 주제들에 대해서도 비교적 쉽게 이해도 할수 있고(또 이러면서 많이 배우게 된다), 적절한 논의 및 보정, 협의를 할수 있게 된다.

 

  또한 이쪽에서 원하는 부분에 대한 빠른 피드백을 얻을 수도 있고, 단순히 어떠한 작업을 해달라고 요구사항 만을 기계적으로 전달하는 것보다는(보통 취약점은 찾을 수 있지만, 대상 코드를 어떻게 수정해야되는지에 대해 정확하게 모르는 경우 이런 태도를 취한다. 극단적으로 보면 "네가 알아서 고쳐" 이런 모드다) 사이드 이펙트가 없는 정확한 코드의 수정이 이루어질 수 있다. 개발 쪽에서는 의외로 보안 쪽에서 당연하다고 생각하는 언어의 보안적 관점에 대해서 모르는 경우가 많기 때문에, 원하는 방식과 결과만을 이야기 하는것만으로 충분하지 못할 때가 많다.(원리와 결과에 이르는 과정을 이해시켜 주는 것이 종종 필요하다).

 

  다만 이 부분은 이상적인 얘기고, 개발자에 따라서는 자신의 코드 영역에 들어오는 것을 싫어하는 사람들도 꽤 많다. 그런 경우 본인의 코드 영역을 간섭 받고 있다고 받아들이게 되어 역효과를 가져올수도 있다. 반대로 적극적으로 코드의 흐름에 대해서 얘기하면서 의논하는걸 좋아하는 개발자도 많이 있다. 상황 상황에 따라 틀리지만, 적절히 상대의 성향에 맞추어서 잘 협상하는 것이 좋다고 생각한다(개인적으로 이런 문제로 부딛쳐 사이가 안 좋아진 경우가 있는데 지금은 좀 후회가 된다. 지나고 나면 좀 부질 없는 일들 같기도 하다)

 

 

4. 보안에서의 코드 읽기의 문제점

  아마 어떤 분들은 위의 글들을 읽으면서 현실과 동떨어진 얘기라고 생각 할수도 있을 것 같은데, 그런 부분들에 대해서 언급해 보려고 한다.

 

 

4.1 현실적으로 어려운 점

  확실히 코드를 읽으면서 보안 설계가 잘 되어 있는지 체크하는 방법은 단점 또한 많다. 첫 번째로 한정된 시간이다. 주어진 시간이 몇 일 밖에 안되는 상황에는 남이 만든 소스를 들여다 보면서 로직을 파악하는 것보다는, 주어진 시간을 효율적으로 쓸수 있는 다른 방법들을 찾아보는 것이 낫다. 시간과 리소스를 고려하여 블랙박스 접근법 만을 쓸지, 화이트 박스 접근법을 같이 쓸지, 아니면 일부만을 보조해서 쓸지를 선택해야 한다.

 

  두 번째는 "1 vs N" 의 문제로써, 인원 비율 상 수 많은 개발자들이 만든 소스를 보통 한 사람이 살펴봐야하기 때문에 적절히 범위를 제한하거나, 요점만을 읽는 속도를 높이는 방법을 계속 고민하고 연습해야 한다.

 

  세 번째는 초기 진입 장벽으로 이것은 영어를 처음 배울때와 비슷하다. 처음에 소스를 읽게되면 하나하나 문법적인 부분도 따져가면서 읽어야 해서 모르는 것도 많고, 느리고, 난해한 해석 과정을 거쳐야 한다. 이 부분은 프로그래밍을 만드는 것을 배우는 것과 비슷하다고 보고, 피할 수 없는 측면이 조금은 있다. 분명한 것은 영어책과 마찬가지로 많이 읽으면 읽을 수록 빨라진다.

 

  네 번째는 회사에서 보안이나, 지적재산권 보호 등의 여러가지 이유로 특정 개발자 이외에는 소스를 보여주지 않는 경우도 있을 수 있다. 이 경우는 세월을 이용한 증명과 설득 밖에 없어 보이는데, 해당 회사에서 꾸준히 근무하여 신뢰를 쌓고, 검증을 위해 코드를 보는 방법이 효율적이고, 안전한 코드를 만드는데 도움이 된다는 부분을 설득해서 권한을 획득하는 방법밖에 없을 것 같다. 어떤 회사는 내부자에게 모두 공개하는 회사도 있으니 복불복일 듯은 싶다. 근데 막상 공개해 줘도 실력이 안되 못 볼 수도 있으니 자기 실력은 잘 객관적으로 파악하고 있어야 하는 듯도 싶다.

 

  마지막으로 세상엔 많은 언어들이 있고, 자신의 일하는 환경에도 많은 언어들이 혼재되어 있을 수 있다는 부분이다. ASP, PHP, C#, 자바, SQL, 자바스크립트, jQuery, Node.js, 스칼라 등등의 수 많은 언어들과 언어의 배경지식들을 적절히 따라잡아야 한다. 또 앞으로 나오는 새로운 기술들도 새 프로젝트에서 기존의 패턴들을 사용하기 위해서 어느 정도의 깊이로는 따라가야 한다고 본다. 또 은근 코드를 볼때, 코드가 저장된 저장소 특성에 따라서, 해당 저장소에 적합한 검색 방식을 습득하거나 궁리해야 하기 때문에 문제가 된다. 낯설지 않은 저장소에 담겨진 소스를 봐야하는 경우는 이전 버전의 비교라든지 하는 작업 부분에서 병목을 가져올수 있게 된다.

 

 

4.1 현실적으로 다행인 점

  위와 같이 난해한 코드 읽기의 세계에서도 다행인 점들이 있다. 

 

  첫째로 패턴화가 가능하다는 것이다. 한 프로젝트 안에는 많은 유사한 구조와 로직들이 있고, 그 구조와 로직은 다른 프로젝트에서도 존재할 가능성이 아주 높다(코드나 구조의 복사는 바이러스와 같고 생각보다 정답은 비슷한 경우가 많다). 또 많은 소스들이 구글 검색이나, 해당 언어 및 라이브러리의 가이드, 다른 시니어, 이전 개발자의 코드 등을 기반으로 만들어졌기 때문에, 이런저런 사유로 유사한 로직과 구조들도 많은 편이다. 또한 웹같은 경우는 동작이나, 디자인 상의 유사적인 부분이 많다(이런 것을 편하게 만들기 위해서 파이썬의 장고나, 자바의 스프링, .NET의 MVC 패턴 같은 프레임워크 들이 있는 것일테고..). 

 

  둘째로 영어, 중국어 등의 언어와 마찬가지로 소스를 많이 보게되면 그만큼 읽는 속도가 빨라지게 된다. 요점만 골라읽는 요령도 조금이나마 늘게 되고 말이다. 또 스스로 로직을 정리하기 때문에 계속적으로 업그레이드가 되는 프로그램 이라면, 향후 추가적인 점검을 진행 할 때 해당 지식들이 좋은 백그라운드를 형성하게 된다(그래서 도메인 전문가가 생기는 걸꺼다..). 그리고 아마도 직접 만드는 만큼은 아니지만, 간접적으로 프로그래밍에 대한 경험적 노하우가 쌓이게 되어 나중에 자동화 등의 분야에서 코드를 만들어 볼때도 도움이 된다.

 

  마지막으로 앞에도 잠시 얘기 했지만 잡(Job), API 같은 UI 적인 형태가 없는 프로그램을 분석하고 테스트 할 때, 프로그램의 형태 및 어떻게 돌아가는지를 이해하기 쉬워 해당 검증 작업을 용이하게 해줄 수 있다.

 

 

 

5. 실행 전략

   조금 앞의 내용과는 겹치는 듯 하지만, 정리하는 의미에서 어떻게 그럼 코드 읽기를 이용할까를 생각해 보자.

 


  첫 번째로, 항상 "시간 vs 선택한 방법의 효율"을 생각해야 한다. 자신이 선호하는 한 두가지 기술에만 집착하면서 진행하다 보면, 오히려 자가당착에 빠지게 된다. 정말로 안으로부터 보는 게 가치가 있는가를 검토해서 확신이 있을 경우만 코드 읽기 방법을 이용해야 한다. 프로그램의 도메인에 따라서 밖에서 보는 것이 더 단순하고 명확할 때도 많다.

 

  두 번째로, 코드를 읽으면서 습득한 지식은 가능하면 문서화를 해두는 것이 좋다. 시간은 기억을 희미하게 만들어서, 다 이해했던 코드도 몇달만 지나면 가물가물 해서 다시보면 이게 뭔가 싶어진다. 이건 매일 코드를 보는 개발자들도 마찬가지라고 한다.

 

  세 번째로, 자신이 선호하는 언어는 있어도 괜찮지만, 보통 주력 언어의 업무를 가능한 고집할 수 있는 개발쪽 과는 달리(물론 새로운 기술의 압박은 계속 되겠지만), QA나 보안 같은 경우는 언제 어떤 언어와 환경을 체크해 해달라고 던져올지 알수가 없는 부분이 있다. 그래서 가능한 언어 자체보다는 언어가 의존하는 여러 배경 지식을 익히고, 언제라도 새로운 언어나 환경에 적응해야 된다는 마음가짐을 가지고 있어야 한다고 생각한다. 다만 다행히 패턴이란게 있고, 새 언어는 기존 언어를 벤치마킹 하는 경향이 있기 때문에, 스크립트 언어를 하나 열심히 경험에 두면 다른 스크립트 언어에 대해 적응이 많이 수월하고, NET, 자바 같은 객체지향의 언어도 마찬가지라고 본다.  

 

  네 번째로, 읽는 속도도 분명이 중요한 요소중 하나라는 것이다. 코드를 읽고, 기능을 분류하고, 케이스를 제외하거나, 뽑아내는 것도 보안 테스팅의 한 요소이긴 하지만, 그 부분에 너무 시간을 쏟다보면, 실제 해당 분석에서 세운 가설들을 직접 해보면서 증명할 시간이 모자랄 수도 있게 된다. 그리고 보통 개발 뒤에 일어나는 검수 업무에 대해서는 생각보다 충분한 시간이 주어지진 않는 경우가 많아서 마린의 스팀팩과 같이 집중해서 속도를 낼 수 있는 훈련을 꾸준히 하는것도 중요한 것 같다(뭐 이렇게 보면 스포츠 경기의 준비 단계와 비슷한 것도 같다). 더 좋은 방법은 분석에 대한 시간을 미리 벌기 위해 개발 단계에서부터 참여가 가능하다면 좋을 것 같다.


   다섯번째로, 역시 앞에서 한 얘기지만 혼자서 모든걸 할수 있다고 생각하는 것 보다, 믿을 수 있는 동료들과의 좋은 네트워크를 가지는 것이 좋다. 일하는 스타일이 맞는 친한 개발자들을 만들고(물론 일을 위해서 억지로 관계를 만들라는건 아니다), 개발자들이 프로그래밍 언어에 접근하는 방식을 계속 관찰하다보면, 단순히 책이나 혼자서 공부하는 과정에서는 발견하지 못하는 사실들을 배울 수 있게 되는 것 같다.

 

  마지막으로 쓰기의 경우는 스스로도 잘 못하는 부분이긴 하지만, 좋은 라이브러리들의 사용에 집중을 하고, 오픈소스들을 이해하고, 실용성에 중심을 두고 접근을 하는게 어떨까 싶다. 앞의 파이썬 글도 사실 그런 바램에서 진행된 활동이라고 보면 된다. 

 

 

 

 

6. 이런 저런 꼬투라기

  꼬리가 길지만 마지막으로 몇가지 생각나는 부분을 적어보면 

 

 

  첫 번째로 화이트 박스 기법을 적용하는데 있어서 가장 중요한 부분 중 하나가 소스 컨트롤이 얼마나 잘 구축되 있으냐 인것 같다. 소스가 자기 멋대로 변경되어 버린다면, 기껏 분석 및 테스트 했던 내용들이 도로묵이 될 수 있으니 말이다. 또는 소스 비교나 변경이력 추적도 분석에 도움이 되는데, 해당 부분이 체계적이고 원활하게 되는 환경이 지원된다면 좀더 유리할 것 같다. 다만 개발쪽에서 편한 툴은 검증 하는 쪽에서는 좀 정보가 부족해 불편한 경우가 많긴 한다.
 

 두 번째로 방법론이 검증하고자 하는 대상을 앞서서는 안 된다. 방법론은 효과적인 점검을 위한 수단이 뿐이지 가장 중요한 것은 대상에 대한 분석적 파악이 우선이다. 이후 어떠한 방법론을 쓸까 하는 것은 분석의 결과지 행동의 목적이 되어서는 안된다.

 

  세 번째로 방어측면 에서 OWASP 같은 데서 제공하는 여러 방어 함수의 효율성과 성능에 대해 꾸준히 관심을 가지는것도 좋은것 같고, 파이썬 등으로 조그마한 툴을 만들어 업무에 사용해 보는 것도 괜찮은 것 같다. 보안 쪽은 의외로 패턴화된 문제들이 많아서 자동화의 ROI 가 좋고 생색내기도 편한 것 같다. 또한 오픈 소스의 사용은 내부 로직을 제대로 이해한 채 사용해야, 필요한 커스터마이징도 할수 있고(외국은 보통 이렇게 쓰는 듯하다), 해당 프로그램의 적절성과 한계를 인식할 수 있다고 생각한다.

 

 

[마무리 하면서]

  스스로 코드 읽기에 대해서 충분히 잘 한다고는 할수 없다 생각되지만, 보안이나 QA에 새로 들어오는 사람들이 직업의 초창기 때부터 의식적으로 노력했으면 하는 부분이라서, 이렇게 얘기를 하게 되었다(개인적으로 더 열심히 할걸 하는 아쉬운 부분이기 때문이기도 하다). 아무쪼록 공감하는 분이 있음 좋겠다.

 

posted by 자유로운설탕
2017. 12. 2. 22:35 보안

  파이썬에 대한 글을 마치고, 이제 두 번째로 보안에 대해서 이야기를 한번 해보려고 한다. 개인적으로 생각했을때 아직 잘 모르는 영역들도 많고, 관심은 있지만 공부를 안해서 발전이 거의 없는 영역들도 많다고 생각한다. 

 

  이 글에서는 보안을 처음 공부하거나, 공부는 하고 있지만 너무 범위가 넓고 어렵게 느껴지거나, 개발이나 시스템, 테스팅 같은 연관이 있는 일을 하는 사람들에게 보안이 생각보다 그렇게 어려운 분야는 아니며, 개발이나 테스팅, 시스템, 네트워크 쪽의 지식처럼 해당 분야의 패턴을 익혀두면 여러 다른 분야에서도 유용하게 쓸 수 있다는 것을 얘기해 보려 한다. 나아가 얼마나 잘 될지는 모르겠지만 보안의 어떤 부분이 보안을 어렵게 생각하게 만들게 되는지도 개인적인 경험에 비추어 설명해보고 싶다. 이전 파이썬 글과 마찬가지로 하나의 접근 방식이라는 관점으로 비판적으로 받아들이기를 바라며 역시 재밌는 시간이 되길 바라면서 글을 시작하려 한다.

 

 

[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 설정, 패치, 하드닝

10. 설계문제

11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터


 

1. 들어가면서

  보안 공부를 어떻게 해야하느냐를 알고 싶어서 검색엔진을 찾아보면 해야될 일이 엄청 많아 보인다. C++와 같은 프로그래밍 언어부터 시작해서, 웹 및 시스템 프로그래밍, 객체지향 개념, 운영체제, 웹서버, 네트워크 장치들, 프로토콜, 암호학, 리버싱, 포렌식, 악성코드 등에 대해 하나하나 이해해야 하고, 나아가 오픈소스, 모의해킹, 보안 관련 툴 들을 이해해야 한다고 한다. 

 

  사실 위의 요소 중 하나로만 범위를 제한하게 되더라도 엄청 많은 하위 카테고리로 확장되게 된다. 예를 들면 웹은 CGI(Common Gateway Interface) 같은 초창기 서비스 구조로부터 ASP, PHP 언어 같은 스크립트 베이스의 언어, JAVA, NET 같은 좀 더 포말한 프로그래밍 형태의 웹으로 확장되고, 여러 타입의 서비스와 API 등도 포함되게 된다.

 

  네트워크도 이름도 낯선 OSI 7 계층 구조부터, TCP/IP, 방화벽, IPS, 스위치, 라우터 등의 여러 이슈들로 나누어지고, 그런 부분도 또 관련 장비의 밴더나 각 진화된 세대에 따라서 서로 다른 영역으로 갈라질 수 있다. 나머지 필수 기술 항목들도 역시 마찬가지로 많은 갈림길이 있다. 웹 서버들도 아파치, IIS, 톰캣 등 조금씩 특성이 다른 여러가지의 웹 서버들이 있고, 각 서버는 각각의 버전마다 설정이나 지원 기능 등에 따라 특성이 또 조금은 다르다고 볼 수 있으며, 다른 웹 언어들과도 연결된다.

 

  게다가 현대의 빠른 기술의 변화에 맞춰서, 빅데이터나 머신러닝등의 새롭게 보안이 필요해지는 영역과 언어들이 계속 쏟아져나오고 있다. 또한 좀 더 넓게 현실적으로 보면 IT 기술의 범위를 벗어나서 물리적인 영역이나, 작업 및 관리 프로세스, 심리적인 부분도 관련 되게 된다.

 

 

  개인적인 관점에서 또 하나 보안을 어려운 분야로 생각하게 하는 부분은, 보안 측면을 바라보는 시각이 매력적인 모의해킹이라는 공격적 측면과, 자격증 획득이라는 측면에 많이 중심이 치우쳐 있기 때문인 것 같다.

 

  보안 지식 전체가 몸을 건강하게 해주는 운동들의 구성이라면, 모의해킹은 프로격투기 선수의 실전 스파링 훈련과 같다고 본다. 모의해킹은 시스템을 구성하는 모든 제반 기술들에 대한 균형있는 검증을 통해서, 안전한 디자인이 이루어졌나를 확인하는 활동이라고 생각한다. 그래서 모의해킹 기법 관련 책에서 얘기하는 많은 백과사전 방식의 기법을 익혀서 대상에 대해서 테스트를 하고, 취약점을 찾거나 찾지 못한 결과에 따라 대상이 불안하거나, 안전하다고 판단하는 것은, 사실 그렇게 많은 의미는 없어 보인다. 중요한 것은 그러한 기법들을 보안 세계 쪽에서 정리하고 권장하게 된 배경을 이해하고, 점검 하려는 대상에 대해 기술적으로 정확하게 이해하여 적절한 기법을 적용하여 검증하는 부분인 것 같다.

 

  자격증 또한 해당 분야를 마스터(사실 사람 자체가 불완전 하며, 모든 분야에서 한 개인은 역사를 스쳐가는 디딤돌 같은 역활인지라 어떤 분야를 마스터했다는 말은 신기루 같은 것 같기도 하지만...)한 전문가를 보면서 행동을 따라하는 것과 비슷하다고 본다. 자격증에 나오는 지식은 현실의 많은 보안 지식을 모아서, 전문가들이 정리하고 체계화된 지식에 불과하다고 본다. 해당 지식 체계가 보안의 전체적인 모습을 정리하고, 체계적으로 접근을 하게 해주는 것은 부정할 수 없는 사실이지만, 해당 지식안에는 현실을 객관화한 지식은 있지만, 현실 자체는 없는 경우가 많다.

 

   지식 자체는 손가락이고, 그 손가락이 가르키는 곳을 봐야지 의미가 있지 손가락 자체를 아무리 외어도 의미는 없다고 본다(자격증 책 본문의 시험용 암기 내용보다는, 각 챕터의 뒤에 있는 그 내용이 나오게 된 근거인 참고 서적이나 관련 링크들에 대한 포괄적인 이해가 사실 더 중요한것 같아 보이지만, 시험공부를 위해서 기본적으로 외우거나 이해해야 할 내용이 엄청 많기 때문에 그런 부분까지 차분히 읽을 분위기는 되지 않는다). 훌륭한 전문가의 행동을 따라하는 것보다, 그 행동을 일으키게 한 마음의 흐름을 이해해야지만, 실제 해당 전문가의 노하우의 그림자라도 익힐수 있게 되지 않을까 싶다.

 

 

 

2. 보안이 어렵게 느껴지는 이유

  우리가 어떤 것을 배우려면, 우리가 어떤 것을 배우고 싶어 하는지를 우선 명확히 알아야 하는 아이러니가 있다(파이썬 글 21교시의 5 orders of ignorance 를 참고). 보안이란 분야가 어떤 것 인지에 대해서 많은 사람들이 서로 다른 정의를 가지고 접근하겠지만, 개인적인 관점에서 가장 주요한 부분은 데이터의 흐름을 따라가는 것이라고 본다. 데이터의 관점에서 한정해 보면 개발은 데이터가 설계된 길로 가게하는 것이고, 테스팅은 데이터가 주어진 길로 제대로 가는지를 증명하는 일이며, 보안은 데이터가 주어진 길에서 납치나 회유되지 않는지 살펴보는 일이라고 볼수 있다고 생각한다. 

 

  또한 보안은 일반적인 프로그래밍이나 테스팅 보다는 우리의 일상생활이나 시스템 바깥의 물리적, 프로세스적 환경의 관리까지 생각하기 때문에 조금 더 범위가 넓게 된다. 여담이지만 점점 개인정보보호 분야의 영역과 보안의 영역이 많이 겹치게 되고 있는데, 개인정보는 (개인의) 중요한 데이터를 보호하는 분야이고, 보안은 중요한 데이터를 보호 하는 분야이기 때문에, 어쩔 수 없이 기술적인 영역에 대해서 관심사는 살짝 다르지만 상당 부분 겹치게 되어있다고 본다.

 

 

  그럼 데이터의 흐름을 따라가려면 어떻게 해야 할까? 데이터가 보여지거나, 지나가거나, 처리되거나, 저장되는 모든 영역을 잘 이해해야 한다. 컴퓨터 안의 CPU, 레지스터부터, 메모리, 디스크 내의 바이너리 형태의 데이터, 운영체제, 프로세스, 프레임워크, 응용 프로그램, 웹서버, 네트워크(프로토콜), 서버, 관리프로그램, 사용자 프로그램, 사용자, 외부 장치, 업무 프로세스 또는 은행의 비밀번호를 기억하고 있는 고객들에 이르기 까지 데이터가 현실의 물리적 형태, 0과 1의 형태의 바이너리 형태로 존재하거나, 레지스트 값, 변수, 메모리 값, 프로그램 내의 변수, 설정 값, 입력 값, 전송 값 등 모든 머무는 영역에 대해서 올바르게 이해해야 한다. 결론적으로 얘기하면 앞의 많은 기술의 요소 부분을 이해해야만 보안을 잘 할 수 있는 것이 아니고, 꺼꾸로 보안을 잘하기 위해서, 그러기 위해서 데이터를 이해하기 위해서, 데이터의 흐름을 이해하고 적절히 보호하기 위하여 해당 기술들을 익혀야 한다고 본다. 

 

  이러한 끝이 잘 보이지 않는 넓은 범위의 기술 범위들은, 앞의 파이썬 글에서 얘기했던 프로그래밍의 배경지식과 비슷하다고 본다. 보안은 보안의 원리 자체가 어려운게 아니라(뭐 쉽다는 의미는 아니고, 배경지식을 잘 안다면 생각보다는... 이라는 말이지만... 반대로 얘기하면 배경지식을 모르면 실제보다 과도하게 어려워 보인다) 이러한 배경지식의 균형있는 습득이 어려우며, 프로그래밍에서의 게임 프로그래밍의 위치와 비슷하게, IT계의 종합 예술 영역 같은 성격을 띄고 있다고 생각한다. 그래서 보안은 데이터를 제대로 보고 흐름을 따라가기 위해서 많은 배경 지식이 필요하게 된다. 프로그래밍도 그러한 배경 지식 중 하나이고, 앞서 얘기한 많은 분야들도 그래서 필요하게 된다. 그리고 모든 분야가 마찬가지겠지만 모르는 분야나, 새로운 기술을 계속 따라가야 하는 어려움도 있게 된다.

 

 

  예를 들어 보안을 모의해킹 같은 공격의 관점으로 봐서, 아주 뛰어난 도둑이 있다고 가정을 해보자. 해당 도둑은 사회공학적 기법을 이용해 사람들을 속여 정보를 얻거나, 원하는 행동을 하게 하는 것도 능숙하고, 담을 타거나 빠르게 이동하기 위한 신체능력도 뛰어나며, 일반 자물쇠나, 생체인식등과 같은 현존하는 모든 자물쇠의 이해에 능통한 상태로 그러한 기법들을 이용하여 원하는 목표를 놓친적이 없다고 하자.

 

  그런데 어느날 열쇠의 주인이 아니면 절대 풀수 없다는 양자역학을 이용한 자물쇠(무슨 자물쇠인지는 모르지만 그런 어려운게 생겼다고 해보자)가 발명되면서 주요 보물들이 해당 자물쇠에 의해서 보호되게 되었다. 이제 도둑이 계속 목표를 달성하려면 어떻게 해야될까? 해당 양자역학적 자물쇠를 이해하여 속일 수 있는 방법을 찾는 수 밖에 없다(미션 임파서블 영화를 생각하면 된다. 물론 영화에서처럼 어떤 자물쇠로 잠겼는 지에 상관없이 사람이나 환경을 이용하여 사회공학적 기법으로 푸는 방법도 있긴 하겠지만 말이다. 기술은 기술을 운영하는 사람의 스마트함에 비례해 효과가 있으니 말이다).

 

  또는 반대로 여러분이 자물쇠가 얼마나 안전한지 체크를 해주는 보안 전문가고, 해당 양자역학적 자물쇠를 사용하는 사람으로부터 해당 자물쇠가 얼마나 안전한지 검증을 해달라고 요청을 받았다고 해보자. 새로운 자물쇠의 안전을 검증하려면 해당 자물쇠의 원리를 우선 이해해야만 한다. 물론 해당 자물쇠를 이해 못한다고 기존의 뛰어난 도둑(악의적인 해커)이 뛰어나지 않은 것은 아니고, 뛰어난 보안 전문가가 전문가가 아니게 되는 것은 아니다. 하지만 해당 분야에서 전문가 명성을 계속 유지하기 위해서는 새로운 기술을 이해하여, 자신이 기존 체계로 부터 이해하고 있는 패턴을 적용하여 공략점을 찾거나, 외부의 공격에 안전한 설계인가를 증명하지 않으면 안된다.

 

 

  다른 예로 당신이 모든 사기 유형을 마스터한 유명한 사기꾼이고, 새로운 매력적인 파생 상품이 나와서 해당 부분을 이용해서 사기를 치고 싶을 때도, 마찬가지로 대상에 대한 이해가 필요하게 될것 이다(사기는 상대방에 대한 이해와 공감을 바탕으로 한, 9개의 진실과 1개의 거짓으로 이루어진 행위라고 생각한다).

 

  현실의 보안 영역으로 내려오면, 윈도우 보안의 전문가인 당신이 리눅스, IOS, 안드로이드의 보안에 대해 체크하고, 적절한 가이드를 하려면 해당 OS 환경을 잘 이해하는 수밖에 없다. 마찬가지로 새로운 데이터베이스, 프레임워크나, 빅데이터 기술에 대한 보안을 체크하려면 해당 분야를 잘 이해할 수 밖에 없다(물론 관련 영역에 대한 스캐너 같은 여러 솔루션을 사용해서 해결하는 방법도 있겠지만, 원리와 무슨 일을 왜 수행 하는지 정확히 모르는 블랙박스 형태의 툴에 의존하는 것은 보안 일을 하는 사람으로서는 너무 순진한 행동이라고 본다). 물론 기존의 다른 유사한 분야에 대한 풍부한 이해는 새로운 분야에 대해서도 많은 부분 패턴으로 적용될 가능성이 높지만, 그것은 새로운 분야를 제대로 이해한 후에야 완전하게 발휘될 수 있는 능력일 것이다(물론 대상의 빠른 이해에도 일정부분은 도움은 된다고 본다). 새로운 분야에서 데이터가 어떻게 흘러가는지를 확실히 이해할 수 없다면, 기존에 익힌 보안에 대한 패턴들을 어디에 적용할지 몰라서 손가락만 빨고 있게 될 수도 있다(아마 아는척 사기를 치지 않는 이상은 말이다)

 

[거짓말 잘하는 비결 - 동아사이언스]

http://m.dongascience.donga.com/news.php?idx=6462

 

 

  또한 보안은 언제나 사실에 기반한 마술과 비슷하다. 취약점은 이해하기 어렵거나 신비로울 수는 있지만, 항상 사실(코드나 설계)에 기반하여 일어나는 것임은 분명하다(그래야 방어의 설계도 가능한 것일테고...). 보안 취약점은 기술의 빈 틈을 파고 들어가는것이며, 기술 자체의 안전한 구성원으로 위장하여 들어가, 데이터베이스 등 외부 요소나 기술 자체에 영향을 미치는 것을 의미한다.

 

  웹 보안에서 가장 흔하게 나오는 SQL Injection 같은 부분은, 만약 여러가지 데이터베이스의 명령어나 관리 기술에 두루 능통하다면, 정말 빠르게 공격과 방어에 대한 원리를 이해할 수 있다(A piece of cake라고 하고 싶다) . 그런데 만약 데이터베이스를 모르는 상태에서 SQL Injection 을 이해하려 한다면, 보안 자체의 측면보다는 데이터베이스와 SQL 문장의 이해의 늪에 빠져서 시간도 많이 걸리고 불완전하게 단편적으로 이해하고 넘어갈 가능성이 높다.

 

  비슷하게 자바스크립트를 기반으로 한 XSS 나 CSRF 같은 취약점 패턴 등도 자바스크립트를 얼마나 잘 이해하고 있느냐에 이해의 난이도와 깊이가 달려있고, 커맨드 인젝션이나, XML 인젝션 등도 얼마나 시스템 명령어나, 프로그램의 내부 구조 및 관련 함수들, XML 구조를 잘 이해하느냐에 중요 포인트가 있다(물론 특정 취약점은 프로그램의 다른 요소와 믹스되어서 효과가 증폭되거나 경감되는 부분도 있긴 때문에 한 기술의 타입으로 단순화 하기는 힘들지만 말이다). 해당 부분에 대한 이해가 충분하다면 보안 이라는 패턴을 적용해 어떻게 해당 기술이 악용이 되고, 방지할 수 있는지를 쉽게 이해할 수 있게 된다. 나아가 그 방어 방식의 한계 및 제약과, 한계에 따른 리스크를 다른 수단으로 보완하여 경감해야 된다는 사실도 인지할 수 있게 된다. 해킹이란게 시스템을 속이는 것이라 얘기되지만, 그 앞에는 사실 시스템을 (잘 이해하고) 속이는 것 이라는 중요한 말이 숨어 있다고 생각한다.

 

  그럼 이 글에서는 맘대로긴 하지만 (기술적인) 보안 공부를 다음과 같이 정의해 보도록 하겠다. "데이터의 흐름을 이해하기 위해서, 데이터가 흘러가는 공간을 구성하고 있는 기술에 대해서 이해하는 과정". 앞으로의 시간들은 쓰는 사람의 지식의 한계 때문에, 그러한 부분의 아주 깊은 곳까지는 안내하진 못하겠지만, 데이터의 흐름을 이해하기 위해 이용되는 여러가지 방법들을 살펴보는 방식으로 진행될 것 같다. 하지만 뭐 항상 그렇지만 진행되다보면 원래 의도와 다르게 흘러가기는 한다...

 

 

 

3. 보안의 공격과 방어 측면의 차이

  프로그래머들에게 조금 미안하긴 하지만 개인적으로 IT 쪽의 많은 분야에서 그다지 바람직하지는 못하다고 생각되는 흐름 중 하나는, 프로그래밍 경험이 있느냐에 대한 부가가치를 너무 크게 잡는 다는 부분이라고 본다. 물론 QA나 보안이나, 시스템, 빅데이터 등의 여러 분야에 대해서 프로그래밍 능력이 상당한 도움이 되는 것은 맞다.

 

  하지만 프로그래밍 실력을 미리 갖추고 일을 시작 하는게 중요한 거라기 보다는, 해당 업무의 특정 레벨에 올라가게 될때 자연스럽게 업무의 효율성과 확장성을 위해서 자연스럽게 필요성이 생기게 되는 것이라고 본다. 무언가 귀찮거나 반복되서 의미 없는 일을 줄이기 위해 위해서는 어쩔 수 없이 자동화의 힘을 빌려야 하고, 그러다 보면 조금씩 자연스럽게 프로그램이란 세계에 발을 들이게 된다. 물론 그 전에도 API 사용이나, 명령어 스크립트 작성 등 기본적인 프로그래밍 지식이 있으면 좀더 적응이 용이한 부분들도 분명히 있다. 

 

  개인적으로는 프로그래밍 능력이 있느냐가 원천적으로 있느냐가 중요하다기 보다는, 업무상 자연스럽게 필요하게 되서 프로그래밍 능력을 습득하게 된다는 것이 좀더 맞을 것 같다. 물론 해당 부분에 대한 부분은 금방 휘리릭 습득되는 부분은 아니기 때문에 직업의 초기부터 조금씩 꾸준히 노력하는 것이 맞아보인다. 추가로 분야에 따라 프로그래밍 이란 것도 각 분야에 적절한 스타일로 다르게 접근 되기 때문에 쓰이는 포커스가 다른 경우도 많다. 제일 중요한 것은 해당 분야의 도메인 지식을 기반으로 적절하게 프로그래밍 언어의 능력을 쓰는 것인것 같다.  파이썬 글 17교시의 머신러닝 부분에서 프로그래밍이 어떤 식으로 쓰이고 있는지와 비슷하게 보면 된다고 할까? 표현하긴 좀 어렵지만 프로그래밍은 프로그래머 들에게만 생기는 직업적 기술이라기 보다는 약간 공공재 적인 기술적 영역이라고 보고 싶다. 그래서 두려워 하지 말고 일단 뛰어들어 보는 것을 추천한다. 시간을 들여 익숙해 지면 야매 정도는 되니까...

 

 

 보안 쪽도 비슷한 흐름이 있다고 보는데, 웹어플리케이션 보안 부분을(특히 모의해킹이라고 칭해지는 부분) 특수한 인력이 특수한 절차를 밟아서 획득할 수 있는 완전한 검증 방식으로 생각한다는 부분이다. 모의해킹은 표면적인 효과 이외에 생각할 점이 많은 분야라고 본다. 

 

  우선 수행하는 사람의 능력에 많이 좌우된다. 보안 분야도 개발이나 테스팅과 비슷하게 사람들마다 접근하는 전략과 효율성의 차이가 많이 나고, 기술 및 도메인에 대한 이해도도 틀리며, 그 차이가 외부에서 볼때 확실히 구분되기 힘들다. 인력이나 도메인에 많은 영향을 받아 어떤 기술셋을 가진 사람이 어떤 분야를 점검하느냐도 중요하고, 어떤 언어나, 개발프로세스를 거쳐 만든 도메인을 만나느냐에 대한 운도 작용하는 것 같다. 또 점검 후 점검한 영역에서 실제 사고가 나거나, 비슷하거나 더 상위 수준(또는 비슷하지만 시간에 쫒기지 않은 한가한)의 인력이 충분한 시간을 가지고 크로스 체크 하기 전에는 효과를 증명하기 힘든 경우도 종종 있는 듯 싶다. 또 이해한 만큼만 점검이 가능하기 때문에, 시행자의 대상의 이해 정도에 의해서도 많이 차이가 나고, 일반적으로 리소스나 범위 등의 여러가지 사유로 소스 기반이 아닌 블랙박스나 그레이박스 기반, 그리고 제한된 시간 프레임 내에서 진행되기 때문에, 구성되어 있는 자원이나 소스에 대한 완전한 커버리지를 가지긴 힘든 경우도 많은것 같다.

 

  그리고 대부분의 경우 증명보다는 시나리오 기반으로 진행되기 때문에, 해당 시나리오를 벗어난 전체적인 보안성 측면에 대해서는 보장(Assureance)하지는 못한다고 보는게 맞을 것 같다. 또한 불행하게도 점검한 소스나 환경은 계속 변할 수도 있기 때문에, 해당 점검 시점 후 시간이 지나게 되면 다시 코드의 안전함을 보장하지 못한다는 모순에 빠지게 된다(개발자중 한명이 모의해킹이 완료된 후의 코드 베이스에 취약한 코드를 하나 넣었다면 어떻게 될까?).

 

  마지막으로 시스템이 너무 기초적인 보안 설계가 안 되어 있다면 중요한 취약점들을 다수 발견할 수는 있겠지만, 해당 취약점에 대부분의 시간을 쏟고, 진행이 막히게 되어, 더 미묘한 내부를 들여다 보지 못할 수도 있다. 뭐 중요한 기본 취약점들을 찾았다는 의미는 있긴 하지만, 해당 취약점들에 갈 길이 막혀 깊이 있는 취약점은 찾지 못할 수 있다. 기본적인 기능의 동작도 제대로 안되서 중요도가 높은 버그를 잔뜩 올렸지만, 해당 가능에 대해 깊이 있는 테스트를 하지 못하는 QA 테스트와 비슷한 상황이라고 보면 된다. 해당 경우는 모의해킹이 중요한게 아니라 시스템이나 프로세스를 기초부터 개선 하는게 더 중요할지도 모르는 상황이라고 본다. 분명히 모의해킹은 보안쪽의 중요한 요소이기도 하고, 수술에 꼭 필요한 잘 드는 메스이기는 하지만 은총알은 아니여서, 환자의 상태나 다른 검사 장비들과, 의사에 판단에 의해 잘 조합하여 사용해야 효과가 큰 것 같다.

 

  그래서 모의해킹은 어떻게 보면 공부를 열심히 하고 보게되는 고3의 수능 같은 평가 활동 같다고 보고 있다. 테스팅을 아무리 하더라도 최초부터 없는 품질을 얻을 수는 없는 것과 마찬가지로(중요한 버그들을 발견하고 고치는 행위자체가 반드시 품질을 보장하진 못한다 파이썬 머신러닝 파트에서 얘기했던 garbage in garbage out 과 마찬가지로...), 모의해킹으로 애초부터 들어 있지 않은 보안성을 잡을 수는 없다. 그런 믿음은 이미 다 지어진 집에서 발견된 균열들을 급하게 메우면서 집이 안전하길 바라는 것과 비슷하다. 사실은 설계 부터의 과정에서 뭔가 어긋나서 생긴 문제인데도 말이다. 

 

  보안성은 시험 전에 한참 꾸준히 해왔던 공부 같이, 여러 요구사항의 수집에서 시작하여, 안전한 설계, 개발의 과정 중에 이루어지는 여러 관점의 보안적 리뷰와 체크에서 생기는 것이라고 보는게 좀더 근본적일 것 같다. 모의해킹은 어플리케이션 보안에 대한 오랜 동안의 업계 사람들의 시각과 기술적 노하우, 취약점에 대한 개인의 감, 실제 경험들이 총 집성된 평가 체계라고 생각하지만, 역으로 그런 기본 역량을 충분히 갖춘 사람들이 평가를 해야지 충분한 의미가 있다는 단점도 분명히 있다는 것을 잊으면 안된다(물론 절차적인 면죄부라는 느낌이 좀 있긴 하다...) 

 

 

  그럼 해킹, 모의해킹과 같은 공격과 시큐어 코딩과 같은 방어의 차이는 무엇일까? 우선 둘의 공통점은 공격하고자 하는 대상과 방어하고자 하는 대상이 먼저 얘기한 데이터라는 것이다. 두 가지의 차이점은 , IT를 무협과 비교하는 걸 별로 선호하는 편은 아니지만, 무협만화에 나오는 빠른 검과 느린 도의 대결과 비슷하다고 본다(멋진 캐릭이 나오는 게임 또는 만화의 삽화를 넣고 싶긴 하지만, 저작권 문제로.. 무협 만화 좋아하는 사람은 열혈강호의 담화린(빠른칼)과 도제(느리지만 무거운 도)가 싸우는 장면을 상상해 보면 어떨까 싶다^^). 공격은 시스템의 헛점을 노리고 다양한 영향을 미치려고 계속 변화하면서 수행되는 반면, 방어는 사실 기본기에 충실해서 느리지만 핵심을 지키면서(靜中動) 공격으로 들어온 외부 코드들을 무용지물로 만들게 된다고 본다.

 

 

  그럼 느리지만 무거운 도에 해당하는 방어를 코드로 예를 들면 어떻게 될까? 개인적인 생각에는 아래와 같이 (외부에 입력된 변수로 부터 영향을 받을 가능성이 있는) 내부에서 사용할 변수의 타입과 범위를 체크하는 코드가 아닐까 싶다. 아래와 같이 정확한 정수 타입과 필요한 수의 범위를 제한하게 되면, (아래와 같은 숫자 변수에 대한 코드에 경우에는) 어떠한 인젝션 시도에도 안전한 코드가 될 것 같다.

1
2
3
4
5
6
ext_var = "union all ... --"
 
if type(ext_var) is int and ext_var > 0 and ext_var <= 10:
    print ("use variable");
else:
    print ("not safe");
cs

 

   해당 방어 코드를 모르는 공격자는 공격을 위해서 많은 화려한 기법을 사용할테고, 그 중에 똑똑한 공격자는 금새 잘 막혀 있다는 걸 알아채고, 시간낭비 없이 다른 취약한 변수나 코드들을 찾아 다니게 될 것이다. 좀 더 나아가면 해당 부분은 관련 방어를 지원하는 프레임워크를 쓰거나, 해당 프로그래밍 언어에서 지원하는 범용적인 보안 함수를 사용하는게 더 맞겠지만, 여기서는 방어하는 무거운 도(刀)에 대한 극단적인 이미지를 나타내기 위해서 아래와 같은 코드로 제시해 보았다. 문자열 변수 등과 같이 좀더 상세하게 따져가며 저울질할 부분들은 나중에 해당 주제에 대해서 다루면서 얘기하면 될 것 같다.

 

  파이썬 코드의 실행을 모를 경우 파이썬 2교시 글을 참고하면 된다

c:\Python\code>python variable_check.py
not safe

 

 

  한가지 더 인터넷에서 많이 언급되는 얘기를 생각해 보면, 공격의 대표적인 방식인 모의해킹의 기법을 이해해야만, 코드의 방어를 어떻게 해야 할지 제대로 알수 있다는 부분이 있다. 해당 부분은 맞는 얘기기는 하지만, 다른 한편으로 조금 더 생각해 볼  부분이 있다고 본다.

 

  방어 측면에서의 모의해킹의 이해란, 모의해킹에서 사용하는 기법이나 다양한 툴의 원리와 사용의 이해의 측면 보다는, 해당 공격으로 일어나는 데이터들의 변화에 초점을 맞추는 것이 맞을 듯 싶다(이 부분은 보안 테스팅을 위해 코드를 읽을 때도 비슷하게 적용된다. 물론 취약점의 원리를 이해하면 자동으로 툴이 무엇을 하려 하는지를 어느정도 이해하게 되겠지만 말이다..). 어차피 방어 측면에서는 공격과 같이 현란한 데이터의 변경으로 맞설 수는 없기 때문에(물론 뭐 요즘의 OS 등에서 실행파일이 로드되는 메모리 번지를 계속 변경 시킨다든지, 솔루션 등에서 머신러닝 등을 이용해서 비 정상적인 통계 패턴을 계속 추적한다든지 하는 등은 공격과 비슷한 다이나믹한 방어로 볼수 있긴 하겠지만), 일반적으로는 공격의 원리에 대치되는 배치로 대응하는 수밖에 없다. 공방 게임에서 공격 유닛에 대한 방어는, 해당 유닛에 상응하는 방어 건물의 배치로 이루어지는 것과 비슷하지 않을까 싶다. 그 방어 건물이 우리가 자주보게 되는 "시큐어 코딩 가이드" 같은 거고 말이다.

 

 물론 모의해킹의 아주 미묘한 부분까지 이해하여, 관련 전문가 수준으로 알게되면 분명 더 좋은 방어를 하게 될듯 싶다(물론 공격 뿐만 아닌 방어 관점의 이슈들도 충분히 잘 안다는 가정하에 얘기이다). 

 

  보안에 대한 분야는 사실 너무 넓은 분야이고, 모든 존재하는게 사실 보안과 연관이 있고, 그 안에 서식하고 있는 많은 요소들도, 계속 살아있는 생태계 처럼 변하고 있기 때문이다. 지금 해당 영역에서 완벽하더라도 앞으로 계속 따라기기 위해서 해야 될 공부가 너무 많다. 실제로 외부에서 보기엔 완벽해 보이는 사람들도, 좀 더 자세히 들여다 보면 생각보다는 완벽하지 못한 경우가 많고, 그것은 비단 보안 뿐만 아니라 모든 지식의 영역이 마찬가지인듯 싶다.

 

 

  

4. 마무리 하면서

  앞으로 글을 진행 하면서 앞에서 한 얘기들을 좀더 자세히 설명하기 위해서, 몇 가지 방향을 잡고 진행하려 한다. 우선 공격보다는 방어에 필요한 부분을 위주로 설명할 예정이며, 공격을 얘기할 경우는 방어 측면에서 이해가 필요한 공격의 측면을 중심으로 얘기하려고 한다. ASP, Python, PHP 와 같은 스크립트 언어와, 자세히는 모르지만 .NET 이나, JAVA 같은 주제를 잘 이해시켜 줄 수 있는 언어를 가지고, 가능한 실제 움직이는 코드를 구현하고자 한다. 그리고 피들러와 같은 관찰 및 조작 할수 있는 툴을 통해 데이터가 흘러가고, 변조 되는 부분을 시연하고, 방어를 위한 코드를 제시하며, 해당 방어의 원리와 제한 들에 대해서 얘기하려 한다. 

 

  비교적 잘 설명이 가능할 거리고 생각하는 웹 기술을 중심으로 이야기를 시작하며(사실 웹이나 다른 분야나 넓게 보면 기본적인 보안 패턴은 비슷하다고 생각하지만 아직은 크게 자신은 없다^^), 후반에 일반 어플리케이션에 대한 등에 대한 얘기도 얇은 지식이나마 정리해 볼까 한다. 이 글의 목표는 읽은 사람들이 각 보안 영역에 대해서 어떻게 접근해야 겠다는 스스로의 생각을 가지게 되고, 여러 보안 가이드를 볼 때, 해당 가이드를 작성한 사람이, 어떤 관점에서 그러한 방어 전략을 제시했는지에 대한 이해의 기반을 가지게 하는 것이다. 

 

  물론 앞의 파이썬 글과 마찬가지로 각 주제들에 대해 깊은 부분 까지 설명하기에는 스스로 아는 것이 적다고 생각하고, 시간의 제한도 받기 때문에 "보안에 대한 이런 저런 주제를 가볍게 다루는 기술적 산문집" 이라고 기대하고 읽어주면 좋을 듯하다. 내용 중 잘못됬거나 의견이 다른 부분은 댓글을 달아 알려주심 감사할듯 싶다.

 

그럼 재미있는 보안공부가 되길 바라며...

posted by 자유로운설탕
prev 1 next