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

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

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 자유로운설탕